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