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