001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2023, 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.AccessToken;
028import com.nimbusds.oauth2.sdk.token.RefreshToken;
029import com.nimbusds.oauth2.sdk.util.*;
030import com.nimbusds.openid.connect.sdk.nativesso.DeviceSecret;
031import net.jcip.annotations.Immutable;
032
033import java.net.URI;
034import java.net.URISyntaxException;
035import java.util.*;
036
037
038/**
039 * Token request. Used to obtain an {@link AccessToken access token} and an
040 * optional {@link RefreshToken refresh token} at the tokens endpoint of an
041 * authorisation server. Supports custom request 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)
060 *     <li>OAuth 2.0 Rich Authorization Requests (RFC 9396)
061 *     <li>Resource Indicators for OAuth 2.0 (RFC 8707)
062 *     <li>OAuth 2.0 Incremental Authorization (draft-ietf-oauth-incremental-authz)
063 *     <li>OpenID Connect Native SSO for Mobile Apps 1.0
064 * </ul>
065 */
066@Immutable
067public class TokenRequest extends AbstractOptionallyIdentifiedRequest {
068
069
070        /**
071         * The registered parameter names.
072         */
073        private static final Set<String> REGISTERED_PARAMETER_NAMES;
074
075        static {
076                Set<String> p = new HashSet<>();
077
078                p.add("grant_type");
079                p.add("client_id");
080                p.add("client_secret");
081                p.add("client_assertion_type");
082                p.add("client_assertion");
083                p.add("scope");
084                p.add("authorization_details");
085                p.add("resource");
086                p.add("existing_grant");
087                p.add("device_secret");
088
089                REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p);
090        }
091
092
093        /**
094         * The authorisation grant.
095         */
096        private final AuthorizationGrant authzGrant;
097
098
099        /**
100         * The scope (optional).
101         */
102        private final Scope scope;
103
104
105        /**
106         * The RAR details (optional).
107         */
108        private final List<AuthorizationDetail> authorizationDetails;
109        
110        
111        /**
112         * The resource URI(s) (optional).
113         */
114        private final List<URI> resources;
115        
116        
117        /**
118         * Existing refresh token for incremental authorisation of a public
119         * client (optional).
120         */
121        private final RefreshToken existingGrant;
122
123
124        /**
125         * Device secret for native SSO (optional).
126         */
127        private final DeviceSecret deviceSecret;
128
129
130        /**
131         * Custom request parameters.
132         */
133        private final Map<String,List<String>> customParams;
134
135
136        private static final Set<String> ALLOWED_REPEATED_PARAMS = new HashSet<>(Arrays.asList(
137                "resource", // https://www.rfc-editor.org/rfc/rfc8707.html#section-2.2
138                "audience" // https://www.rfc-editor.org/rfc/rfc8693.html#name-relationship-between-resour
139        ));
140
141
142        /**
143         * Builder for constructing token requests.
144         */
145        public static class Builder {
146
147
148                /**
149                 * The endpoint URI (optional).
150                 */
151                private final URI endpoint;
152
153
154                /**
155                 * The client authentication, {@code null} if none.
156                 */
157                private final ClientAuthentication clientAuth;
158
159
160                /**
161                 * The client identifier, {@code null} if not specified.
162                 */
163                private final ClientID clientID;
164
165
166                /**
167                 * The authorisation grant.
168                 */
169                private final AuthorizationGrant authzGrant;
170
171
172                /**
173                 * The scope (optional).
174                 */
175                private Scope scope;
176
177
178                /**
179                 * The RAR details (optional).
180                 */
181                private List<AuthorizationDetail> authorizationDetails;
182
183
184                /**
185                 * The resource URI(s) (optional).
186                 */
187                private List<URI> resources;
188
189
190                /**
191                 * Existing refresh token for incremental authorisation of a
192                 * public client (optional).
193                 */
194                private RefreshToken existingGrant;
195
196
197                /**
198                 * Device secret for native SSO (optional).
199                 */
200                private DeviceSecret deviceSecret;
201
202
203                /**
204                 * Custom parameters.
205                 */
206                private final Map<String,List<String>> customParams = new HashMap<>();
207
208
209                /**
210                 * Creates a new builder for a token request with client
211                 * authentication.
212                 *
213                 * @param endpoint   The URI of the token endpoint. May be
214                 *                   {@code null} if the {@link #toHTTPRequest}
215                 *                   method is not going to be used.
216                 * @param clientAuth The client authentication. Must not be
217                 *                   {@code null}.
218                 * @param authzGrant The authorisation grant. Must not be
219                 *                   {@code null}.
220                 */
221                public Builder(final URI endpoint,
222                               final ClientAuthentication clientAuth,
223                               final AuthorizationGrant authzGrant) {
224                        this.endpoint = endpoint;
225                        this.clientAuth = Objects.requireNonNull(clientAuth);
226                        clientID = null;
227                        this.authzGrant = Objects.requireNonNull(authzGrant);
228                }
229
230
231                /**
232                 * Creates a new builder for a token request with no (explicit)
233                 * client authentication. The grant itself may be used to
234                 * authenticate the client.
235                 *
236                 * @param endpoint   The URI of the token endpoint. May be
237                 *                   {@code null} if the {@link #toHTTPRequest}
238                 *                   method is not going to be used.
239                 * @param clientID   The client identifier. Must not be
240                 *                   {@code null}.
241                 * @param authzGrant The authorisation grant. Must not be
242                 *                   {@code null}.
243                 */
244                public Builder(final URI endpoint,
245                               final ClientID clientID,
246                               final AuthorizationGrant authzGrant) {
247                        this.endpoint = endpoint;
248                        clientAuth = null;
249                        this.clientID = Objects.requireNonNull(clientID);
250                        this.authzGrant = Objects.requireNonNull(authzGrant);
251                }
252
253
254                /**
255                 * Creates a new builder for a token request with no (explicit)
256                 * client authentication, the client identifier is inferred
257                 * from the authorisation grant.
258                 *
259                 * @param endpoint   The URI of the token endpoint. May be
260                 *                   {@code null} if the {@link #toHTTPRequest}
261                 *                   method is not going to be used.
262                 * @param authzGrant The authorisation grant. Must not be
263                 *                   {@code null}.
264                 */
265                public Builder(final URI endpoint,
266                               final AuthorizationGrant authzGrant) {
267                        this.endpoint = endpoint;
268                        clientAuth = null;
269                        clientID = null;
270                        this.authzGrant = Objects.requireNonNull(authzGrant);
271                }
272
273
274                /**
275                 * Sets the scope. Corresponds to the optional {@code scope}
276                 * parameter.
277                 *
278                 * @param scope The scope, {@code null} if not specified.
279                 *
280                 * @return This builder.
281                 */
282                public Builder scope(final Scope scope) {
283                        this.scope = scope;
284                        return this;
285                }
286
287
288                /**
289                 * Sets the Rich Authorisation Request (RAR) details.
290                 * Corresponds to the optional {@code authorization_details}
291                 * parameter.
292                 *
293                 * @param authorizationDetails The authorisation details,
294                 *                             {@code null} if not specified.
295                 *
296                 * @return This builder.
297                 */
298                public Builder authorizationDetails(final List<AuthorizationDetail> authorizationDetails) {
299                        this.authorizationDetails = authorizationDetails;
300                        return this;
301                }
302
303
304                /**
305                 * Sets the resource server URI. Corresponds to the optional
306                 * {@code resource} parameter.
307                 *
308                 * @param resource The resource URI, {@code null} if not
309                 *                 specified.
310                 *
311                 * @return This builder.
312                 */
313                public Builder resource(final URI resource) {
314                        if (resource != null) {
315                                this.resources = Collections.singletonList(resource);
316                        } else {
317                                this.resources = null;
318                        }
319                        return this;
320                }
321
322
323                /**
324                 * Sets the resource server URI(s). Corresponds to the optional
325                 * {@code resource} parameter.
326                 *
327                 * @param resources The resource URI(s), {@code null} if not
328                 *                  specified.
329                 *
330                 * @return This builder.
331                 */
332                public Builder resources(final URI ... resources) {
333                        if (resources != null) {
334                                this.resources = Arrays.asList(resources);
335                        } else {
336                                this.resources = null;
337                        }
338                        return this;
339                }
340
341
342                /**
343                 * Sets the existing refresh token for incremental
344                 * authorisation of a public client. Corresponds to the
345                 * optional {@code existing_grant} parameter.
346                 *
347                 * @param existingGrant Existing refresh token for incremental
348                 *                      authorisation of a public client,
349                 *                      {@code null} if not specified.
350                 *
351                 * @return This builder.
352                 */
353                public Builder existingGrant(final RefreshToken existingGrant) {
354                        this.existingGrant = existingGrant;
355                        return this;
356                }
357
358
359                /**
360                 * Sets the device secret for native SSO. Corresponds to the
361                 * optional {@code device_secret} parameter.
362                 *
363                 * @param deviceSecret The device secret, {@code null} if not
364                 *                     specified.
365                 *
366                 * @return This builder.
367                 */
368                public Builder deviceSecret(final DeviceSecret deviceSecret) {
369                        this.deviceSecret = deviceSecret;
370                        return this;
371                }
372
373
374                /**
375                 * Sets a custom parameter.
376                 *
377                 * @param name   The parameter name. Must not be {@code null}.
378                 * @param values The parameter values, {@code null} if not
379                 *               specified.
380                 *
381                 * @return This builder.
382                 */
383                public Builder customParameter(final String name, final String ... values) {
384                        if (values == null || values.length == 0) {
385                                customParams.remove(name);
386                        } else {
387                                customParams.put(name, Arrays.asList(values));
388                        }
389                        return this;
390                }
391
392
393                /**
394                 * Builds a new token request.
395                 *
396                 * @return The token request.
397                 */
398                public TokenRequest build() {
399
400                        try {
401                                if (clientAuth != null) {
402                                        return new TokenRequest(
403                                                endpoint,
404                                                clientAuth,
405                                                authzGrant,
406                                                scope,
407                                                authorizationDetails,
408                                                resources,
409                                                deviceSecret,
410                                                customParams);
411                                }
412
413                                return new TokenRequest(
414                                        endpoint,
415                                        clientID,
416                                        authzGrant,
417                                        scope,
418                                        authorizationDetails,
419                                        resources,
420                                        existingGrant,
421                                        deviceSecret,
422                                        customParams);
423                        } catch (IllegalArgumentException e) {
424                                throw new IllegalStateException(e.getMessage(), e);
425                        }
426                }
427        }
428
429
430        /**
431         * Creates a new token request with client authentication.
432         *
433         * @param endpoint   The URI of the token endpoint. May be
434         *                   {@code null} if the {@link #toHTTPRequest} method
435         *                   is not going to be used.
436         * @param clientAuth The client authentication. Must not be
437         *                   {@code null}.
438         * @param authzGrant The authorisation grant. Must not be {@code null}.
439         * @param scope      The requested scope, {@code null} if not
440         *                   specified.
441         */
442        public TokenRequest(final URI endpoint,
443                            final ClientAuthentication clientAuth,
444                            final AuthorizationGrant authzGrant,
445                            final Scope scope) {
446
447                this(endpoint, clientAuth, authzGrant, scope, null, null);
448        }
449
450
451        /**
452         * Creates a new token request with client authentication and extension
453         * and custom parameters.
454         *
455         * @param endpoint     The URI of the token endpoint. May be
456         *                     {@code null} if the {@link #toHTTPRequest}
457         *                     method is not going to be used.
458         * @param clientAuth   The client authentication. Must not be
459         *                     {@code null}.
460         * @param authzGrant   The authorisation grant. Must not be
461         *                     {@code null}.
462         * @param scope        The requested scope, {@code null} if not
463         *                     specified.
464         * @param resources    The resource URI(s), {@code null} if not
465         *                     specified.
466         * @param customParams Custom parameters to be included in the request
467         *                     body, empty map or {@code null} if none.
468         */
469        @Deprecated
470        public TokenRequest(final URI endpoint,
471                            final ClientAuthentication clientAuth,
472                            final AuthorizationGrant authzGrant,
473                            final Scope scope,
474                            final List<URI> resources,
475                            final Map<String,List<String>> customParams) {
476
477                this(endpoint, clientAuth, authzGrant, scope, null, resources, customParams);
478        }
479
480
481        /**
482         * Creates a new token request with client authentication and extension
483         * and custom parameters.
484         *
485         * @param endpoint             The URI of the token endpoint. May be
486         *                             {@code null} if the
487         *                             {@link #toHTTPRequest} method is not
488         *                             going be used.
489         * @param clientAuth           The client authentication. Must not be
490         *                             {@code null}.
491         * @param authzGrant           The authorisation grant. Must not be
492         *                             {@code null}.
493         * @param scope                The requested scope, {@code null} if not
494         *                             specified.
495         * @param authorizationDetails The Rich Authorisation Request (RAR)
496         *                             details, {@code null} if not specified.
497         * @param resources            The resource URI(s), {@code null} if not
498         *                             specified.
499         * @param customParams         Custom parameters to be included in the
500         *                             request body, empty map or {@code null}
501         *                             if none.
502         */
503        @Deprecated
504        public TokenRequest(final URI endpoint,
505                            final ClientAuthentication clientAuth,
506                            final AuthorizationGrant authzGrant,
507                            final Scope scope,
508                            final List<AuthorizationDetail> authorizationDetails,
509                            final List<URI> resources,
510                            final Map<String,List<String>> customParams) {
511
512                this(endpoint, clientAuth, authzGrant, scope, authorizationDetails, resources, null, customParams);
513        }
514
515
516        /**
517         * Creates a new token request with client authentication.
518         *
519         * @param endpoint   The URI of the token endpoint. May be
520         *                   {@code null} if the {@link #toHTTPRequest} method
521         *                   is not going to be used.
522         * @param clientAuth The client authentication. Must not be
523         *                   {@code null}.
524         * @param authzGrant The authorisation grant. Must not be {@code null}.
525         */
526        @Deprecated
527        public TokenRequest(final URI endpoint,
528                            final ClientAuthentication clientAuth,
529                            final AuthorizationGrant authzGrant) {
530
531                this(endpoint, clientAuth, authzGrant, null);
532        }
533
534
535        /**
536         * Creates a new token request with no (explicit) client
537         * authentication. The grant itself may be used to authenticate the
538         * client.
539         *
540         * @param endpoint   The URI of the token endpoint. May be
541         *                   {@code null} if the {@link #toHTTPRequest} method
542         *                   is not going to be used.
543         * @param clientID   The client identifier, {@code null} if not
544         *                   specified.
545         * @param authzGrant The authorisation grant. Must not be {@code null}.
546         * @param scope      The requested scope, {@code null} if not
547         *                   specified.
548         */
549        public TokenRequest(final URI endpoint,
550                            final ClientID clientID,
551                            final AuthorizationGrant authzGrant,
552                            final Scope scope) {
553
554                this(endpoint, clientID, authzGrant, scope, null, null,null);
555        }
556
557
558        /**
559         * Creates a new token request, with no (explicit) client
560         * authentication and extension and custom parameters. The grant itself
561         * may be used to authenticate the client.
562         *
563         * @param endpoint      The URI of the token endpoint. May be
564         *                      {@code null} if the {@link #toHTTPRequest}
565         *                      method is not going to be used.
566         * @param clientID      The client identifier, {@code null} if not
567         *                      specified.
568         * @param authzGrant    The authorisation grant. Must not be
569         *                      {@code null}.
570         * @param scope         The requested scope, {@code null} if not
571         *                      specified.
572         * @param resources     The resource URI(s), {@code null} if not
573         *                      specified.
574         * @param existingGrant Existing refresh token for incremental
575         *                      authorisation of a public client, {@code null}
576         *                      if not specified.
577         * @param customParams  Custom parameters to be included in the request
578         *                      body, empty map or {@code null} if none.
579         */
580        @Deprecated
581        public TokenRequest(final URI endpoint,
582                            final ClientID clientID,
583                            final AuthorizationGrant authzGrant,
584                            final Scope scope,
585                            final List<URI> resources,
586                            final RefreshToken existingGrant,
587                            final Map<String,List<String>> customParams) {
588
589                this(endpoint, clientID, authzGrant, scope, null, resources, existingGrant, customParams);
590        }
591
592
593        /**
594         * Creates a new token request, with no (explicit) client
595         * authentication and extension and custom parameters. The grant itself
596         * may be used to authenticate the client.
597         *
598         * @param endpoint             The URI of the token endpoint. May be
599         *                             {@code null} if the
600         *                             {@link #toHTTPRequest}
601         *                             method is not going to be used.
602         * @param clientID             The client identifier, {@code null} if
603         *                             not specified.
604         * @param authzGrant           The authorisation grant. Must not be
605         *                             {@code null}.
606         * @param scope                The requested scope, {@code null} if not
607         *                             specified.
608         * @param authorizationDetails The Rich Authorisation Request (RAR)
609         *                             details, {@code null} if not specified.
610         * @param resources            The resource URI(s), {@code null} if not
611         *                             specified.
612         * @param existingGrant        Existing refresh token for incremental
613         *                             authorisation of a public client,
614         *                             {@code null} if not specified.
615         * @param customParams         Custom parameters to be included in the
616         *                             request body, empty map or {@code null}
617         *                             if none.
618         */
619        @Deprecated
620        public TokenRequest(final URI endpoint,
621                            final ClientID clientID,
622                            final AuthorizationGrant authzGrant,
623                            final Scope scope,
624                            final List<AuthorizationDetail> authorizationDetails,
625                            final List<URI> resources,
626                            final RefreshToken existingGrant,
627                            final Map<String,List<String>> customParams) {
628
629                this(endpoint, clientID, authzGrant, scope, authorizationDetails, resources, existingGrant, null, customParams);
630        }
631
632
633        /**
634         * Creates a new token request, with no (explicit) client
635         * authentication. The grant itself may be used to authenticate the
636         * client.
637         *
638         * @param endpoint   The URI of the token endpoint. May be
639         *                   {@code null} if the {@link #toHTTPRequest} method
640         *                   is not going to be used.
641         * @param clientID   The client identifier, {@code null} if not
642         *                   specified.
643         * @param authzGrant The authorisation grant. Must not be {@code null}.
644         */
645        @Deprecated
646        public TokenRequest(final URI endpoint,
647                            final ClientID clientID,
648                            final AuthorizationGrant authzGrant) {
649
650                this(endpoint, clientID, authzGrant, null);
651        }
652
653
654        /**
655         * Creates a new token request with no (explicit) client
656         * authentication, the client identifier is inferred from the
657         * authorisation grant.
658         *
659         * @param endpoint   The URI of the token endpoint. May be
660         *                   {@code null} if the {@link #toHTTPRequest} method
661         *                   is not going to be used.
662         * @param authzGrant The authorisation grant. Must not be {@code null}.
663         * @param scope      The requested scope, {@code null} if not
664         *                   specified.
665         */
666        public TokenRequest(final URI endpoint,
667                            final AuthorizationGrant authzGrant,
668                            final Scope scope) {
669
670                this(endpoint, (ClientID)null, authzGrant, scope);
671        }
672
673
674        /**
675         * Creates a new token request with no (explicit) client
676         * authentication, the client identifier is inferred from the
677         * authorisation grant.
678         *
679         * @param endpoint   The URI of the token endpoint. May be
680         *                   {@code null} if the {@link #toHTTPRequest} method
681         *                   is not going to be used.
682         * @param authzGrant The authorisation grant. Must not be {@code null}.
683         */
684        @Deprecated
685        public TokenRequest(final URI endpoint,
686                            final AuthorizationGrant authzGrant) {
687
688                this(endpoint, (ClientID)null, authzGrant, null);
689        }
690
691
692        /**
693         * Creates a new token request with client authentication and extension
694         * and custom parameters.
695         *
696         * @param endpoint             The URI of the token endpoint. May be
697         *                             {@code null} if the
698         *                             {@link #toHTTPRequest} method is not
699         *                             going be used.
700         * @param clientAuth           The client authentication. Must not be
701         *                             {@code null}.
702         * @param authzGrant           The authorisation grant. Must not be
703         *                             {@code null}.
704         * @param scope                The requested scope, {@code null} if not
705         *                             specified.
706         * @param authorizationDetails The Rich Authorisation Request (RAR)
707         *                             details, {@code null} if not specified.
708         * @param resources            The resource URI(s), {@code null} if not
709         *                             specified.
710         * @param deviceSecret         The device secret, {@code null} if not
711         *                             specified.
712         * @param customParams         Custom parameters to be included in the
713         *                             request body, empty map or {@code null}
714         *                             if none.
715         */
716        public TokenRequest(final URI endpoint,
717                            final ClientAuthentication clientAuth,
718                            final AuthorizationGrant authzGrant,
719                            final Scope scope,
720                            final List<AuthorizationDetail> authorizationDetails,
721                            final List<URI> resources,
722                            final DeviceSecret deviceSecret,
723                            final Map<String,List<String>> customParams) {
724
725                super(endpoint, Objects.requireNonNull(clientAuth));
726
727                this.authzGrant = Objects.requireNonNull(authzGrant);
728
729                this.scope = scope;
730
731                if (resources != null) {
732                        for (URI resourceURI: resources) {
733                                if (! ResourceUtils.isLegalResourceURI(resourceURI))
734                                        throw new IllegalArgumentException("Resource URI must be absolute and with no query or fragment: " + resourceURI);
735                        }
736                }
737
738                this.authorizationDetails = authorizationDetails;
739
740                this.resources = resources;
741
742                this.existingGrant = null; // only for public client
743
744                this.deviceSecret = deviceSecret;
745
746                if (MapUtils.isNotEmpty(customParams)) {
747                        this.customParams = customParams;
748                } else {
749                        this.customParams = Collections.emptyMap();
750                }
751        }
752
753
754        /**
755         * Creates a new token request, with no (explicit) client
756         * authentication and extension and custom parameters. The grant itself
757         * may be used to authenticate the client.
758         *
759         * @param endpoint             The URI of the token endpoint. May be
760         *                             {@code null} if the
761         *                             {@link #toHTTPRequest}
762         *                             method is not going to be used.
763         * @param clientID             The client identifier, {@code null} if
764         *                             not specified.
765         * @param authzGrant           The authorisation grant. Must not be
766         *                             {@code null}.
767         * @param scope                The requested scope, {@code null} if not
768         *                             specified.
769         * @param authorizationDetails The Rich Authorisation Request (RAR)
770         *                             details, {@code null} if not specified.
771         * @param resources            The resource URI(s), {@code null} if not
772         *                             specified.
773         * @param existingGrant        Existing refresh token for incremental
774         *                             authorisation of a public client,
775         *                             {@code null} if not specified.
776         * @param deviceSecret         The device secret, {@code null} if not
777         *                             specified.
778         * @param customParams         Custom parameters to be included in the
779         *                             request body, empty map or {@code null}
780         *                             if none.
781         */
782        public TokenRequest(final URI endpoint,
783                            final ClientID clientID,
784                            final AuthorizationGrant authzGrant,
785                            final Scope scope,
786                            final List<AuthorizationDetail> authorizationDetails,
787                            final List<URI> resources,
788                            final RefreshToken existingGrant,
789                            final DeviceSecret deviceSecret,
790                            final Map<String,List<String>> customParams) {
791
792                super(endpoint, clientID);
793
794                if (authzGrant.getType().requiresClientAuthentication()) {
795                        throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires client authentication");
796                }
797
798                if (authzGrant.getType().requiresClientID() && clientID == null) {
799                        throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires a \"client_id\" parameter");
800                }
801
802                this.authzGrant = authzGrant;
803
804                this.scope = scope;
805
806                if (resources != null) {
807                        for (URI resourceURI: resources) {
808                                if (! ResourceUtils.isLegalResourceURI(resourceURI))
809                                        throw new IllegalArgumentException("Resource URI must be absolute and with no query or fragment: " + resourceURI);
810                        }
811                }
812
813                this.authorizationDetails = authorizationDetails;
814
815                this.resources = resources;
816
817                this.existingGrant = existingGrant;
818
819                this.deviceSecret = deviceSecret;
820
821                if (MapUtils.isNotEmpty(customParams)) {
822                        this.customParams = customParams;
823                } else {
824                        this.customParams = Collections.emptyMap();
825                }
826        }
827
828
829        /**
830         * Returns the authorisation grant.
831         *
832         * @return The authorisation grant.
833         */
834        public AuthorizationGrant getAuthorizationGrant() {
835
836                return authzGrant;
837        }
838
839
840        /**
841         * Returns the requested scope. Corresponds to the {@code scope}
842         * parameter.
843         *
844         * @return The requested scope, {@code null} if not specified.
845         */
846        public Scope getScope() {
847
848                return scope;
849        }
850
851
852        /**
853         * Returns the Rich Authorisation Request (RAR) details. Corresponds to
854         * the {@code authorization_details} parameter.
855         *
856         * @return The authorisation details, {@code null} if not specified.
857         */
858        public List<AuthorizationDetail> getAuthorizationDetails() {
859
860                return authorizationDetails;
861        }
862        
863        
864        /**
865         * Returns the resource server URI. Corresponds to the {@code resource}
866         * parameter.
867         *
868         * @return The resource URI(s), {@code null} if not specified.
869         */
870        public List<URI> getResources() {
871                
872                return resources;
873        }
874        
875        
876        /**
877         * Returns the existing refresh token for incremental authorisation of
878         * a public client. Corresponds to the {@code existing_grant}
879         * parameter.
880         *
881         * @return The existing grant, {@code null} if not specified.
882         */
883        public RefreshToken getExistingGrant() {
884                
885                return existingGrant;
886        }
887
888
889        /**
890         * Returns the device secret for native SSO. Corresponds to the
891         * {@code device_secret} parameter.
892         *
893         * @return The device secret, {@code null} if not specified.
894         */
895        public DeviceSecret getDeviceSecret() {
896
897                return deviceSecret;
898        }
899
900
901        /**
902         * Returns the additional custom parameters included in the request
903         * body.
904         *
905         * @return The additional custom parameters as an unmodifiable map,
906         *         empty map if none.
907         */
908        public Map<String,List<String>> getCustomParameters () {
909
910                return Collections.unmodifiableMap(customParams);
911        }
912
913
914        /**
915         * Returns the specified custom parameter included in the request body.
916         *
917         * @param name The parameter name. Must not be {@code null}.
918         *
919         * @return The parameter value(s), {@code null} if not specified.
920         */
921        public List<String> getCustomParameter(final String name) {
922
923                return customParams.get(name);
924        }
925
926
927        @Override
928        public HTTPRequest toHTTPRequest() {
929
930                if (getEndpointURI() == null)
931                        throw new SerializeException("The endpoint URI is not specified");
932
933                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getEndpointURI());
934                httpRequest.setEntityContentType(ContentType.APPLICATION_URLENCODED);
935
936                if (getClientAuthentication() != null) {
937                        getClientAuthentication().applyTo(httpRequest);
938                }
939
940                Map<String, List<String>> params;
941                try {
942                        params = new LinkedHashMap<>(httpRequest.getBodyAsFormParameters());
943                } catch (ParseException e) {
944                        throw new SerializeException(e.getMessage(), e);
945                }
946                params.putAll(getAuthorizationGrant().toParameters());
947
948                switch (getAuthorizationGrant().getType().getScopeRequirementInTokenRequest()) {
949                        case REQUIRED:
950                                if (CollectionUtils.isEmpty(getScope())) {
951                                        throw new SerializeException("Scope is required for the " + getAuthorizationGrant().getType() + " grant");
952                                }
953                                params.put("scope", Collections.singletonList(getScope().toString()));
954                                break;
955                        case OPTIONAL:
956                                if (CollectionUtils.isNotEmpty(getScope())) {
957                                        params.put("scope", Collections.singletonList(getScope().toString()));
958                                }
959                                break;
960                        case NOT_ALLOWED:
961                        default:
962                                break;
963                }
964
965                if (getClientID() != null) {
966                        params.put("client_id", Collections.singletonList(getClientID().getValue()));
967                }
968
969                if (getAuthorizationDetails() != null) {
970                        params.put("authorization_details", Collections.singletonList(AuthorizationDetail.toJSONString(getAuthorizationDetails())));
971                }
972                
973                if (getResources() != null) {
974                        List<String> values = new LinkedList<>();
975                        for (URI uri: getResources()) {
976                                if (uri == null)
977                                        continue;
978                                values.add(uri.toString());
979                        }
980                        params.put("resource", values);
981                }
982                
983                if (getExistingGrant() != null) {
984                        params.put("existing_grant", Collections.singletonList(getExistingGrant().getValue()));
985                }
986
987                if (getDeviceSecret() != null) {
988                        params.put("device_secret", Collections.singletonList(getDeviceSecret().getValue()));
989                }
990
991                if (! getCustomParameters().isEmpty()) {
992                        params.putAll(getCustomParameters());
993                }
994
995                httpRequest.setBody(URLUtils.serializeParameters(params));
996
997                return httpRequest;
998        }
999
1000
1001        /**
1002         * Parses a token request from the specified HTTP request.
1003         *
1004         * @param httpRequest The HTTP request. Must not be {@code null}.
1005         *
1006         * @return The token request.
1007         *
1008         * @throws ParseException If the HTTP request couldn't be parsed to a
1009         *                        token request.
1010         */
1011        public static TokenRequest parse(final HTTPRequest httpRequest)
1012                throws ParseException {
1013
1014                // Only HTTP POST accepted
1015                URI endpoint = httpRequest.getURI();
1016                httpRequest.ensureMethod(HTTPRequest.Method.POST);
1017                httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED);
1018
1019                // Parse client authentication, if any
1020                ClientAuthentication clientAuth;
1021                try {
1022                        clientAuth = ClientAuthentication.parse(httpRequest);
1023                } catch (ParseException e) {
1024                        throw new ParseException(e.getMessage(), OAuth2Error.INVALID_REQUEST.appendDescription(": " + e.getMessage()));
1025                }
1026
1027                // No fragment! May use query component!
1028                Map<String,List<String>> params = httpRequest.getBodyAsFormParameters();
1029                
1030                Set<String> repeatParams = MultivaluedMapUtils.getKeysWithMoreThanOneValue(params, ALLOWED_REPEATED_PARAMS);
1031                if (! repeatParams.isEmpty()) {
1032                        String msg = "Parameter(s) present more than once: " + repeatParams;
1033                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.setDescription(msg));
1034                }
1035                
1036                // Multiple conflicting client auth methods (issue #203)?
1037                if (clientAuth instanceof ClientSecretBasic) {
1038                        if (StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion")) || StringUtils.isNotBlank(MultivaluedMapUtils.getFirstValue(params, "client_assertion_type"))) {
1039                                String msg = "Multiple conflicting client authentication methods found: Basic and JWT assertion";
1040                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
1041                        }
1042                }
1043
1044                // Parse grant
1045                AuthorizationGrant grant = AuthorizationGrant.parse(params);
1046
1047                if (clientAuth == null && grant.getType().requiresClientAuthentication()) {
1048                        String msg = "Missing client authentication";
1049                        throw new ParseException(msg, OAuth2Error.INVALID_CLIENT.appendDescription(": " + msg));
1050                }
1051
1052                // Parse client id
1053                ClientID clientID = null;
1054
1055                if (clientAuth == null) {
1056
1057                        // Parse optional client ID
1058                        String clientIDString = MultivaluedMapUtils.getFirstValue(params, "client_id");
1059
1060                        if (StringUtils.isNotBlank(clientIDString))
1061                                clientID = new ClientID(clientIDString);
1062
1063                        if (clientID == null && grant.getType().requiresClientID()) {
1064                                String msg = "Missing required client_id parameter";
1065                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
1066                        }
1067                }
1068
1069                // Parse optional scope
1070                String scopeValue = MultivaluedMapUtils.getFirstValue(params, "scope");
1071
1072                ParameterRequirement scopeRequirement = grant.getType().getScopeRequirementInTokenRequest();
1073
1074                Scope scope = null;
1075
1076                if (scopeValue != null && (ParameterRequirement.REQUIRED.equals(scopeRequirement) || ParameterRequirement.OPTIONAL.equals(scopeRequirement))) {
1077                        scope = Scope.parse(scopeValue);
1078                }
1079
1080                // Parse optional RAR
1081                String json = MultivaluedMapUtils.getFirstValue(params, "authorization_details");
1082
1083                List<AuthorizationDetail> authorizationDetails = null;
1084
1085                if (json != null) {
1086                        authorizationDetails = AuthorizationDetail.parseList(json);
1087                }
1088                
1089                // Parse optional resource URIs
1090                List<URI> resources = null;
1091                
1092                List<String> vList = params.get("resource");
1093                
1094                if (vList != null) {
1095                        
1096                        resources = new LinkedList<>();
1097                        
1098                        for (String uriValue: vList) {
1099                                
1100                                if (uriValue == null)
1101                                        continue;
1102                                
1103                                String errMsg = "Illegal resource parameter: Must be an absolute URI without a fragment: " + uriValue;
1104                                
1105                                URI resourceURI;
1106                                try {
1107                                        resourceURI = new URI(uriValue);
1108                                } catch (URISyntaxException e) {
1109                                        throw new ParseException(errMsg, OAuth2Error.INVALID_TARGET.setDescription(errMsg));
1110                                }
1111                                
1112                                if (! ResourceUtils.isLegalResourceURI(resourceURI)) {
1113                                        throw new ParseException(errMsg, OAuth2Error.INVALID_TARGET.setDescription(errMsg));
1114                                }
1115                                
1116                                resources.add(resourceURI);
1117                        }
1118                }
1119                
1120                String rt = MultivaluedMapUtils.getFirstValue(params, "existing_grant");
1121                RefreshToken existingGrant = StringUtils.isNotBlank(rt) ? new RefreshToken(rt) : null;
1122
1123                DeviceSecret deviceSecret = DeviceSecret.parse(MultivaluedMapUtils.getFirstValue(params, "device_secret"));
1124
1125                // Parse custom parameters
1126                Map<String,List<String>> customParams = new HashMap<>();
1127
1128                for (Map.Entry<String,List<String>> p: params.entrySet()) {
1129
1130                        if (REGISTERED_PARAMETER_NAMES.contains(p.getKey().toLowerCase())) {
1131                                continue; // skip
1132                        }
1133
1134                        if (! grant.getType().getRequestParameterNames().contains(p.getKey())) {
1135                                // We have a custom (non-registered) parameter
1136                                customParams.put(p.getKey(), p.getValue());
1137                        }
1138                }
1139
1140                if (clientAuth != null) {
1141                        return new TokenRequest(endpoint, clientAuth, grant, scope, authorizationDetails, resources, deviceSecret, customParams);
1142                } else {
1143                        // public client
1144                        return new TokenRequest(endpoint, clientID, grant, scope, authorizationDetails, resources, existingGrant, deviceSecret, customParams);
1145                }
1146        }
1147}