001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.oauth2.sdk.ciba;
019
020
021import java.net.URI;
022import java.net.URISyntaxException;
023import java.util.*;
024
025import net.jcip.annotations.Immutable;
026
027import com.nimbusds.common.contenttype.ContentType;
028import com.nimbusds.jose.JWSObject;
029import com.nimbusds.jwt.JWT;
030import com.nimbusds.jwt.JWTClaimsSet;
031import com.nimbusds.jwt.JWTParser;
032import com.nimbusds.jwt.SignedJWT;
033import com.nimbusds.oauth2.sdk.AbstractAuthenticatedRequest;
034import com.nimbusds.oauth2.sdk.ParseException;
035import com.nimbusds.oauth2.sdk.Scope;
036import com.nimbusds.oauth2.sdk.SerializeException;
037import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
038import com.nimbusds.oauth2.sdk.auth.Secret;
039import com.nimbusds.oauth2.sdk.http.HTTPRequest;
040import com.nimbusds.oauth2.sdk.id.Identifier;
041import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
042import com.nimbusds.oauth2.sdk.util.*;
043import com.nimbusds.openid.connect.sdk.claims.ACR;
044
045/**
046 * <p>CIBA request to an OpenID provider / OAuth 2.0 authorisation server
047 * backend authentication endpoint. Supports plan as well as signed (JWT)
048 * requests.
049 *
050 * <p>Example HTTP request:
051 * 
052 * <pre>
053 * POST /bc-authorize HTTP/1.1
054 * Host: server.example.com
055 * Content-Type: application/x-www-form-urlencoded
056 *
057 * scope=openid%20email%20example-scope&
058 * client_notification_token=8d67dc78-7faa-4d41-aabd-67707b374255&
059 * binding_message=W4SCT&
060 * login_hint_token=eyJraWQiOiJsdGFjZXNidyIsImFsZyI6IkVTMjU2In0.eyJ
061 * zdWJfaWQiOnsic3ViamVjdF90eXBlIjoicGhvbmUiLCJwaG9uZSI6IisxMzMwMjg
062 * xODAwNCJ9fQ.Kk8jcUbHjJAQkRSHyDuFQr3NMEOSJEZc85VfER74tX6J9CuUllr8
063 * 9WKUHUR7MA0-mWlptMRRhdgW1ZDt7g1uwQ&
064 * client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3A
065 * client-assertion-type%3Ajwt-bearer&
066 * client_assertion=eyJraWQiOiJsdGFjZXNidyIsImFsZyI6IkVTMjU2In0.eyJ
067 * pc3MiOiJzNkJoZFJrcXQzIiwic3ViIjoiczZCaGRSa3F0MyIsImF1ZCI6Imh0dHB
068 * zOi8vc2VydmVyLmV4YW1wbGUuY29tIiwianRpIjoiYmRjLVhzX3NmLTNZTW80RlN
069 * 6SUoyUSIsImlhdCI6MTUzNzgxOTQ4NiwiZXhwIjoxNTM3ODE5Nzc3fQ.Ybr8mg_3
070 * E2OptOSsA8rnelYO_y1L-yFaF_j1iemM3ntB61_GN3APe5cl_-5a6cvGlP154XAK
071 * 7fL-GaZSdnd9kg
072 * </pre>
073 *
074 * <p>Related specifications:
075 *
076 * <ul>
077 *      <li>OpenID Connect CIBA Flow - Core 1.0, section 7.1.
078 * </ul>
079 */
080@Immutable
081public class CIBARequest extends AbstractAuthenticatedRequest {
082        
083        
084        /**
085         * The maximum allowed length of a client notification token.
086         */
087        public static final int CLIENT_NOTIFICATION_TOKEN_MAX_LENGTH = 1024;
088        
089
090        /**
091         * The registered parameter names.
092         */
093        private static final Set<String> REGISTERED_PARAMETER_NAMES;
094
095        static {
096                Set<String> p = new HashSet<>();
097
098                // Plain
099                p.add("scope");
100                p.add("client_notification_token");
101                p.add("acr_values");
102                p.add("login_hint_token");
103                p.add("id_token_hint");
104                p.add("login_hint");
105                p.add("binding_message");
106                p.add("user_code");
107                p.add("requested_expiry");
108                
109                // Signed JWT
110                p.add("request");
111
112                REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p);
113        }
114
115        
116        /**
117         * The scope (required), must contain {@code openid}.
118         */
119        private final Scope scope;
120
121        
122        /**
123         * The client notification token, required for the CIBA ping and push
124         * token delivery modes.
125         */
126        private final BearerAccessToken clientNotificationToken;
127        
128        
129        /**
130         * Requested Authentication Context Class Reference values (optional).
131         */
132        private final List<ACR> acrValues;
133        
134        
135        /**
136         * A token containing information identifying the end-user for whom
137         * authentication is being requested (optional).
138         */
139        private final String loginHintTokenString;
140        
141        
142        /**
143         * Previously issued ID token passed as a hint to identify the end-user
144         * for whom authentication is being requested (optional).
145         */
146        private final JWT idTokenHint;
147        
148        
149        /**
150         * Login hint (email address, phone number, etc) about the end-user for
151         * whom authentication is being requested (optional).
152         */
153        private final String loginHint;
154        
155        
156        /**
157         * Human readable binding message for the display at the consumption
158         * and authentication devices (optional).
159         */
160        private final String bindingMessage;
161        
162        
163        /**
164         * User secret code (password, PIN, etc) to authorise the CIBA request
165         * with the authentication device (optional).
166         */
167        private final Secret userCode;
168        
169        
170        /**
171         * Requested expiration for the {@code auth_req_id} (optional).
172         */
173        private final Integer requestedЕxpiry;
174        
175        
176        /**
177         * Custom parameters.
178         */
179        private final Map<String,List<String>> customParams;
180        
181        
182        /**
183         * The JWT for a signed request.
184         */
185        private final SignedJWT signedRequest;
186        
187
188        /**
189         * Builder for constructing CIBA requests.
190         */
191        public static class Builder {
192
193                
194                /**
195                 * The endpoint URI (optional).
196                 */
197                private URI uri;
198                
199                
200                /**
201                 * The client authentication (required).
202                 */
203                private final ClientAuthentication clientAuth;
204                
205                
206                /**
207                 * The scope (required).
208                 */
209                private final Scope scope;
210                
211                
212                /**
213                 * The client notification type, required for the CIBA ping and
214                 * push token delivery modes.
215                 */
216                private BearerAccessToken clientNotificationToken;
217                
218                
219                /**
220                 * Requested Authentication Context Class Reference values
221                 * (optional).
222                 */
223                private List<ACR> acrValues;
224                
225                
226                /**
227                 * A token containing information identifying the end-user for
228                 * whom authentication is being requested (optional).
229                 */
230                private String loginHintTokenString;
231                
232                
233                /**
234                 * Previously issued ID token passed as a hint to identify the
235                 * end-user for whom authentication is being requested
236                 * (optional).
237                 */
238                private JWT idTokenHint;
239                
240                
241                /**
242                 * Identity hint (email address, phone number, etc) about the
243                 * end-user for whom authentication is being requested
244                 * (optional).
245                 */
246                private String loginHint;
247                
248                
249                /**
250                 * Human readable binding message for the display at the
251                 * consumption and authentication devices (optional).
252                 */
253                private String bindingMessage;
254                
255                
256                /**
257                 * User secret code (password, PIN, etc) to authorise the CIBA
258                 * request with the authentication device (optional).
259                 */
260                private Secret userCode;
261                
262                
263                /**
264                 * Requested expiration for the {@code auth_req_id} (optional).
265                 */
266                private Integer requestedExpiry;
267                
268                
269                /**
270                 * Custom parameters.
271                 */
272                private Map<String,List<String>> customParams = new HashMap<>();
273                
274                
275                /**
276                 * The JWT for a signed request.
277                 */
278                private final SignedJWT signedRequest;
279
280                
281                /**
282                 * Creates a new CIBA request builder.
283                 *
284                 * @param clientAuth The client authentication. Must not be
285                 *                   {@code null}.
286                 * @param scope      The requested scope. Must not be empty or
287                 *                   {@code null}.
288                 */
289                public Builder(final ClientAuthentication clientAuth,
290                               final Scope scope) {
291                        
292                        if (clientAuth == null) {
293                                throw new IllegalArgumentException("The client authentication must not be null");
294                        }
295                        this.clientAuth = clientAuth;
296                        
297                        if (CollectionUtils.isEmpty(scope)) {
298                                throw new IllegalArgumentException("The scope must not be null or empty");
299                        }
300                        this.scope = scope;
301                        
302                        signedRequest = null;
303                }
304                
305                
306                /**
307                 * Creates a new CIBA signed request builder.
308                 *
309                 * @param clientAuth    The client authentication. Must not be
310                 *                      {@code null}.
311                 * @param signedRequest The signed request JWT. Must not be
312                 *                      {@code null}.
313                 */
314                public Builder(final ClientAuthentication clientAuth,
315                               final SignedJWT signedRequest) {
316                        
317                        if (clientAuth == null) {
318                                throw new IllegalArgumentException("The client authentication must not be null");
319                        }
320                        this.clientAuth = clientAuth;
321                        
322                        if (signedRequest == null) {
323                                throw new IllegalArgumentException("The signed request JWT must not be null");
324                        }
325                        this.signedRequest = signedRequest;
326                        
327                        scope = null;
328                }
329                
330
331                /**
332                 * Creates a new CIBA request builder from the specified
333                 * request.
334                 *
335                 * @param request The CIBA request. Must not be {@code null}.
336                 */
337                public Builder(final CIBARequest request) {
338                        
339                        uri = request.getEndpointURI();
340                        clientAuth = request.getClientAuthentication();
341                        scope = request.getScope();
342                        clientNotificationToken = request.getClientNotificationToken();
343                        acrValues = request.getACRValues();
344                        loginHintTokenString = request.getLoginHintTokenString();
345                        idTokenHint = request.getIDTokenHint();
346                        loginHint = request.getLoginHint();
347                        bindingMessage = request.getBindingMessage();
348                        userCode = request.getUserCode();
349                        requestedExpiry = request.getRequestedExpiry();
350                        customParams = request.getCustomParameters();
351                        signedRequest = request.getRequestJWT();
352                }
353                
354                
355                /**
356                 * Sets the client notification token, required for the CIBA
357                 * ping and push token delivery modes. Corresponds to the
358                 * {@code client_notification_token} parameter.
359                 *
360                 * @param token The client notification token, {@code null} if
361                 *              not specified.
362                 *
363                 * @return This builder.
364                 */
365                public Builder clientNotificationToken(final BearerAccessToken token) {
366                        this.clientNotificationToken = token;
367                        return this;
368                }
369
370                
371                /**
372                 * Sets the requested Authentication Context Class Reference
373                 * values. Corresponds to the optional {@code acr_values}
374                 * parameter.
375                 *
376                 * @param acrValues The requested ACR values, {@code null} if
377                 *                  not specified.
378                 *
379                 * @return This builder.
380                 */
381                public Builder acrValues(final List<ACR> acrValues) {
382                        this.acrValues = acrValues;
383                        return this;
384                }
385                
386                
387                /**
388                 * Sets the login hint token string, containing information
389                 * identifying the end-user for whom authentication is being requested.
390                 * Corresponds to the {@code login_hint_token} parameter.
391                 *
392                 * @param loginHintTokenString The login hint token string,
393                 *                             {@code null} if not specified.
394                 *
395                 * @return This builder.
396                 */
397                public Builder loginHintTokenString(final String loginHintTokenString) {
398                        this.loginHintTokenString = loginHintTokenString;
399                        return this;
400                }
401                
402                
403                /**
404                 * Sets the ID Token hint, passed as a hint to identify the
405                 * end-user for whom authentication is being requested.
406                 * Corresponds to the {@code id_token_hint} parameter.
407                 *
408                 * @param idTokenHint The ID Token hint, {@code null} if not
409                 *                    specified.
410                 *
411                 * @return This builder.
412                 */
413                public Builder idTokenHint(final JWT idTokenHint) {
414                        this.idTokenHint = idTokenHint;
415                        return this;
416                }
417                
418                
419                /**
420                 * Sets the login hint (email address, phone number, etc),
421                 * about the end-user for whom authentication is being
422                 * requested. Corresponds to the {@code login_hint} parameter.
423                 *
424                 * @param loginHint The login hint, {@code null} if not
425                 *                  specified.
426                 *
427                 * @return This builder.
428                 */
429                public Builder loginHint(final String loginHint) {
430                        this.loginHint = loginHint;
431                        return this;
432                }
433                
434                
435                /**
436                 * Sets the human readable binding message for the display at
437                 * the consumption and authentication devices. Corresponds to
438                 * the {@code binding_message} parameter.
439                 *
440                 * @param bindingMessage The binding message, {@code null} if
441                 *                       not specified.
442                 *
443                 * @return This builder.
444                 */
445                public Builder bindingMessage(final String bindingMessage) {
446                        this.bindingMessage = bindingMessage;
447                        return this;
448                }
449                
450                
451                /**
452                 * Gets the user secret code (password, PIN, etc) to authorise
453                 * the CIBA request with the authentication device. Corresponds
454                 * to the {@code user_code} parameter.
455                 *
456                 * @param userCode The user code, {@code null} if not
457                 *                 specified.
458                 *
459                 * @return This builder.
460                 */
461                public Builder userCode(final Secret userCode) {
462                        this.userCode = userCode;
463                        return this;
464                }
465                
466                
467                /**
468                 * Sets the requested expiration for the {@code auth_req_id}.
469                 * Corresponds to the {@code requested_expiry} parameter.
470                 *
471                 * @param requestedExpiry The required expiry (as positive
472                 *                        integer), {@code null} if not
473                 *                        specified.
474                 */
475                public Builder requestedExpiry(final Integer requestedExpiry) {
476                        this.requestedExpiry = requestedExpiry;
477                        return this;
478                }
479                
480                
481                /**
482                 * Sets a custom parameter.
483                 *
484                 * @param name   The parameter name. Must not be {@code null}.
485                 * @param values The parameter values, {@code null} if not
486                 *               specified.
487                 *
488                 * @return This builder.
489                 */
490                public Builder customParameter(final String name, final String ... values) {
491                        
492                        if (values == null || values.length == 0) {
493                                customParams.remove(name);
494                        } else {
495                                customParams.put(name, Arrays.asList(values));
496                        }
497                        
498                        return this;
499                }
500                
501                
502                /**
503                 * Sets the URI of the endpoint (HTTP or HTTPS) for which the
504                 * request is intended.
505                 *
506                 * @param uri The endpoint URI, {@code null} if not specified.
507                 *
508                 * @return This builder.
509                 */
510                public Builder endpointURI(final URI uri) {
511                        
512                        this.uri = uri;
513                        return this;
514                }
515                
516                
517                /**
518                 * Builds a new CIBA request.
519                 *
520                 * @return The CIBA request.
521                 */
522                public CIBARequest build() {
523                        
524                        try {
525                                if (signedRequest != null) {
526                                        return new CIBARequest(
527                                                uri,
528                                                clientAuth,
529                                                signedRequest
530                                        );
531                                }
532                                
533                                // Plain request
534                                return new CIBARequest(
535                                        uri,
536                                        clientAuth,
537                                        scope,
538                                        clientNotificationToken,
539                                        acrValues,
540                                        loginHintTokenString,
541                                        idTokenHint,
542                                        loginHint,
543                                        bindingMessage,
544                                        userCode,
545                                        requestedExpiry,
546                                        customParams
547                                );
548                        } catch (IllegalArgumentException e) {
549                                throw new IllegalArgumentException(e.getMessage(), e);
550                        }
551                }
552        }
553        
554        
555        /**
556         * Creates a new CIBA request.
557         *
558         * @param uri                     The endpoint URI, {@code null} if not
559         *                                specified.
560         * @param clientAuth              The client authentication. Must not
561         *                                be {@code null}.
562         * @param scope                   The requested scope. Must not be
563         *                                empty or {@code null}.
564         * @param clientNotificationToken The client notification token,
565         *                                {@code null} if not specified.
566         * @param acrValues               The requested ACR values,
567         *                                {@code null} if not specified.
568         * @param loginHintTokenString    The login hint token string,
569         *                                {@code null} if not specified.
570         * @param idTokenHint             The ID Token hint, {@code null} if
571         *                                not specified.
572         * @param loginHint               The login hint, {@code null} if not
573         *                                specified.
574         * @param bindingMessage          The binding message, {@code null} if
575         *                                not specified.
576         * @param userCode                The user code, {@code null} if not
577         *                                specified.
578         * @param requestedExpiry         The required expiry (as positive
579         *                                integer), {@code null} if not
580         *                                specified.
581         * @param customParams            Custom parameters, empty or
582         *                                {@code null} if not specified.
583         */
584        public CIBARequest(final URI uri,
585                           final ClientAuthentication clientAuth,
586                           final Scope scope,
587                           final BearerAccessToken clientNotificationToken,
588                           final List<ACR> acrValues,
589                           final String loginHintTokenString,
590                           final JWT idTokenHint,
591                           final String loginHint,
592                           final String bindingMessage,
593                           final Secret userCode,
594                           final Integer requestedExpiry,
595                           final Map<String, List<String>> customParams) {
596                
597                super(uri, clientAuth);
598                
599                if (CollectionUtils.isEmpty(scope)) {
600                        throw new IllegalArgumentException("The scope must not be null or empty");
601                }
602                this.scope = scope;
603                
604                if (clientNotificationToken != null && clientNotificationToken.getValue().length() > CLIENT_NOTIFICATION_TOKEN_MAX_LENGTH) {
605                        throw new IllegalArgumentException("The client notification token must not exceed " + CLIENT_NOTIFICATION_TOKEN_MAX_LENGTH + " chars");
606                }
607                this.clientNotificationToken = clientNotificationToken;
608                
609                this.acrValues = acrValues;
610                
611                // https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0-03.html#rfc.section.7.1
612                // As in the CIBA flow the OP does not have an interaction with
613                // the end-user through the consumption device, it is REQUIRED
614                // that the Client provides one (and only one) of the hints
615                // specified above in the authentication request, that is
616                // "login_hint_token", "id_token_hint" or "login_hint".
617                int numHints = 0;
618                
619                if (loginHintTokenString != null) numHints++;
620                this.loginHintTokenString = loginHintTokenString;
621                
622                if (idTokenHint != null) numHints++;
623                this.idTokenHint = idTokenHint;
624                
625                if (loginHint != null) numHints++;
626                this.loginHint = loginHint;
627                
628                if (numHints != 1) {
629                        throw new IllegalArgumentException("One user identity hist must be provided (login_hint_token, id_token_hint or login_hint)");
630                }
631                
632                this.bindingMessage = bindingMessage;
633                
634                this.userCode = userCode;
635                
636                if (requestedExpiry != null && requestedExpiry < 1) {
637                        throw new IllegalArgumentException("The requested expiry must be a positive integer");
638                }
639                this.requestedЕxpiry = requestedExpiry;
640                
641                this.customParams = customParams != null ? customParams : Collections.<String, List<String>>emptyMap();
642                
643                signedRequest = null;
644        }
645        
646        
647        /**
648         * Creates a new CIBA signed request.
649         *
650         * @param uri           The endpoint URI, {@code null} if not
651         *                      specified.
652         * @param clientAuth    The client authentication. Must not be
653         *                      {@code null}.
654         * @param signedRequest The signed request JWT. Must not be
655         *                      {@code null}.
656         */
657        public CIBARequest(final URI uri,
658                           final ClientAuthentication clientAuth,
659                           final SignedJWT signedRequest) {
660                
661                super(uri, clientAuth);
662                
663                if (signedRequest == null) {
664                        throw new IllegalArgumentException("The signed request JWT must not be null");
665                }
666                if (JWSObject.State.UNSIGNED.equals(signedRequest.getState())) {
667                        throw new IllegalArgumentException("The request JWT must be in a signed state");
668                }
669                this.signedRequest = signedRequest;
670                
671                scope = null;
672                clientNotificationToken = null;
673                acrValues = null;
674                loginHintTokenString = null;
675                idTokenHint = null;
676                loginHint = null;
677                bindingMessage = null;
678                userCode = null;
679                requestedЕxpiry = null;
680                customParams = Collections.emptyMap();
681        }
682
683        
684        /**
685         * Returns the registered (standard) CIBA request parameter names.
686         *
687         * @return The registered CIBA request parameter names, as a
688         *         unmodifiable set.
689         */
690        public static Set<String> getRegisteredParameterNames() {
691
692                return REGISTERED_PARAMETER_NAMES;
693        }
694
695        
696        /**
697         * Gets the scope. Corresponds to the optional {@code scope} parameter.
698         *
699         * @return The scope, {@code null} for a {@link #isSigned signed
700         *         request}.
701         */
702        public Scope getScope() {
703
704                return scope;
705        }
706        
707        
708        /**
709         * Gets the client notification token, required for the CIBA ping and
710         * push token delivery modes. Corresponds to the
711         * {@code client_notification_token} parameter.
712         *
713         * @return The client notification token, {@code null} if not
714         *         specified.
715         */
716        public BearerAccessToken getClientNotificationToken() {
717                
718                return clientNotificationToken;
719        }
720        
721        
722        /**
723         * Gets the requested Authentication Context Class Reference values.
724         * Corresponds to the optional {@code acr_values} parameter.
725         *
726         * @return The requested ACR values, {@code null} if not specified.
727         */
728        public List<ACR> getACRValues() {
729                
730                return acrValues;
731        }
732        
733        
734        /**
735         * Gets the login hint token string, containing information
736         * identifying the end-user for whom authentication is being requested.
737         * Corresponds to the {@code login_hint_token} parameter.
738         *
739         * @return The login hint token string, {@code null} if not
740         *         specified.
741         */
742        public String getLoginHintTokenString() {
743                
744                return loginHintTokenString;
745        }
746        
747        
748        /**
749         * Gets the ID Token hint, passed as a hint to identify the end-user
750         * for whom authentication is being requested. Corresponds to the
751         * {@code id_token_hint} parameter.
752         *
753         * @return The ID Token hint, {@code null} if not specified.
754         */
755        public JWT getIDTokenHint() {
756                
757                return idTokenHint;
758        }
759        
760        
761        /**
762         * Gets the login hint (email address, phone number, etc), about the
763         * end-user for whom authentication is being requested. Corresponds to
764         * the {@code login_hint} parameter.
765         *
766         * @return The login hint, {@code null} if not specified.
767         */
768        public String getLoginHint() {
769                
770                return loginHint;
771        }
772        
773        
774        /**
775         * Gets the human readable binding message for the display at the
776         * consumption and authentication devices. Corresponds to the
777         * {@code binding_message} parameter.
778         *
779         * @return The binding message, {@code null} if not specified.
780         */
781        public String getBindingMessage() {
782                
783                return bindingMessage;
784        }
785        
786        
787        /**
788         * Gets the user secret code (password, PIN, etc) to authorise the CIBA
789         * request with the authentication device. Corresponds to the
790         * {@code user_code} parameter.
791         *
792         * @return The user code, {@code null} if not specified.
793         */
794        public Secret getUserCode() {
795                
796                return userCode;
797        }
798        
799        
800        /**
801         * Gets the requested expiration for the {@code auth_req_id}.
802         * Corresponds to the {@code requested_expiry} parameter.
803         *
804         * @return The required expiry (as positive integer), {@code null} if
805         *         not specified.
806         */
807        public Integer getRequestedExpiry() {
808                
809                return requestedЕxpiry;
810        }
811        
812        
813        /**
814         * Returns the additional custom parameters.
815         *
816         * @return The additional custom parameters as a unmodifiable map,
817         *         empty map if none.
818         */
819        public Map<String, List<String>> getCustomParameters() {
820                
821                return customParams;
822        }
823        
824        
825        /**
826         * Returns the specified custom parameter.
827         *
828         * @param name The parameter name. Must not be {@code null}.
829         *
830         * @return The parameter value(s), {@code null} if not specified.
831         */
832        public List<String> getCustomParameter(final String name) {
833                
834                return customParams.get(name);
835        }
836        
837        
838        /**
839         * Returns {@code true} if this request is signed.
840         *
841         * @return {@code true} for a signed request, {@code false} for a plain
842         *         request.
843         */
844        public boolean isSigned() {
845                
846                return signedRequest != null;
847        }
848        
849        
850        /**
851         * Returns the JWT for a signed request.
852         *
853         * @return The request JWT.
854         */
855        public SignedJWT getRequestJWT() {
856                
857                return signedRequest;
858        }
859        
860        
861        /**
862         * Returns the for parameters for this CIBA request. Parameters which
863         * are part of the client authentication are not included.
864         *
865         * @return The parameters.
866         */
867        public Map<String, List<String>> toParameters() {
868                
869                // Put custom params first, so they may be overwritten by std params
870                Map<String, List<String>> params = new LinkedHashMap<>(getCustomParameters());
871                
872                if (isSigned()) {
873                        params.put("request", Collections.singletonList(signedRequest.serialize()));
874                        return params;
875                }
876                
877                params.put("scope", Collections.singletonList(getScope().toString()));
878                
879                if (getClientNotificationToken() != null) {
880                        params.put("client_notification_token", Collections.singletonList(getClientNotificationToken().getValue()));
881                }
882                if (getACRValues() != null) {
883                        params.put("acr_values", Identifier.toStringList(getACRValues()));
884                }
885                if (getLoginHintTokenString() != null) {
886                        params.put("login_hint_token", Collections.singletonList(getLoginHintTokenString()));
887                }
888                if (getIDTokenHint() != null) {
889                        params.put("id_token_hint", Collections.singletonList(getIDTokenHint().serialize()));
890                }
891                if (getLoginHint() != null) {
892                        params.put("login_hint", Collections.singletonList(getLoginHint()));
893                }
894                if (getBindingMessage() != null) {
895                        params.put("binding_message", Collections.singletonList(getBindingMessage()));
896                }
897                if (getUserCode() != null) {
898                        params.put("user_code", Collections.singletonList(getUserCode().getValue()));
899                }
900                if (getRequestedExpiry() != null) {
901                        params.put("requested_expiry", Collections.singletonList(getRequestedExpiry().toString()));
902                }
903                
904                return params;
905        }
906        
907        
908        /**
909         * Returns the parameters for this CIBA request as a JSON Web Token
910         * (JWT) claims set. Intended for creating a signed CIBA request.
911         *
912         * @return The parameters as JWT claim set.
913         */
914        public JWTClaimsSet toJWTClaimsSet() {
915                
916                if (isSigned()) {
917                        throw new IllegalStateException();
918                }
919                
920                return JWTClaimsSetUtils.toJWTClaimsSet(toParameters());
921        }
922        
923        
924        /**
925         * Returns the matching HTTP request.
926         *
927         * @return The HTTP request.
928         */
929        @Override
930        public HTTPRequest toHTTPRequest() {
931
932                if (getEndpointURI() == null)
933                        throw new SerializeException("The endpoint URI is not specified");
934
935                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getEndpointURI());
936                httpRequest.setEntityContentType(ContentType.APPLICATION_URLENCODED);
937
938                getClientAuthentication().applyTo(httpRequest);
939
940                Map<String, List<String>> params = httpRequest.getQueryParameters();
941                params.putAll(toParameters());
942                httpRequest.setQuery(URLUtils.serializeParameters(params));
943                
944                return httpRequest;
945        }
946
947        
948        /**
949         * Parses a CIBA request from the specified HTTP request.
950         *
951         * @param httpRequest The HTTP request. Must not be {@code null}.
952         *
953         * @return The CIBA request.
954         *
955         * @throws ParseException If parsing failed.
956         */
957        public static CIBARequest parse(final HTTPRequest httpRequest) throws ParseException {
958
959                // Only HTTP POST accepted
960                URI uri = httpRequest.getURI();
961                httpRequest.ensureMethod(HTTPRequest.Method.POST);
962                httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED);
963                
964                ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest);
965                
966                if (clientAuth == null) {
967                        throw new ParseException("Missing required client authentication");
968                }
969                
970                Map<String, List<String>> params = httpRequest.getQueryParameters();
971                
972                String v;
973                
974                if (params.containsKey("request")) {
975                        // Signed request
976                        v = MultivaluedMapUtils.getFirstValue(params, "request");
977                        
978                        if (StringUtils.isBlank(v)) {
979                                throw new ParseException("Empty request parameter");
980                        }
981                        
982                        SignedJWT signedRequest;
983                        try {
984                                signedRequest = SignedJWT.parse(v);
985                        } catch (java.text.ParseException e) {
986                                throw new ParseException("Invalid request JWT: " + e.getMessage(), e);
987                        }
988                        
989                        try {
990                                return new CIBARequest(uri, clientAuth, signedRequest);
991                        } catch (IllegalArgumentException e) {
992                                throw new ParseException(e.getMessage(), e);
993                        }
994                }
995                
996                
997                // Plain request
998                
999                // Parse required scope
1000                v = MultivaluedMapUtils.getFirstValue(params, "scope");
1001                Scope scope = Scope.parse(v);
1002
1003                v = MultivaluedMapUtils.getFirstValue(params, "client_notification_token");
1004                BearerAccessToken clientNotificationToken = null;
1005                if (StringUtils.isNotBlank(v)) {
1006                        clientNotificationToken = new BearerAccessToken(v);
1007                }
1008                
1009                v = MultivaluedMapUtils.getFirstValue(params, "acr_values");
1010                List<ACR> acrValues = null;
1011                if (StringUtils.isNotBlank(v)) {
1012                        acrValues = new LinkedList<>();
1013                        StringTokenizer st = new StringTokenizer(v, " ");
1014                        while (st.hasMoreTokens()) {
1015                                acrValues.add(new ACR(st.nextToken()));
1016                        }
1017                }
1018                
1019                String loginHintTokenString = MultivaluedMapUtils.getFirstValue(params, "login_hint_token");
1020                
1021                v = MultivaluedMapUtils.getFirstValue(params, "id_token_hint");
1022                JWT idTokenHint = null;
1023                if (StringUtils.isNotBlank(v)) {
1024                        try {
1025                                idTokenHint = JWTParser.parse(v);
1026                        } catch (java.text.ParseException e) {
1027                                throw new ParseException("Invalid id_token_hint parameter: " + e.getMessage());
1028                        }
1029                }
1030                
1031                String loginHint = MultivaluedMapUtils.getFirstValue(params, "login_hint");
1032                
1033                v = MultivaluedMapUtils.getFirstValue(params, "user_code");
1034                
1035                Secret userCode = null;
1036                if (StringUtils.isNotBlank(v)) {
1037                        userCode = new Secret(v);
1038                }
1039                
1040                String bindingMessage = MultivaluedMapUtils.getFirstValue(params, "binding_message");
1041                
1042                v = MultivaluedMapUtils.getFirstValue(params, "requested_expiry");
1043                
1044                Integer requestedExpiry = null;
1045
1046                if (StringUtils.isNotBlank(v)) {
1047                        try {
1048                                requestedExpiry = Integer.valueOf(v);
1049                        } catch (NumberFormatException e) {
1050                                throw new ParseException("The requested_expiry parameter must be an integer");
1051                        }
1052                }
1053                
1054                // Parse additional custom parameters
1055                Map<String,List<String>> customParams = null;
1056                
1057                for (Map.Entry<String,List<String>> p: params.entrySet()) {
1058                        
1059                        if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) {
1060                                // We have a custom parameter
1061                                if (customParams == null) {
1062                                        customParams = new HashMap<>();
1063                                }
1064                                customParams.put(p.getKey(), p.getValue());
1065                        }
1066                }
1067
1068                try {
1069                        return new CIBARequest(
1070                                uri, clientAuth,
1071                                scope, clientNotificationToken, acrValues, loginHintTokenString, idTokenHint, loginHint, bindingMessage, userCode, requestedExpiry,
1072                                customParams);
1073                } catch (IllegalArgumentException e) {
1074                        throw new ParseException(e.getMessage());
1075                }
1076        }
1077}