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. This 180 * is determined by the presence of a {@code code} value. 181 * 182 * @return {@code true} if a code flow is implied, else {@code false}. 183 */ 184 public boolean impliesCodeFlow() { 185 186 return this.contains(Value.CODE); 187 } 188 189 190 /** 191 * Returns {@code true} if this response type implies an implicit flow. 192 * This is determined by the absence of a {@code code} value. 193 * 194 * @return {@code true} if an implicit flow is implied, else 195 * {@code false}. 196 */ 197 public boolean impliesImplicitFlow() { 198 199 return ! impliesCodeFlow(); 200 } 201 202 203 /** 204 * Checks if this response type contains the specified string value. 205 * 206 * @param value The string value. Must not be {@code null}. 207 * 208 * @return {@code true} if the value is contained, else {@code false}. 209 */ 210 public boolean contains(final String value) { 211 212 return contains(new Value(value)); 213 } 214 215 216 /** 217 * Returns the string representation of this authorisation response 218 * type. 219 * 220 * <p>Example serialised response types: 221 * 222 * <pre> 223 * code 224 * token 225 * id_token 226 * id_token token 227 * code token 228 * code id_token 229 * code id_token token 230 * </pre> 231 * 232 * @return Space delimited string representing the authorisation 233 * response type. 234 */ 235 @Override 236 public String toString() { 237 238 StringBuilder sb = new StringBuilder(); 239 240 for (ResponseType.Value v: this) { 241 242 if (sb.length() > 0) 243 sb.append(' '); 244 245 sb.append(v.getValue()); 246 } 247 248 return sb.toString(); 249 } 250}