001package com.nimbusds.oauth2.sdk.tokenexchange; 002 003 004import java.util.*; 005 006import net.jcip.annotations.Immutable; 007 008import com.nimbusds.oauth2.sdk.AuthorizationGrant; 009import com.nimbusds.oauth2.sdk.GrantType; 010import com.nimbusds.oauth2.sdk.OAuth2Error; 011import com.nimbusds.oauth2.sdk.ParseException; 012import com.nimbusds.oauth2.sdk.id.Audience; 013import com.nimbusds.oauth2.sdk.token.Token; 014import com.nimbusds.oauth2.sdk.token.TokenTypeURI; 015import com.nimbusds.oauth2.sdk.token.TypelessToken; 016import com.nimbusds.oauth2.sdk.util.CollectionUtils; 017import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils; 018import com.nimbusds.oauth2.sdk.util.StringUtils; 019 020 021/** 022 * OAuth 2.0 token exchange grant. 023 * 024 * <p>Related specifications: 025 * 026 * <ul> 027 * <li>OAuth 2.0 Token Exchange (RFC 8693). 028 * </ul> 029 */ 030@Immutable 031public class TokenExchangeGrant extends AuthorizationGrant { 032 033 034 /** 035 * The grant type. 036 */ 037 public static final GrantType GRANT_TYPE = GrantType.TOKEN_EXCHANGE; 038 039 040 /** 041 * The subject token representing the identity of the party on behalf 042 * of whom the request is being made. 043 */ 044 private final Token subjectToken; 045 046 047 /** 048 * Identifier for the type of the subject token. 049 */ 050 private final TokenTypeURI subjectTokenType; 051 052 053 /** 054 * Optional token representing the identity of the acting party. 055 */ 056 private final Token actorToken; 057 058 059 /** 060 * Identifier for the type of the actor token, if present. 061 */ 062 private final TokenTypeURI actorTokenType; 063 064 065 /** 066 * Optional identifier for the requested type of security token. 067 */ 068 private final TokenTypeURI requestedTokenType; 069 070 071 /** 072 * Optional audience for the requested security token. 073 */ 074 private final List<Audience> audience; 075 076 077 /** 078 * Creates a new token exchange grant. 079 * 080 * @param subjectToken The subject token representing the identity 081 * of the party on behalf of whom the request 082 * is being made. Must not be {@code null}. 083 * @param subjectTokenType Identifier for the type of the subject 084 * token. Must not be {@code null}. 085 */ 086 public TokenExchangeGrant(final Token subjectToken, 087 final TokenTypeURI subjectTokenType) { 088 089 this(subjectToken, subjectTokenType, null, null, null, null); 090 } 091 092 093 /** 094 * Creates a new token exchange grant. 095 * 096 * @param subjectToken The subject token representing the 097 * identity of the party on behalf of whom 098 * the request is being made. Must not be 099 * {@code null}. 100 * @param subjectTokenType Identifier for the type of the subject 101 * token. Must not be {@code null}. 102 * @param actorToken Optional token representing the identity 103 * of the acting party, {@code null} if not 104 * specified. 105 * @param actorTokenType Identifier for the type of the actor 106 * token, if present. 107 * @param requestedTokenType Optional identifier for the requested type 108 * of security token, {@code null} if not 109 * specified. 110 * @param audience Optional audience for the requested 111 * security token, {@code null} if not 112 * specified. 113 */ 114 public TokenExchangeGrant(final Token subjectToken, 115 final TokenTypeURI subjectTokenType, 116 final Token actorToken, 117 final TokenTypeURI actorTokenType, 118 final TokenTypeURI requestedTokenType, 119 final List<Audience> audience) { 120 121 super(GRANT_TYPE); 122 123 if (subjectToken == null) { 124 throw new IllegalArgumentException("The subject token must not be null"); 125 } 126 this.subjectToken = subjectToken; 127 128 if (subjectTokenType == null) { 129 throw new IllegalArgumentException("The subject token type must not be null"); 130 } 131 this.subjectTokenType = subjectTokenType; 132 133 this.actorToken = actorToken; 134 135 if (actorToken != null && actorTokenType == null) { 136 throw new IllegalArgumentException("If an actor token is specified the actor token type must not be null"); 137 } 138 this.actorTokenType = actorTokenType; 139 140 this.requestedTokenType = requestedTokenType; 141 142 this.audience = audience; 143 } 144 145 146 /** 147 * Returns the subject token representing the identity of the party on 148 * behalf of whom the request is being made. 149 * 150 * @return The subject token, {@code null} if not specified. 151 */ 152 public Token getSubjectToken() { 153 154 return subjectToken; 155 } 156 157 158 /** 159 * Returns the identifier for the type of the subject token. 160 * 161 * @return The subject token type identifier. 162 */ 163 public TokenTypeURI getSubjectTokenType() { 164 165 return subjectTokenType; 166 } 167 168 169 /** 170 * Returns the optional token representing the identity of the acting 171 * party. 172 * 173 * @return The actor token, {@code null} if not specified. 174 */ 175 public Token getActorToken() { 176 177 return actorToken; 178 } 179 180 181 /** 182 * Returns the identifier for the type of the optional actor token, if 183 * present. 184 * 185 * @return The actor token type identifier, {@code null} if not 186 * present. 187 */ 188 public TokenTypeURI getActorTokenType() { 189 190 return actorTokenType; 191 } 192 193 194 /** 195 * Returns the optional identifier for the requested type of security 196 * token. 197 * 198 * @return The requested token type, {@code null} if not specified. 199 */ 200 public TokenTypeURI getRequestedTokenType() { 201 202 return requestedTokenType; 203 } 204 205 206 /** 207 * Returns the optional audience for the requested security token. 208 * 209 * @return The audience, {@code null} if not specified. 210 */ 211 public List<Audience> getAudience() { 212 213 return audience; 214 } 215 216 217 @Override 218 public Map<String, List<String>> toParameters() { 219 220 Map<String, List<String>> params = new LinkedHashMap<>(); 221 222 params.put("grant_type", Collections.singletonList(GRANT_TYPE.getValue())); 223 224 if (CollectionUtils.isNotEmpty(audience)) { 225 params.put("audience", Audience.toStringList(audience)); 226 } 227 228 if (requestedTokenType != null) { 229 params.put("requested_token_type", Collections.singletonList(requestedTokenType.getURI().toString())); 230 } 231 232 params.put("subject_token", Collections.singletonList(subjectToken.getValue())); 233 params.put("subject_token_type", Collections.singletonList(subjectTokenType.getURI().toString())); 234 235 if (actorToken != null) { 236 params.put("actor_token", Collections.singletonList(actorToken.getValue())); 237 params.put("actor_token_type", Collections.singletonList(actorTokenType.getURI().toString())); 238 } 239 240 return params; 241 } 242 243 244 private static List<Audience> parseAudience(final Map<String, List<String>> params) { 245 246 List<String> audienceList = params.get("audience"); 247 248 if (CollectionUtils.isEmpty(audienceList)) { 249 return null; 250 } 251 252 return Audience.create(audienceList); 253 } 254 255 256 private static TokenTypeURI parseTokenType(final Map<String, List<String>> params, final String key, final boolean mandatory) 257 throws ParseException { 258 259 String tokenTypeString = MultivaluedMapUtils.getFirstValue(params, key); 260 261 if (StringUtils.isBlank(tokenTypeString)) { 262 if (mandatory) { 263 String msg = String.format("Missing or empty %s parameter", key); 264 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 265 } else { 266 return null; 267 } 268 } 269 270 try { 271 return TokenTypeURI.parse(tokenTypeString); 272 } catch (ParseException uriSyntaxException) { 273 String msg = "Invalid " + key + " " + tokenTypeString; 274 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 275 } 276 } 277 278 279 private static TypelessToken parseToken(final Map<String, List<String>> params, final String key, final boolean mandatory) 280 throws ParseException { 281 282 String tokenString = MultivaluedMapUtils.getFirstValue(params, key); 283 284 if (StringUtils.isBlank(tokenString)) { 285 286 if (mandatory) { 287 String msg = String.format("Missing or empty %s parameter", key); 288 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 289 } else { 290 return null; 291 } 292 } 293 294 return new TypelessToken(tokenString); 295 } 296 297 298 /** 299 * Parses a token exchange grant from the specified request body 300 * parameters. 301 * 302 * <p>Example: 303 * 304 * <pre> 305 * grant_type=urn:ietf:params:oauth:grant-type:token-exchange 306 * resource=https://backend.example.com/api 307 * subject_token=accVkjcJyb4BWCxGsndESCJQbdFMogUC5PbRDqceLTC 308 * subject_token_type=urn:ietf:params:oauth:token-type:access_token 309 * </pre> 310 * 311 * @param params The parameters. 312 * 313 * @return The token exchange grant. 314 * 315 * @throws ParseException If parsing failed. 316 */ 317 public static TokenExchangeGrant parse(final Map<String, List<String>> params) 318 throws ParseException { 319 320 GrantType.ensure(GRANT_TYPE, params); 321 322 List<Audience> audience = parseAudience(params); 323 TokenTypeURI requestedTokenType = parseTokenType(params, "requested_token_type", false); 324 TypelessToken subjectToken = parseToken(params, "subject_token", true); 325 TokenTypeURI subjectTokenType = parseTokenType(params, "subject_token_type", true); 326 TypelessToken actorToken = parseToken(params, "actor_token", false); 327 TokenTypeURI actorTokenType = parseTokenType(params, "actor_token_type", false); 328 329 return new TokenExchangeGrant(subjectToken, subjectTokenType, actorToken, actorTokenType, requestedTokenType, audience); 330 } 331 332 333 @Override 334 public boolean equals(final Object o) { 335 336 if (this == o) return true; 337 338 if (!(o instanceof TokenExchangeGrant)) return false; 339 340 TokenExchangeGrant that = (TokenExchangeGrant) o; 341 342 return 343 getSubjectToken().equals(that.getSubjectToken()) && 344 getSubjectTokenType().equals(that.getSubjectTokenType()) && 345 346 Objects.equals(getActorToken(), that.getActorToken()) && 347 Objects.equals(getActorTokenType(), that.getActorTokenType()) && 348 Objects.equals(getRequestedTokenType(), that.getRequestedTokenType()) && 349 Objects.equals(getAudience(), that.getAudience()); 350 } 351 352 353 @Override 354 public int hashCode() { 355 return Objects.hash( 356 getSubjectToken(), 357 getSubjectTokenType(), 358 getActorToken(), 359 getActorTokenType(), 360 getRequestedTokenType(), 361 getAudience() 362 ); 363 } 364}