001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.oauth2.sdk;
019
020
021import com.nimbusds.common.contenttype.ContentType;
022import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
023import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
024import com.nimbusds.oauth2.sdk.http.HTTPRequest;
025import com.nimbusds.oauth2.sdk.id.ClientID;
026import com.nimbusds.oauth2.sdk.rar.AuthorizationDetail;
027import com.nimbusds.oauth2.sdk.token.RefreshToken;
028import com.nimbusds.oauth2.sdk.util.*;
029import net.jcip.annotations.Immutable;
030
031import java.net.URI;
032import java.net.URISyntaxException;
033import java.util.*;
034
035
036/**
037 * Token request. Used to obtain an
038 * {@link com.nimbusds.oauth2.sdk.token.AccessToken access token} and an
039 * optional {@link com.nimbusds.oauth2.sdk.token.RefreshToken refresh token}
040 * at the Token endpoint of the authorisation server. Supports custom request
041 * parameters.
042 *
043 * <p>Example token request with an authorisation code grant:
044 *
045 * <pre>
046 * POST /token HTTP/1.1
047 * Host: server.example.com
048 * Content-Type: application/x-www-form-urlencoded
049 * Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
050 *
051 * grant_type=authorization_code
052 * &amp;code=SplxlOBeZQQYbYS6WxSbIA
053 * &amp;redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
054 * </pre>
055 *
056 * <p>Related specifications:
057 *
058 * <ul>
059 *     <li>OAuth 2.0 (RFC 6749), sections 4.1.3, 4.3.2, 4.4.2 and 6.
060 *     <li>OAuth 2.0 Rich Authorization Requests (RFC 9396), section 6.
061 *     <li>Resource Indicators for OAuth 2.0 (RFC 8707)
062 *     <li>OAuth 2.0 Incremental Authorization
063 *         (draft-ietf-oauth-incremental-authz-04)
064 * </ul>
065 */
066@Immutable
067public class TokenRequest extends AbstractOptionallyIdentifiedRequest {
068
069
070        /**
071         * The authorisation grant.
072         */
073        private final AuthorizationGrant authzGrant;
074
075
076        /**
077         * The requested scope, {@code null} if not specified.
078         */
079        private final Scope scope;
080
081
082        /**
083         * The RAR details (optional).
084         */
085        private final List<AuthorizationDetail> authorizationDetails;
086        
087        
088        /**
089         * The resource URI(s), {@code null} if not specified.
090         */
091        private final List<URI> resources;
092        
093        
094        /**
095         * Existing refresh token for incremental authorisation of a public
096         * client, {@code null} if not specified.
097         */
098        private final RefreshToken existingGrant;
099
100
101        /**
102         * Custom request parameters.
103         */
104        private final Map<String,List<String>> customParams;
105
106
107        private static final Set<String> ALLOWED_REPEATED_PARAMS = new HashSet<>(Arrays.asList("resource", "audience"));
108
109
110        /**
111         * Creates a new token request with the specified client
112         * authentication.
113         *
114         * @param uri        The URI of the token endpoint. May be
115         *                   {@code null} if the {@link #toHTTPRequest} method
116         *                   will not be used.
117         * @param clientAuth The client authentication. Must not be
118         *                   {@code null}.
119         * @param authzGrant The authorisation grant. Must not be {@code null}.
120         * @param scope      The requested scope, {@code null} if not
121         *                   specified.
122         */
123        public TokenRequest(final URI uri,
124                            final ClientAuthentication clientAuth,
125                            final AuthorizationGrant authzGrant,
126                            final Scope scope) {
127
128                this(uri, clientAuth, authzGrant, scope, null, null);
129        }
130
131
132        /**
133         * Creates a new token request with the specified client
134         * authentication and extension and custom parameters.
135         *
136         * @param uri          The URI of the token endpoint. May be
137         *                     {@code null} if the {@link #toHTTPRequest}
138         *                     method will not be used.
139         * @param clientAuth   The client authentication. Must not be
140         *                     {@code null}.
141         * @param authzGrant   The authorisation grant. Must not be
142         *                     {@code null}.
143         * @param scope        The requested scope, {@code null} if not
144         *                     specified.
145         * @param resources    The resource URI(s), {@code null} if not
146         *                     specified.
147         * @param customParams Custom parameters to be included in the request
148         *                     body, empty map or {@code null} if none.
149         */
150        public TokenRequest(final URI uri,
151                            final ClientAuthentication clientAuth,
152                            final AuthorizationGrant authzGrant,
153                            final Scope scope,
154                            final List<URI> resources,
155                            final Map<String,List<String>> customParams) {
156
157                this(uri, clientAuth, authzGrant, scope, null, resources, customParams);
158        }
159
160
161        /**
162         * Creates a new token request with the specified client
163         * authentication and extension and custom parameters.
164         *
165         * @param uri                  The URI of the token endpoint. May be
166         *                             {@code null} if the
167         *                             {@link #toHTTPRequest} method will not
168         *                             be used.
169         * @param clientAuth           The client authentication. Must not be
170         *                             {@code null}.
171         * @param authzGrant           The authorisation grant. Must not be
172         *                             {@code null}.
173         * @param scope                The requested scope, {@code null} if not
174         *                             specified.
175         * @param authorizationDetails The Rich Authorisation Request (RAR)
176         *                             details, {@code null} if not specified.
177         * @param resources            The resource URI(s), {@code null} if not
178         *                             specified.
179         * @param customParams         Custom parameters to be included in the
180         *                             request body, empty map or {@code null}
181         *                             if none.
182         */
183        public TokenRequest(final URI uri,
184                            final ClientAuthentication clientAuth,
185                            final AuthorizationGrant authzGrant,
186                            final Scope scope,
187                            final List<AuthorizationDetail> authorizationDetails,
188                            final List<URI> resources,
189                            final Map<String,List<String>> customParams) {
190
191                super(uri, clientAuth);
192
193                if (clientAuth == null)
194                        throw new IllegalArgumentException("The client authentication must not be null");
195
196                this.authzGrant = authzGrant;
197
198                this.scope = scope;
199
200                if (resources != null) {
201                        for (URI resourceURI: resources) {
202                                if (! ResourceUtils.isLegalResourceURI(resourceURI))
203                                        throw new IllegalArgumentException("Resource URI must be absolute and with no query or fragment: " + resourceURI);
204                        }
205                }
206
207                this.authorizationDetails = authorizationDetails;
208
209                this.resources = resources;
210
211                this.existingGrant = null; // only for confidential client
212
213                if (MapUtils.isNotEmpty(customParams)) {
214                        this.customParams = customParams;
215                } else {
216                        this.customParams = Collections.emptyMap();
217                }
218        }
219
220
221        /**
222         * Creates a new token request with the specified client
223         * authentication.
224         *
225         * @param uri        The URI of the token endpoint. May be
226         *                   {@code null} if the {@link #toHTTPRequest} method
227         *                   will not be used.
228         * @param clientAuth The client authentication. Must not be
229         *                   {@code null}.
230         * @param authzGrant The authorisation grant. Must not be {@code null}.
231         */
232        public TokenRequest(final URI uri,
233                            final ClientAuthentication clientAuth,
234                            final AuthorizationGrant authzGrant) {
235
236                this(uri, clientAuth, authzGrant, null);
237        }
238
239
240        /**
241         * Creates a new token request, with no explicit client authentication
242         * (maybe present in the grant depending on its type).
243         *
244         * @param uri        The URI of the token endpoint. May be
245         *                   {@code null} if the {@link #toHTTPRequest} method
246         *                   will not be used.
247         * @param clientID   The client identifier, {@code null} if not
248         *                   specified.
249         * @param authzGrant The authorisation grant. Must not be {@code null}.
250         * @param scope      The requested scope, {@code null} if not
251         *                   specified.
252         */
253        public TokenRequest(final URI uri,
254                            final ClientID clientID,
255                            final AuthorizationGrant authzGrant,
256                            final Scope scope) {
257
258                this(uri, clientID, authzGrant, scope, null, null,null);
259        }
260
261
262        /**
263         * Creates a new token request, with no explicit client authentication
264         * (maybe present in the grant depending on its type) and extension
265         * and custom parameters.
266         *
267         * @param uri           The URI of the token endpoint. May be
268         *                      {@code null} if the {@link #toHTTPRequest}
269         *                      method will not be used.
270         * @param clientID      The client identifier, {@code null} if not
271         *                      specified.
272         * @param authzGrant    The authorisation grant. Must not be
273         *                      {@code null}.
274         * @param scope         The requested scope, {@code null} if not
275         *                      specified.
276         * @param resources     The resource URI(s), {@code null} if not
277         *                      specified.
278         * @param existingGrant Existing refresh token for incremental
279         *                      authorisation of a public client, {@code null}
280         *                      if not specified.
281         * @param customParams  Custom parameters to be included in the request
282         *                      body, empty map or {@code null} if none.
283         */
284        public TokenRequest(final URI uri,
285                            final ClientID clientID,
286                            final AuthorizationGrant authzGrant,
287                            final Scope scope,
288                            final List<URI> resources,
289                            final RefreshToken existingGrant,
290                            final Map<String,List<String>> customParams) {
291
292                this(uri, clientID, authzGrant, scope, null, resources, existingGrant, customParams);
293        }
294
295
296        /**
297         * Creates a new token request, with no explicit client authentication
298         * (maybe present in the grant depending on its type) and extension
299         * and custom parameters.
300         *
301         * @param uri                  The URI of the token endpoint. May be
302         *                             {@code null} if the
303         *                             {@link #toHTTPRequest}
304         *                             method will not be used.
305         * @param clientID             The client identifier, {@code null} if
306         *                             not specified.
307         * @param authzGrant           The authorisation grant. Must not be
308         *                             {@code null}.
309         * @param scope                The requested scope, {@code null} if not
310         *                             specified.
311         * @param authorizationDetails The Rich Authorisation Request (RAR)
312         *                             details, {@code null} if not specified.
313         * @param resources            The resource URI(s), {@code null} if not
314         *                             specified.
315         * @param existingGrant        Existing refresh token for incremental
316         *                             authorisation of a public client,
317         *                             {@code null} if not specified.
318         * @param customParams         Custom parameters to be included in the
319         *                             request body, empty map or {@code null}
320         *                             if none.
321         */
322        public TokenRequest(final URI uri,
323                            final ClientID clientID,
324                            final AuthorizationGrant authzGrant,
325                            final Scope scope,
326                            final List<AuthorizationDetail> authorizationDetails,
327                            final List<URI> resources,
328                            final RefreshToken existingGrant,
329                            final Map<String,List<String>> customParams) {
330
331                super(uri, clientID);
332
333                if (authzGrant.getType().requiresClientAuthentication()) {
334                        throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires client authentication");
335                }
336
337                if (authzGrant.getType().requiresClientID() && clientID == null) {
338                        throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires a \"client_id\" parameter");
339                }
340
341                this.authzGrant = authzGrant;
342
343                this.scope = scope;
344
345                this.authorizationDetails = authorizationDetails;
346
347                if (resources != null) {
348                        for (URI resourceURI: resources) {
349                                if (! ResourceUtils.isLegalResourceURI(resourceURI))
350                                        throw new IllegalArgumentException("Resource URI must be absolute and with no query or fragment: " + resourceURI);
351                        }
352                }
353
354                this.resources = resources;
355
356                this.existingGrant = existingGrant;
357
358                if (MapUtils.isNotEmpty(customParams)) {
359                        this.customParams = customParams;
360                } else {
361                        this.customParams = Collections.emptyMap();
362                }
363        }
364
365
366        /**
367         * Creates a new token request, with no explicit client authentication
368         * (maybe present in the grant depending on its type).
369         *
370         * @param uri        The URI of the token endpoint. May be
371         *                   {@code null} if the {@link #toHTTPRequest} method
372         *                   will not be used.
373         * @param clientID   The client identifier, {@code null} if not
374         *                   specified.
375         * @param authzGrant The authorisation grant. Must not be {@code null}.
376         */
377        public TokenRequest(final URI uri,
378                            final ClientID clientID,
379                            final AuthorizationGrant authzGrant) {
380
381                this(uri, clientID, authzGrant, null);
382        }
383
384
385        /**
386         * Creates a new token request, without client authentication and a
387         * specified client identifier.
388         *
389         * @param uri        The URI of the token endpoint. May be
390         *                   {@code null} if the {@link #toHTTPRequest} method
391         *                   will not be used.
392         * @param authzGrant The authorisation grant. Must not be {@code null}.
393         * @param scope      The requested scope, {@code null} if not
394         *                   specified.
395         */
396        public TokenRequest(final URI uri,
397                            final AuthorizationGrant authzGrant,
398                            final Scope scope) {
399
400                this(uri, (ClientID)null, authzGrant, scope);
401        }
402
403
404        /**
405         * Creates a new token request, without client authentication and a
406         * specified client identifier.
407         *
408         * @param uri        The URI of the token endpoint. May be
409         *                   {@code null} if the {@link #toHTTPRequest} method
410         *                   will not be used.
411         * @param authzGrant The authorisation grant. Must not be {@code null}.
412         */
413        public TokenRequest(final URI uri,
414                            final AuthorizationGrant authzGrant) {
415
416                this(uri, (ClientID)null, authzGrant, null);
417        }
418
419
420        /**
421         * Returns the authorisation grant.
422         *
423         * @return The authorisation grant.
424         */
425        public AuthorizationGrant getAuthorizationGrant() {
426
427                return authzGrant;
428        }
429
430
431        /**
432         * Returns the requested scope.
433         *
434         * @return The requested scope, {@code null} if not specified.
435         */
436        public Scope getScope() {
437
438                return scope;
439        }
440
441
442        /**
443         * Returns the Rich Authorisation Request (RAR) details.
444         *
445         * @return The authorisation details, {@code null} if not specified.
446         */
447        public List<AuthorizationDetail> getAuthorizationDetails() {
448
449                return authorizationDetails;
450        }
451        
452        
453        /**
454         * Returns the resource server URI.
455         *
456         * @return The resource URI(s), {@code null} if not specified.
457         */
458        public List<URI> getResources() {
459                
460                return resources;
461        }
462        
463        
464        /**
465         * Returns the existing refresh token for incremental authorisation of
466         * a public client, {@code null} if not specified.
467         *
468         * @return The existing grant, {@code null} if not specified.
469         */
470        public RefreshToken getExistingGrant() {
471                
472                return existingGrant;
473        }
474
475
476        /**
477         * Returns the additional custom parameters included in the request
478         * body.
479         *
480         * <p>Example:
481         *
482         * <pre>
483         * resource=http://xxxxxx/PartyOData
484         * </pre>
485         *
486         * @return The additional custom parameters as an unmodifiable map,
487         *         empty map if none.
488         */
489        public Map<String,List<String>> getCustomParameters () {
490
491                return Collections.unmodifiableMap(customParams);
492        }
493
494
495        /**
496         * Returns the specified custom parameter included in the request body.
497         *
498         * @param name The parameter name. Must not be {@code null}.
499         *
500         * @return The parameter value(s), {@code null} if not specified.
501         */
502        public List<String> getCustomParameter(final String name) {
503
504                return customParams.get(name);
505        }
506
507
508        @Override
509        public HTTPRequest toHTTPRequest() {
510
511                if (getEndpointURI() == null)
512                        throw new SerializeException("The endpoint URI is not specified");
513
514                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getEndpointURI());
515                httpRequest.setEntityContentType(ContentType.APPLICATION_URLENCODED);
516
517                if (getClientAuthentication() != null) {
518                        getClientAuthentication().applyTo(httpRequest);
519                }
520
521                Map<String,List<String>> params = httpRequest.getQueryParameters();
522
523                params.putAll(authzGrant.toParameters());
524
525                if (scope != null && ! scope.isEmpty()) {
526                        params.put("scope", Collections.singletonList(scope.toString()));
527                }
528
529                if (getClientID() != null) {
530                        params.put("client_id", Collections.singletonList(getClientID().getValue()));
531                }
532
533                if (getAuthorizationDetails() != null) {
534                        params.put("authorization_details", Collections.singletonList(AuthorizationDetail.toJSONString(getAuthorizationDetails())));
535                }
536                
537                if (getResources() != null) {
538                        List<String> values = new LinkedList<>();
539                        for (URI uri: resources) {
540                                if (uri == null)
541                                        continue;
542                                values.add(uri.toString());
543                        }
544                        params.put("resource", values);
545                }
546                
547                if (getExistingGrant() != null) {
548                        params.put("existing_grant", Collections.singletonList(existingGrant.getValue()));
549                }
550
551                if (! getCustomParameters().isEmpty()) {
552                        params.putAll(getCustomParameters());
553                }
554
555                httpRequest.setQuery(URLUtils.serializeParameters(params));
556
557                return httpRequest;
558        }
559
560
561        /**
562         * Parses a token request from the specified HTTP request.
563         *
564         * @param httpRequest The HTTP request. Must not be {@code null}.
565         *
566         * @return The token request.
567         *
568         * @throws ParseException If the HTTP request couldn't be parsed to a
569         *                        token request.
570         */
571        public static TokenRequest parse(final HTTPRequest httpRequest)
572                throws ParseException {
573
574                // Only HTTP POST accepted
575                URI uri = httpRequest.getURI();
576                httpRequest.ensureMethod(HTTPRequest.Method.POST);
577                httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED);
578
579                // Parse client authentication, if any
580                ClientAuthentication clientAuth;
581                
582                try {
583                        clientAuth = ClientAuthentication.parse(httpRequest);
584                } catch (ParseException e) {
585                        throw new ParseException(e.getMessage(), OAuth2Error.INVALID_REQUEST.appendDescription(": " + e.getMessage()));
586                }
587
588                // No fragment! May use query component!
589                Map<String,List<String>> params = httpRequest.getQueryParameters();
590                
591                Set<String> repeatParams = MultivaluedMapUtils.getKeysWithMoreThanOneValue(params, ALLOWED_REPEATED_PARAMS);
592                if (! repeatParams.isEmpty()) {
593                        String msg = "Parameter(s) present more than once: " + repeatParams;
594                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.setDescription(msg));
595                }
596                
597                // Multiple conflicting client auth methods (issue #203)?
598                if (clientAuth instanceof ClientSecretBasic) {
599                        if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion")) || StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion_type"))) {
600                                String msg = "Multiple conflicting client authentication methods found: Basic and JWT assertion";
601                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
602                        }
603                }
604
605                // Parse grant
606                AuthorizationGrant grant = AuthorizationGrant.parse(params);
607
608                if (clientAuth == null && grant.getType().requiresClientAuthentication()) {
609                        String msg = "Missing client authentication";
610                        throw new ParseException(msg, OAuth2Error.INVALID_CLIENT.appendDescription(": " + msg));
611                }
612
613                // Parse client id
614                ClientID clientID = null;
615
616                if (clientAuth == null) {
617
618                        // Parse optional client ID
619                        String clientIDString = MultivaluedMapUtils.getFirstValue(params, "client_id");
620
621                        if (clientIDString != null && ! clientIDString.trim().isEmpty())
622                                clientID = new ClientID(clientIDString);
623
624                        if (clientID == null && grant.getType().requiresClientID()) {
625                                String msg = "Missing required client_id parameter";
626                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
627                        }
628                }
629
630                // Parse optional scope
631                String scopeValue = MultivaluedMapUtils.getFirstValue(params, "scope");
632
633                Scope scope = null;
634
635                if (scopeValue != null) {
636                        scope = Scope.parse(scopeValue);
637                }
638
639                // Parse optional RAR
640                String json = MultivaluedMapUtils.getFirstValue(params, "authorization_details");
641
642                List<AuthorizationDetail> authorizationDetails = null;
643
644                if (json != null) {
645                        authorizationDetails = AuthorizationDetail.parseList(json);
646                }
647                
648                // Parse optional resource URIs
649                List<URI> resources = null;
650                
651                List<String> vList = params.get("resource");
652                
653                if (vList != null) {
654                        
655                        resources = new LinkedList<>();
656                        
657                        for (String uriValue: vList) {
658                                
659                                if (uriValue == null)
660                                        continue;
661                                
662                                String errMsg = "Illegal resource parameter: Must be an absolute URI without a fragment: " + uriValue;
663                                
664                                URI resourceURI;
665                                try {
666                                        resourceURI = new URI(uriValue);
667                                } catch (URISyntaxException e) {
668                                        throw new ParseException(errMsg, OAuth2Error.INVALID_TARGET.setDescription(errMsg));
669                                }
670                                
671                                if (! ResourceUtils.isLegalResourceURI(resourceURI)) {
672                                        throw new ParseException(errMsg, OAuth2Error.INVALID_TARGET.setDescription(errMsg));
673                                }
674                                
675                                resources.add(resourceURI);
676                        }
677                }
678                
679                String rt = MultivaluedMapUtils.getFirstValue(params, "existing_grant");
680                RefreshToken existingGrant = StringUtils.isNotBlank(rt) ? new RefreshToken(rt) : null;
681
682                // Parse custom parameters
683                Map<String,List<String>> customParams = new HashMap<>();
684
685                for (Map.Entry<String,List<String>> p: params.entrySet()) {
686
687                        if (p.getKey().equalsIgnoreCase("grant_type")) {
688                                continue; // skip
689                        }
690
691                        if (p.getKey().equalsIgnoreCase("client_id")) {
692                                continue; // skip
693                        }
694
695                        if (p.getKey().equalsIgnoreCase("client_secret")) {
696                                continue; // skip
697                        }
698
699                        if (p.getKey().equalsIgnoreCase("client_assertion_type")) {
700                                continue; // skip
701                        }
702
703                        if (p.getKey().equalsIgnoreCase("client_assertion")) {
704                                continue; // skip
705                        }
706
707                        if (p.getKey().equalsIgnoreCase("scope")) {
708                                continue; // skip
709                        }
710
711                        if (p.getKey().equalsIgnoreCase("authorization_details")) {
712                                continue; // skip
713                        }
714                        
715                        if (p.getKey().equalsIgnoreCase("resource")) {
716                                continue; // skip
717                        }
718                        
719                        if (p.getKey().equalsIgnoreCase("existing_grant"))
720                                continue; // skip
721
722                        if (! grant.getType().getRequestParameterNames().contains(p.getKey())) {
723                                // We have a custom (non-registered) parameter
724                                customParams.put(p.getKey(), p.getValue());
725                        }
726                }
727
728                if (clientAuth != null) {
729                        return new TokenRequest(uri, clientAuth, grant, scope, authorizationDetails, resources, customParams);
730                } else {
731                        // public client
732                        return new TokenRequest(uri, clientID, grant, scope, authorizationDetails, resources, existingGrant, customParams);
733                }
734        }
735}