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}