001package com.nimbusds.oauth2.sdk; 002 003 004import java.util.Arrays; 005import java.util.HashSet; 006import java.util.StringTokenizer; 007 008import net.jcip.annotations.Immutable; 009import net.jcip.annotations.NotThreadSafe; 010 011import org.apache.commons.lang3.StringUtils; 012 013import com.nimbusds.oauth2.sdk.id.Identifier; 014 015 016/** 017 * Authorisation response type. Can be single-valued or multiple-valued. 018 * 019 * <p>The following helper methods can be used to find out the OAuth 2.0 020 * protocol flow that a particular response type implies: 021 * 022 * <ul> 023 * <li>{@link #impliesImplicitFlow} 024 * <li>{@link #impliesCodeFlow} 025 * </ul> 026 * 027 * <p>Example response type implying an authorisation code flow: 028 * 029 * <pre> 030 * ResponseType() rt = new ResponseType(); 031 * rt.add(ResponseType.Value.CODE); 032 * </pre> 033 * 034 * <p>Example response type from OpenID Connect specifying an ID token and an 035 * access token (implies implicit flow): 036 * 037 * <pre> 038 * ResponseType() rt = new ResponseType(); 039 * rt.add(OIDCResponseTypeValue.ID_TOKEN); 040 * rt.add(ResponseType.Value.TOKEN); 041 * </pre> 042 * 043 * <p>Related specifications: 044 * 045 * <ul> 046 * <li>OAuth 2.0 (RFC 6749), sections 3.1.1 and 4.1.1. 047 * <li>OAuth 2.0 Multiple Response Type Encoding Practices. 048 * </ul> 049 */ 050@NotThreadSafe 051public class ResponseType extends HashSet<ResponseType.Value> { 052 053 054 /** 055 * Authorisation response type value. 056 */ 057 @Immutable 058 public static final class Value extends Identifier { 059 060 /** 061 * Authorisation code. 062 */ 063 public static final Value CODE = new Value("code"); 064 065 066 /** 067 * Access token, with optional refresh token. 068 */ 069 public static final Value TOKEN = new Value("token"); 070 071 072 /** 073 * Creates a new response type value. 074 * 075 * @param value The response type value. Must not be 076 * {@code null} or empty string. 077 */ 078 public Value(final String value) { 079 080 super(value); 081 } 082 083 084 @Override 085 public boolean equals(final Object object) { 086 087 return object instanceof Value && 088 this.toString().equals(object.toString()); 089 } 090 } 091 092 093 /** 094 * Gets the default response type. 095 * 096 * @return The default response type, consisting of the value 097 * {@link ResponseType.Value#CODE}. 098 */ 099 public static ResponseType getDefault() { 100 101 ResponseType defaultResponseType = new ResponseType(); 102 defaultResponseType.add(ResponseType.Value.CODE); 103 return defaultResponseType; 104 } 105 106 107 /** 108 * Creates a new empty response type. 109 */ 110 public ResponseType() { 111 112 } 113 114 115 /** 116 * Creates a new response type with the specified string values. 117 * 118 * @param values The string values. Must not be {@code null}. 119 */ 120 public ResponseType(final String ... values) { 121 122 for (String v: values) 123 add(new Value(v)); 124 } 125 126 127 /** 128 * Creates a new response type with the specified values. 129 * 130 * @param values The values. Must not be {@code null}. 131 */ 132 public ResponseType(final Value ... values) { 133 134 addAll(Arrays.asList(values)); 135 } 136 137 138 /** 139 * Parses a set of authorisation response types. 140 * 141 * <p>Example serialised response type sets: 142 * 143 * <pre> 144 * code 145 * token 146 * id_token 147 * id_token token 148 * code token 149 * code id_token 150 * code id_token token 151 * </pre> 152 * 153 * @param s Space-delimited list of one or more authorisation response 154 * types. 155 * 156 * @return The authorisation response types set. 157 * 158 * @throws ParseException If the parsed string is {@code null} or 159 * empty. 160 */ 161 public static ResponseType parse(final String s) 162 throws ParseException { 163 164 if (StringUtils.isBlank(s)) 165 throw new ParseException("Null or empty response type string"); 166 167 ResponseType rt = new ResponseType(); 168 169 StringTokenizer st = new StringTokenizer(s, " "); 170 171 while (st.hasMoreTokens()) 172 rt.add(new ResponseType.Value(st.nextToken())); 173 174 return rt; 175 } 176 177 178 /** 179 * Returns {@code true} if this response type implies a code flow. 180 * 181 * @return {@code true} if a code flow is implied, else {@code false}. 182 */ 183 public boolean impliesCodeFlow() { 184 185 return this.contains(Value.CODE) && this.size() == 1; 186 } 187 188 189 /** 190 * Returns {@code true} if this response type implies an implicit flow. 191 * 192 * @return {@code true} if an implicit flow is implied, else 193 * {@code false}. 194 */ 195 public boolean impliesImplicitFlow() { 196 197 return ! impliesCodeFlow(); 198 } 199 200 201 /** 202 * Checks if this response type contains the specified string value. 203 * 204 * @param value The string value. Must not be {@code null}. 205 * 206 * @return {@code true} if the value is contained, else {@code false}. 207 */ 208 public boolean contains(final String value) { 209 210 return contains(new Value(value)); 211 } 212 213 214 /** 215 * Returns the string representation of this authorisation response 216 * type. 217 * 218 * <p>Example serialised response types: 219 * 220 * <pre> 221 * code 222 * token 223 * id_token 224 * id_token token 225 * code token 226 * code id_token 227 * code id_token token 228 * </pre> 229 * 230 * @return Space delimited string representing the authorisation 231 * response type. 232 */ 233 @Override 234 public String toString() { 235 236 StringBuilder sb = new StringBuilder(); 237 238 for (ResponseType.Value v: this) { 239 240 if (sb.length() > 0) 241 sb.append(' '); 242 243 sb.append(v.getValue()); 244 } 245 246 return sb.toString(); 247 } 248}