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