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 candidates, {@code null} if none.
156                 */
157                private final List<ClientAuthentication> clientAuthCandidates;
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.clientAuthCandidates = Collections.singletonList(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 client
233                 * authentication candidates.
234                 *
235                 * @param endpoint             The URI of the token endpoint.
236                 *                             May be {@code null} if the
237                 *                             {@link #toHTTPRequest} method is
238                 *                             not going to be used.
239                 * @param clientAuthCandidates The client authentication
240                 *                             candidates. Must not be
241                 *                             {@code null}.
242                 * @param authzGrant           The authorisation grant. Must
243                 *                             not be {@code null}.
244                 */
245                public Builder(final URI endpoint,
246                               final List<ClientAuthentication> clientAuthCandidates,
247                               final AuthorizationGrant authzGrant) {
248                        this.endpoint = endpoint;
249                        if (CollectionUtils.isEmpty(clientAuthCandidates)) {
250                                throw new IllegalArgumentException("At least one client authentication candidate must be specified");
251                        }
252                        this.clientAuthCandidates = Collections.unmodifiableList(clientAuthCandidates);
253                        clientID = null;
254                        this.authzGrant = Objects.requireNonNull(authzGrant);
255                }
256
257
258                /**
259                 * Creates a new builder for a token request with no (explicit)
260                 * client authentication. The grant itself may be used to
261                 * authenticate the client.
262                 *
263                 * @param endpoint   The URI of the token endpoint. May be
264                 *                   {@code null} if the {@link #toHTTPRequest}
265                 *                   method is not going to be used.
266                 * @param clientID   The client identifier. Must not be
267                 *                   {@code null}.
268                 * @param authzGrant The authorisation grant. Must not be
269                 *                   {@code null}.
270                 */
271                public Builder(final URI endpoint,
272                               final ClientID clientID,
273                               final AuthorizationGrant authzGrant) {
274                        this.endpoint = endpoint;
275                        clientAuthCandidates = null;
276                        this.clientID = Objects.requireNonNull(clientID);
277                        this.authzGrant = Objects.requireNonNull(authzGrant);
278                }
279
280
281                /**
282                 * Creates a new builder for a token request with no (explicit)
283                 * client authentication, the client identifier is inferred
284                 * from the authorisation grant.
285                 *
286                 * @param endpoint   The URI of the token endpoint. May be
287                 *                   {@code null} if the {@link #toHTTPRequest}
288                 *                   method is not going to be used.
289                 * @param authzGrant The authorisation grant. Must not be
290                 *                   {@code null}.
291                 */
292                public Builder(final URI endpoint,
293                               final AuthorizationGrant authzGrant) {
294                        this.endpoint = endpoint;
295                        clientAuthCandidates = null;
296                        clientID = null;
297                        this.authzGrant = Objects.requireNonNull(authzGrant);
298                }
299
300
301                /**
302                 * Sets the scope. Corresponds to the optional {@code scope}
303                 * parameter.
304                 *
305                 * @param scope The scope, {@code null} if not specified.
306                 *
307                 * @return This builder.
308                 */
309                public Builder scope(final Scope scope) {
310                        this.scope = scope;
311                        return this;
312                }
313
314
315                /**
316                 * Sets the Rich Authorisation Request (RAR) details.
317                 * Corresponds to the optional {@code authorization_details}
318                 * parameter.
319                 *
320                 * @param authorizationDetails The authorisation details,
321                 *                             {@code null} if not specified.
322                 *
323                 * @return This builder.
324                 */
325                public Builder authorizationDetails(final List<AuthorizationDetail> authorizationDetails) {
326                        this.authorizationDetails = authorizationDetails;
327                        return this;
328                }
329
330
331                /**
332                 * Sets the resource server URI. Corresponds to the optional
333                 * {@code resource} parameter.
334                 *
335                 * @param resource The resource URI, {@code null} if not
336                 *                 specified.
337                 *
338                 * @return This builder.
339                 */
340                public Builder resource(final URI resource) {
341                        if (resource != null) {
342                                this.resources = Collections.singletonList(resource);
343                        } else {
344                                this.resources = null;
345                        }
346                        return this;
347                }
348
349
350                /**
351                 * Sets the resource server URI(s). Corresponds to the optional
352                 * {@code resource} parameter.
353                 *
354                 * @param resources The resource URI(s), {@code null} if not
355                 *                  specified.
356                 *
357                 * @return This builder.
358                 */
359                public Builder resources(final URI ... resources) {
360                        if (resources != null) {
361                                this.resources = Arrays.asList(resources);
362                        } else {
363                                this.resources = null;
364                        }
365                        return this;
366                }
367
368
369                /**
370                 * Sets the existing refresh token for incremental
371                 * authorisation of a public client. Corresponds to the
372                 * optional {@code existing_grant} parameter.
373                 *
374                 * @param existingGrant Existing refresh token for incremental
375                 *                      authorisation of a public client,
376                 *                      {@code null} if not specified.
377                 *
378                 * @return This builder.
379                 */
380                public Builder existingGrant(final RefreshToken existingGrant) {
381                        this.existingGrant = existingGrant;
382                        return this;
383                }
384
385
386                /**
387                 * Sets the device secret for native SSO. Corresponds to the
388                 * optional {@code device_secret} parameter.
389                 *
390                 * @param deviceSecret The device secret, {@code null} if not
391                 *                     specified.
392                 *
393                 * @return This builder.
394                 */
395                public Builder deviceSecret(final DeviceSecret deviceSecret) {
396                        this.deviceSecret = deviceSecret;
397                        return this;
398                }
399
400
401                /**
402                 * Sets a custom parameter.
403                 *
404                 * @param name   The parameter name. Must not be {@code null}.
405                 * @param values The parameter values, {@code null} if not
406                 *               specified.
407                 *
408                 * @return This builder.
409                 */
410                public Builder customParameter(final String name, final String ... values) {
411                        if (values == null || values.length == 0) {
412                                customParams.remove(name);
413                        } else {
414                                customParams.put(name, Arrays.asList(values));
415                        }
416                        return this;
417                }
418
419
420                /**
421                 * Builds a new token request.
422                 *
423                 * @return The token request.
424                 */
425                public TokenRequest build() {
426
427                        try {
428                                if (clientAuthCandidates != null) {
429                                        return new TokenRequest(
430                                                endpoint,
431                                                clientAuthCandidates,
432                                                authzGrant,
433                                                scope,
434                                                authorizationDetails,
435                                                resources,
436                                                deviceSecret,
437                                                customParams);
438                                }
439
440                                return new TokenRequest(
441                                        endpoint,
442                                        clientID,
443                                        authzGrant,
444                                        scope,
445                                        authorizationDetails,
446                                        resources,
447                                        existingGrant,
448                                        deviceSecret,
449                                        customParams);
450                        } catch (IllegalArgumentException e) {
451                                throw new IllegalStateException(e.getMessage(), e);
452                        }
453                }
454        }
455
456
457        /**
458         * Creates a new token request with client authentication.
459         *
460         * @param endpoint   The URI of the token endpoint. May be
461         *                   {@code null} if the {@link #toHTTPRequest} method
462         *                   is not going to be used.
463         * @param clientAuth The client authentication. Must not be
464         *                   {@code null}.
465         * @param authzGrant The authorisation grant. Must not be {@code null}.
466         * @param scope      The requested scope, {@code null} if not
467         *                   specified.
468         */
469        public TokenRequest(final URI endpoint,
470                            final ClientAuthentication clientAuth,
471                            final AuthorizationGrant authzGrant,
472                            final Scope scope) {
473
474                this(endpoint, clientAuth, authzGrant, scope, null, null);
475        }
476
477
478        /**
479         * Creates a new token request with client authentication and extension
480         * and custom parameters.
481         *
482         * @param endpoint     The URI of the token endpoint. May be
483         *                     {@code null} if the {@link #toHTTPRequest}
484         *                     method is not going to be used.
485         * @param clientAuth   The client authentication. Must not be
486         *                     {@code null}.
487         * @param authzGrant   The authorisation grant. Must not be
488         *                     {@code null}.
489         * @param scope        The requested scope, {@code null} if not
490         *                     specified.
491         * @param resources    The resource URI(s), {@code null} if not
492         *                     specified.
493         * @param customParams Custom parameters to be included in the request
494         *                     body, empty map or {@code null} if none.
495         */
496        @Deprecated
497        public TokenRequest(final URI endpoint,
498                            final ClientAuthentication clientAuth,
499                            final AuthorizationGrant authzGrant,
500                            final Scope scope,
501                            final List<URI> resources,
502                            final Map<String,List<String>> customParams) {
503
504                this(endpoint, clientAuth, authzGrant, scope, null, resources, customParams);
505        }
506
507
508        /**
509         * Creates a new token request with client authentication and extension
510         * and custom parameters.
511         *
512         * @param endpoint             The URI of the token endpoint. May be
513         *                             {@code null} if the
514         *                             {@link #toHTTPRequest} method is not
515         *                             going be used.
516         * @param clientAuth           The client authentication. Must not be
517         *                             {@code null}.
518         * @param authzGrant           The authorisation grant. Must not be
519         *                             {@code null}.
520         * @param scope                The requested scope, {@code null} if not
521         *                             specified.
522         * @param authorizationDetails The Rich Authorisation Request (RAR)
523         *                             details, {@code null} if not specified.
524         * @param resources            The resource URI(s), {@code null} if not
525         *                             specified.
526         * @param customParams         Custom parameters to be included in the
527         *                             request body, empty map or {@code null}
528         *                             if none.
529         */
530        @Deprecated
531        public TokenRequest(final URI endpoint,
532                            final ClientAuthentication clientAuth,
533                            final AuthorizationGrant authzGrant,
534                            final Scope scope,
535                            final List<AuthorizationDetail> authorizationDetails,
536                            final List<URI> resources,
537                            final Map<String,List<String>> customParams) {
538
539                this(endpoint, clientAuth, authzGrant, scope, authorizationDetails, resources, null, customParams);
540        }
541
542
543        /**
544         * Creates a new token request with client authentication.
545         *
546         * @param endpoint   The URI of the token endpoint. May be
547         *                   {@code null} if the {@link #toHTTPRequest} method
548         *                   is not going to be used.
549         * @param clientAuth The client authentication. Must not be
550         *                   {@code null}.
551         * @param authzGrant The authorisation grant. Must not be {@code null}.
552         */
553        @Deprecated
554        public TokenRequest(final URI endpoint,
555                            final ClientAuthentication clientAuth,
556                            final AuthorizationGrant authzGrant) {
557
558                this(endpoint, clientAuth, authzGrant, null);
559        }
560
561
562        /**
563         * Creates a new token request with no (explicit) client
564         * authentication. The grant itself may be used to authenticate the
565         * client.
566         *
567         * @param endpoint   The URI of the token endpoint. May be
568         *                   {@code null} if the {@link #toHTTPRequest} method
569         *                   is not going to be used.
570         * @param clientID   The client identifier, {@code null} if not
571         *                   specified.
572         * @param authzGrant The authorisation grant. Must not be {@code null}.
573         * @param scope      The requested scope, {@code null} if not
574         *                   specified.
575         */
576        public TokenRequest(final URI endpoint,
577                            final ClientID clientID,
578                            final AuthorizationGrant authzGrant,
579                            final Scope scope) {
580
581                this(endpoint, clientID, authzGrant, scope, null, null,null);
582        }
583
584
585        /**
586         * Creates a new token request, with no (explicit) client
587         * authentication and extension and custom parameters. The grant itself
588         * may be used to authenticate the client.
589         *
590         * @param endpoint      The URI of the token endpoint. May be
591         *                      {@code null} if the {@link #toHTTPRequest}
592         *                      method is not going to be used.
593         * @param clientID      The client identifier, {@code null} if not
594         *                      specified.
595         * @param authzGrant    The authorisation grant. Must not be
596         *                      {@code null}.
597         * @param scope         The requested scope, {@code null} if not
598         *                      specified.
599         * @param resources     The resource URI(s), {@code null} if not
600         *                      specified.
601         * @param existingGrant Existing refresh token for incremental
602         *                      authorisation of a public client, {@code null}
603         *                      if not specified.
604         * @param customParams  Custom parameters to be included in the request
605         *                      body, empty map or {@code null} if none.
606         */
607        @Deprecated
608        public TokenRequest(final URI endpoint,
609                            final ClientID clientID,
610                            final AuthorizationGrant authzGrant,
611                            final Scope scope,
612                            final List<URI> resources,
613                            final RefreshToken existingGrant,
614                            final Map<String,List<String>> customParams) {
615
616                this(endpoint, clientID, authzGrant, scope, null, resources, existingGrant, customParams);
617        }
618
619
620        /**
621         * Creates a new token request, with no (explicit) client
622         * authentication and extension and custom parameters. The grant itself
623         * may be used to authenticate the client.
624         *
625         * @param endpoint             The URI of the token endpoint. May be
626         *                             {@code null} if the
627         *                             {@link #toHTTPRequest}
628         *                             method is not going to be used.
629         * @param clientID             The client identifier, {@code null} if
630         *                             not specified.
631         * @param authzGrant           The authorisation grant. Must not be
632         *                             {@code null}.
633         * @param scope                The requested scope, {@code null} if not
634         *                             specified.
635         * @param authorizationDetails The Rich Authorisation Request (RAR)
636         *                             details, {@code null} if not specified.
637         * @param resources            The resource URI(s), {@code null} if not
638         *                             specified.
639         * @param existingGrant        Existing refresh token for incremental
640         *                             authorisation of a public client,
641         *                             {@code null} if not specified.
642         * @param customParams         Custom parameters to be included in the
643         *                             request body, empty map or {@code null}
644         *                             if none.
645         */
646        @Deprecated
647        public TokenRequest(final URI endpoint,
648                            final ClientID clientID,
649                            final AuthorizationGrant authzGrant,
650                            final Scope scope,
651                            final List<AuthorizationDetail> authorizationDetails,
652                            final List<URI> resources,
653                            final RefreshToken existingGrant,
654                            final Map<String,List<String>> customParams) {
655
656                this(endpoint, clientID, authzGrant, scope, authorizationDetails, resources, existingGrant, null, customParams);
657        }
658
659
660        /**
661         * Creates a new token request, with no (explicit) client
662         * authentication. The grant itself may be used to authenticate the
663         * client.
664         *
665         * @param endpoint   The URI of the token endpoint. May be
666         *                   {@code null} if the {@link #toHTTPRequest} method
667         *                   is not going to be used.
668         * @param clientID   The client identifier, {@code null} if not
669         *                   specified.
670         * @param authzGrant The authorisation grant. Must not be {@code null}.
671         */
672        @Deprecated
673        public TokenRequest(final URI endpoint,
674                            final ClientID clientID,
675                            final AuthorizationGrant authzGrant) {
676
677                this(endpoint, clientID, authzGrant, null);
678        }
679
680
681        /**
682         * Creates a new token request with no (explicit) client
683         * authentication, the client identifier is inferred from the
684         * authorisation grant.
685         *
686         * @param endpoint   The URI of the token endpoint. May be
687         *                   {@code null} if the {@link #toHTTPRequest} method
688         *                   is not going to be used.
689         * @param authzGrant The authorisation grant. Must not be {@code null}.
690         * @param scope      The requested scope, {@code null} if not
691         *                   specified.
692         */
693        public TokenRequest(final URI endpoint,
694                            final AuthorizationGrant authzGrant,
695                            final Scope scope) {
696
697                this(endpoint, (ClientID)null, authzGrant, scope);
698        }
699
700
701        /**
702         * Creates a new token request with no (explicit) client
703         * authentication, the client identifier is inferred from the
704         * authorisation grant.
705         *
706         * @param endpoint   The URI of the token endpoint. May be
707         *                   {@code null} if the {@link #toHTTPRequest} method
708         *                   is not going to be used.
709         * @param authzGrant The authorisation grant. Must not be {@code null}.
710         */
711        @Deprecated
712        public TokenRequest(final URI endpoint,
713                            final AuthorizationGrant authzGrant) {
714
715                this(endpoint, (ClientID)null, authzGrant, null);
716        }
717
718
719        /**
720         * Creates a new token request with client authentication and extension
721         * and custom parameters.
722         *
723         * @param endpoint             The URI of the token endpoint. May be
724         *                             {@code null} if the
725         *                             {@link #toHTTPRequest} method is not
726         *                             going be used.
727         * @param clientAuth           The client authentication. Must not be
728         *                             {@code null}.
729         * @param authzGrant           The authorisation grant. Must not be
730         *                             {@code null}.
731         * @param scope                The requested scope, {@code null} if not
732         *                             specified.
733         * @param authorizationDetails The Rich Authorisation Request (RAR)
734         *                             details, {@code null} if not specified.
735         * @param resources            The resource URI(s), {@code null} if not
736         *                             specified.
737         * @param deviceSecret         The device secret, {@code null} if not
738         *                             specified.
739         * @param customParams         Custom parameters to be included in the
740         *                             request body, empty map or {@code null}
741         *                             if none.
742         */
743        public TokenRequest(final URI endpoint,
744                            final ClientAuthentication clientAuth,
745                            final AuthorizationGrant authzGrant,
746                            final Scope scope,
747                            final List<AuthorizationDetail> authorizationDetails,
748                            final List<URI> resources,
749                            final DeviceSecret deviceSecret,
750                            final Map<String,List<String>> customParams) {
751
752                this(endpoint, Collections.singletonList(clientAuth), authzGrant, scope, authorizationDetails, resources, deviceSecret, customParams);
753        }
754
755
756        /**
757         * Creates a new token request with client authentication candidates
758         * and extension and custom parameters.
759         *
760         * @param endpoint             The URI of the token endpoint. May be
761         *                             {@code null} if the
762         *                             {@link #toHTTPRequest} method is not
763         *                             going be used.
764         * @param clientAuthCandidates The client authentication candidates.
765         *                             Must not be {@code null}.
766         * @param authzGrant           The authorisation grant. Must not be
767         *                             {@code null}.
768         * @param scope                The requested scope, {@code null} if not
769         *                             specified.
770         * @param authorizationDetails The Rich Authorisation Request (RAR)
771         *                             details, {@code null} if not specified.
772         * @param resources            The resource URI(s), {@code null} if not
773         *                             specified.
774         * @param deviceSecret         The device secret, {@code null} if not
775         *                             specified.
776         * @param customParams         Custom parameters to be included in the
777         *                             request body, empty map or {@code null}
778         *                             if none.
779         */
780        public TokenRequest(final URI endpoint,
781                            final List<ClientAuthentication> clientAuthCandidates,
782                            final AuthorizationGrant authzGrant,
783                            final Scope scope,
784                            final List<AuthorizationDetail> authorizationDetails,
785                            final List<URI> resources,
786                            final DeviceSecret deviceSecret,
787                            final Map<String,List<String>> customParams) {
788
789                super(endpoint, Objects.requireNonNull(clientAuthCandidates));
790                for (ClientAuthentication ca: clientAuthCandidates) {
791                        Objects.requireNonNull(ca);
792                }
793
794                this.authzGrant = Objects.requireNonNull(authzGrant);
795
796                this.scope = scope;
797
798                if (resources != null) {
799                        for (URI resourceURI: resources) {
800                                if (! ResourceUtils.isLegalResourceURI(resourceURI))
801                                        throw new IllegalArgumentException("Resource URI must be absolute and with no query or fragment: " + resourceURI);
802                        }
803                }
804
805                this.authorizationDetails = authorizationDetails;
806
807                this.resources = resources;
808
809                this.existingGrant = null; // only for public client
810
811                this.deviceSecret = deviceSecret;
812
813                if (MapUtils.isNotEmpty(customParams)) {
814                        this.customParams = customParams;
815                } else {
816                        this.customParams = Collections.emptyMap();
817                }
818        }
819
820
821        /**
822         * Creates a new token request, with no (explicit) client
823         * authentication and extension and custom parameters. The grant itself
824         * may be used to authenticate the client.
825         *
826         * @param endpoint             The URI of the token endpoint. May be
827         *                             {@code null} if the
828         *                             {@link #toHTTPRequest}
829         *                             method is not going to be used.
830         * @param clientID             The client identifier, {@code null} if
831         *                             not specified.
832         * @param authzGrant           The authorisation grant. Must not be
833         *                             {@code null}.
834         * @param scope                The requested scope, {@code null} if not
835         *                             specified.
836         * @param authorizationDetails The Rich Authorisation Request (RAR)
837         *                             details, {@code null} if not specified.
838         * @param resources            The resource URI(s), {@code null} if not
839         *                             specified.
840         * @param existingGrant        Existing refresh token for incremental
841         *                             authorisation of a public client,
842         *                             {@code null} if not specified.
843         * @param deviceSecret         The device secret, {@code null} if not
844         *                             specified.
845         * @param customParams         Custom parameters to be included in the
846         *                             request body, empty map or {@code null}
847         *                             if none.
848         */
849        public TokenRequest(final URI endpoint,
850                            final ClientID clientID,
851                            final AuthorizationGrant authzGrant,
852                            final Scope scope,
853                            final List<AuthorizationDetail> authorizationDetails,
854                            final List<URI> resources,
855                            final RefreshToken existingGrant,
856                            final DeviceSecret deviceSecret,
857                            final Map<String,List<String>> customParams) {
858
859                super(endpoint, clientID);
860
861                if (authzGrant.getType().requiresClientAuthentication()) {
862                        throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires client authentication");
863                }
864
865                if (authzGrant.getType().requiresClientID() && clientID == null) {
866                        throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires a \"client_id\" parameter");
867                }
868
869                this.authzGrant = authzGrant;
870
871                this.scope = scope;
872
873                if (resources != null) {
874                        for (URI resourceURI: resources) {
875                                if (! ResourceUtils.isLegalResourceURI(resourceURI))
876                                        throw new IllegalArgumentException("Resource URI must be absolute and with no query or fragment: " + resourceURI);
877                        }
878                }
879
880                this.authorizationDetails = authorizationDetails;
881
882                this.resources = resources;
883
884                this.existingGrant = existingGrant;
885
886                this.deviceSecret = deviceSecret;
887
888                if (MapUtils.isNotEmpty(customParams)) {
889                        this.customParams = customParams;
890                } else {
891                        this.customParams = Collections.emptyMap();
892                }
893        }
894
895
896        /**
897         * Returns the authorisation grant.
898         *
899         * @return The authorisation grant.
900         */
901        public AuthorizationGrant getAuthorizationGrant() {
902
903                return authzGrant;
904        }
905
906
907        /**
908         * Returns the requested scope. Corresponds to the {@code scope}
909         * parameter.
910         *
911         * @return The requested scope, {@code null} if not specified.
912         */
913        public Scope getScope() {
914
915                return scope;
916        }
917
918
919        /**
920         * Returns the Rich Authorisation Request (RAR) details. Corresponds to
921         * the {@code authorization_details} parameter.
922         *
923         * @return The authorisation details, {@code null} if not specified.
924         */
925        public List<AuthorizationDetail> getAuthorizationDetails() {
926
927                return authorizationDetails;
928        }
929        
930        
931        /**
932         * Returns the resource server URI. Corresponds to the {@code resource}
933         * parameter.
934         *
935         * @return The resource URI(s), {@code null} if not specified.
936         */
937        public List<URI> getResources() {
938                
939                return resources;
940        }
941        
942        
943        /**
944         * Returns the existing refresh token for incremental authorisation of
945         * a public client. Corresponds to the {@code existing_grant}
946         * parameter.
947         *
948         * @return The existing grant, {@code null} if not specified.
949         */
950        public RefreshToken getExistingGrant() {
951                
952                return existingGrant;
953        }
954
955
956        /**
957         * Returns the device secret for native SSO. Corresponds to the
958         * {@code device_secret} parameter.
959         *
960         * @return The device secret, {@code null} if not specified.
961         */
962        public DeviceSecret getDeviceSecret() {
963
964                return deviceSecret;
965        }
966
967
968        /**
969         * Returns the additional custom parameters included in the request
970         * body.
971         *
972         * @return The additional custom parameters as an unmodifiable map,
973         *         empty map if none.
974         */
975        public Map<String,List<String>> getCustomParameters () {
976
977                return Collections.unmodifiableMap(customParams);
978        }
979
980
981        /**
982         * Returns the specified custom parameter included in the request body.
983         *
984         * @param name The parameter name. Must not be {@code null}.
985         *
986         * @return The parameter value(s), {@code null} if not specified.
987         */
988        public List<String> getCustomParameter(final String name) {
989
990                return customParams.get(name);
991        }
992
993
994        @Override
995        public HTTPRequest toHTTPRequest() {
996
997                if (getEndpointURI() == null)
998                        throw new SerializeException("The endpoint URI is not specified");
999
1000                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getEndpointURI());
1001                httpRequest.setEntityContentType(ContentType.APPLICATION_URLENCODED);
1002
1003                if (getClientAuthentication() != null) {
1004                        for (ClientAuthentication ca: getClientAuthenticationCandidates()) {
1005                                ca.applyTo(httpRequest);
1006                        }
1007                }
1008
1009                Map<String, List<String>> params;
1010                try {
1011                        params = new LinkedHashMap<>(httpRequest.getBodyAsFormParameters());
1012                } catch (ParseException e) {
1013                        throw new SerializeException(e.getMessage(), e);
1014                }
1015                params.putAll(getAuthorizationGrant().toParameters());
1016
1017                switch (getAuthorizationGrant().getType().getScopeRequirementInTokenRequest()) {
1018                        case REQUIRED:
1019                                if (CollectionUtils.isEmpty(getScope())) {
1020                                        throw new SerializeException("Scope is required for the " + getAuthorizationGrant().getType() + " grant");
1021                                }
1022                                params.put("scope", Collections.singletonList(getScope().toString()));
1023                                break;
1024                        case OPTIONAL:
1025                                if (CollectionUtils.isNotEmpty(getScope())) {
1026                                        params.put("scope", Collections.singletonList(getScope().toString()));
1027                                }
1028                                break;
1029                        case NOT_ALLOWED:
1030                        default:
1031                                break;
1032                }
1033
1034                if (getClientID() != null) {
1035                        params.put("client_id", Collections.singletonList(getClientID().getValue()));
1036                }
1037
1038                if (getAuthorizationDetails() != null) {
1039                        params.put("authorization_details", Collections.singletonList(AuthorizationDetail.toJSONString(getAuthorizationDetails())));
1040                }
1041                
1042                if (getResources() != null) {
1043                        List<String> values = new LinkedList<>();
1044                        for (URI uri: getResources()) {
1045                                if (uri == null)
1046                                        continue;
1047                                values.add(uri.toString());
1048                        }
1049                        params.put("resource", values);
1050                }
1051                
1052                if (getExistingGrant() != null) {
1053                        params.put("existing_grant", Collections.singletonList(getExistingGrant().getValue()));
1054                }
1055
1056                if (getDeviceSecret() != null) {
1057                        params.put("device_secret", Collections.singletonList(getDeviceSecret().getValue()));
1058                }
1059
1060                if (! getCustomParameters().isEmpty()) {
1061                        params.putAll(getCustomParameters());
1062                }
1063
1064                httpRequest.setBody(URLUtils.serializeParameters(params));
1065
1066                return httpRequest;
1067        }
1068
1069
1070        /**
1071         * Parses a token request from the specified HTTP request.
1072         *
1073         * @param httpRequest The HTTP request. Must not be {@code null}.
1074         *
1075         * @return The token request.
1076         *
1077         * @throws ParseException If the HTTP request couldn't be parsed to a
1078         *                        token request.
1079         */
1080        public static TokenRequest parse(final HTTPRequest httpRequest)
1081                throws ParseException {
1082
1083                // Only HTTP POST accepted
1084                URI endpoint = httpRequest.getURI();
1085                httpRequest.ensureMethod(HTTPRequest.Method.POST);
1086                httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED);
1087
1088                // Parse client authentication candidates, if any
1089                List<ClientAuthentication> clientAuthCandidates;
1090                try {
1091                        clientAuthCandidates = ClientAuthentication.parseCandidates(httpRequest);
1092                } catch (ParseException e) {
1093                        throw new ParseException(e.getMessage(), OAuth2Error.INVALID_REQUEST.appendDescription(": " + e.getMessage()));
1094                }
1095
1096                // No fragment! May use query component!
1097                Map<String,List<String>> params = httpRequest.getBodyAsFormParameters();
1098                
1099                Set<String> repeatParams = MultivaluedMapUtils.getKeysWithMoreThanOneValue(params, ALLOWED_REPEATED_PARAMS);
1100                if (! repeatParams.isEmpty()) {
1101                        String msg = "Parameter(s) present more than once: " + repeatParams;
1102                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.setDescription(msg));
1103                }
1104
1105                // Parse grant
1106                AuthorizationGrant grant = AuthorizationGrant.parse(params);
1107
1108                if (CollectionUtils.isEmpty(clientAuthCandidates) && grant.getType().requiresClientAuthentication()) {
1109                        String msg = "Missing client authentication";
1110                        throw new ParseException(msg, OAuth2Error.INVALID_CLIENT.appendDescription(": " + msg));
1111                }
1112
1113                // Parse client id
1114                ClientID clientID = null;
1115
1116                if (CollectionUtils.isEmpty(clientAuthCandidates)) {
1117
1118                        // Parse optional client ID
1119                        String clientIDString = MultivaluedMapUtils.getFirstValue(params, "client_id");
1120
1121                        if (StringUtils.isNotBlank(clientIDString))
1122                                clientID = new ClientID(clientIDString);
1123
1124                        if (clientID == null && grant.getType().requiresClientID()) {
1125                                String msg = "Missing required client_id parameter";
1126                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
1127                        }
1128                }
1129
1130                // Parse optional scope
1131                String scopeValue = MultivaluedMapUtils.getFirstValue(params, "scope");
1132
1133                ParameterRequirement scopeRequirement = grant.getType().getScopeRequirementInTokenRequest();
1134
1135                Scope scope = null;
1136
1137                if (scopeValue != null && (ParameterRequirement.REQUIRED.equals(scopeRequirement) || ParameterRequirement.OPTIONAL.equals(scopeRequirement))) {
1138                        scope = Scope.parse(scopeValue);
1139                }
1140
1141                // Parse optional RAR
1142                String json = MultivaluedMapUtils.getFirstValue(params, "authorization_details");
1143
1144                List<AuthorizationDetail> authorizationDetails = null;
1145
1146                if (json != null) {
1147                        authorizationDetails = AuthorizationDetail.parseList(json);
1148                }
1149                
1150                // Parse optional resource URIs
1151                List<URI> resources = null;
1152                
1153                List<String> vList = params.get("resource");
1154                
1155                if (vList != null) {
1156                        
1157                        resources = new LinkedList<>();
1158                        
1159                        for (String uriValue: vList) {
1160                                
1161                                if (uriValue == null)
1162                                        continue;
1163                                
1164                                String errMsg = "Illegal resource parameter: Must be an absolute URI without a fragment: " + uriValue;
1165                                
1166                                URI resourceURI;
1167                                try {
1168                                        resourceURI = new URI(uriValue);
1169                                } catch (URISyntaxException e) {
1170                                        throw new ParseException(errMsg, OAuth2Error.INVALID_TARGET.setDescription(errMsg));
1171                                }
1172                                
1173                                if (! ResourceUtils.isLegalResourceURI(resourceURI)) {
1174                                        throw new ParseException(errMsg, OAuth2Error.INVALID_TARGET.setDescription(errMsg));
1175                                }
1176                                
1177                                resources.add(resourceURI);
1178                        }
1179                }
1180                
1181                String rt = MultivaluedMapUtils.getFirstValue(params, "existing_grant");
1182                RefreshToken existingGrant = StringUtils.isNotBlank(rt) ? new RefreshToken(rt) : null;
1183
1184                DeviceSecret deviceSecret = DeviceSecret.parse(MultivaluedMapUtils.getFirstValue(params, "device_secret"));
1185
1186                // Parse custom parameters
1187                Map<String,List<String>> customParams = new HashMap<>();
1188
1189                for (Map.Entry<String,List<String>> p: params.entrySet()) {
1190
1191                        if (REGISTERED_PARAMETER_NAMES.contains(p.getKey().toLowerCase())) {
1192                                continue; // skip
1193                        }
1194
1195                        if (! grant.getType().getRequestParameterNames().contains(p.getKey())) {
1196                                // We have a custom (non-registered) parameter
1197                                customParams.put(p.getKey(), p.getValue());
1198                        }
1199                }
1200
1201                if (CollectionUtils.isNotEmpty(clientAuthCandidates)) {
1202                        return new TokenRequest(endpoint, clientAuthCandidates, grant, scope, authorizationDetails, resources, deviceSecret, customParams);
1203                } else {
1204                        // public client
1205                        return new TokenRequest(endpoint, clientID, grant, scope, authorizationDetails, resources, existingGrant, deviceSecret, customParams);
1206                }
1207        }
1208}