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}