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&amp;
058 * client_notification_token=8d67dc78-7faa-4d41-aabd-67707b374255&amp;
059 * binding_message=W4SCT&amp;
060 * login_hint_token=eyJraWQiOiJsdGFjZXNidyIsImFsZyI6IkVTMjU2In0.eyJ
061 * zdWJfaWQiOnsic3ViamVjdF90eXBlIjoicGhvbmUiLCJwaG9uZSI6IisxMzMwMjg
062 * xODAwNCJ9fQ.Kk8jcUbHjJAQkRSHyDuFQr3NMEOSJEZc85VfER74tX6J9CuUllr8
063 * 9WKUHUR7MA0-mWlptMRRhdgW1ZDt7g1uwQ&amp;
064 * client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3A
065 * client-assertion-type%3Ajwt-bearer&amp;
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                 * @return This builder.
476                 */
477                public Builder requestedExpiry(final Integer requestedExpiry) {
478                        this.requestedExpiry = requestedExpiry;
479                        return this;
480                }
481                
482                
483                /**
484                 * Sets a custom parameter.
485                 *
486                 * @param name   The parameter name. Must not be {@code null}.
487                 * @param values The parameter values, {@code null} if not
488                 *               specified.
489                 *
490                 * @return This builder.
491                 */
492                public Builder customParameter(final String name, final String ... values) {
493                        
494                        if (values == null || values.length == 0) {
495                                customParams.remove(name);
496                        } else {
497                                customParams.put(name, Arrays.asList(values));
498                        }
499                        
500                        return this;
501                }
502                
503                
504                /**
505                 * Sets the URI of the endpoint (HTTP or HTTPS) for which the
506                 * request is intended.
507                 *
508                 * @param uri The endpoint URI, {@code null} if not specified.
509                 *
510                 * @return This builder.
511                 */
512                public Builder endpointURI(final URI uri) {
513                        
514                        this.uri = uri;
515                        return this;
516                }
517                
518                
519                /**
520                 * Builds a new CIBA request.
521                 *
522                 * @return The CIBA request.
523                 */
524                public CIBARequest build() {
525                        
526                        try {
527                                if (signedRequest != null) {
528                                        return new CIBARequest(
529                                                uri,
530                                                clientAuth,
531                                                signedRequest
532                                        );
533                                }
534                                
535                                // Plain request
536                                return new CIBARequest(
537                                        uri,
538                                        clientAuth,
539                                        scope,
540                                        clientNotificationToken,
541                                        acrValues,
542                                        loginHintTokenString,
543                                        idTokenHint,
544                                        loginHint,
545                                        bindingMessage,
546                                        userCode,
547                                        requestedExpiry,
548                                        customParams
549                                );
550                        } catch (IllegalArgumentException e) {
551                                throw new IllegalArgumentException(e.getMessage(), e);
552                        }
553                }
554        }
555        
556        
557        /**
558         * Creates a new CIBA request.
559         *
560         * @param uri                     The endpoint URI, {@code null} if not
561         *                                specified.
562         * @param clientAuth              The client authentication. Must not
563         *                                be {@code null}.
564         * @param scope                   The requested scope. Must not be
565         *                                empty or {@code null}.
566         * @param clientNotificationToken The client notification token,
567         *                                {@code null} if not specified.
568         * @param acrValues               The requested ACR values,
569         *                                {@code null} if not specified.
570         * @param loginHintTokenString    The login hint token string,
571         *                                {@code null} if not specified.
572         * @param idTokenHint             The ID Token hint, {@code null} if
573         *                                not specified.
574         * @param loginHint               The login hint, {@code null} if not
575         *                                specified.
576         * @param bindingMessage          The binding message, {@code null} if
577         *                                not specified.
578         * @param userCode                The user code, {@code null} if not
579         *                                specified.
580         * @param requestedExpiry         The required expiry (as positive
581         *                                integer), {@code null} if not
582         *                                specified.
583         * @param customParams            Custom parameters, empty or
584         *                                {@code null} if not specified.
585         */
586        public CIBARequest(final URI uri,
587                           final ClientAuthentication clientAuth,
588                           final Scope scope,
589                           final BearerAccessToken clientNotificationToken,
590                           final List<ACR> acrValues,
591                           final String loginHintTokenString,
592                           final JWT idTokenHint,
593                           final String loginHint,
594                           final String bindingMessage,
595                           final Secret userCode,
596                           final Integer requestedExpiry,
597                           final Map<String, List<String>> customParams) {
598                
599                super(uri, clientAuth);
600                
601                if (CollectionUtils.isEmpty(scope)) {
602                        throw new IllegalArgumentException("The scope must not be null or empty");
603                }
604                this.scope = scope;
605                
606                if (clientNotificationToken != null && clientNotificationToken.getValue().length() > CLIENT_NOTIFICATION_TOKEN_MAX_LENGTH) {
607                        throw new IllegalArgumentException("The client notification token must not exceed " + CLIENT_NOTIFICATION_TOKEN_MAX_LENGTH + " chars");
608                }
609                this.clientNotificationToken = clientNotificationToken;
610                
611                this.acrValues = acrValues;
612                
613                // https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0-03.html#rfc.section.7.1
614                // As in the CIBA flow the OP does not have an interaction with
615                // the end-user through the consumption device, it is REQUIRED
616                // that the Client provides one (and only one) of the hints
617                // specified above in the authentication request, that is
618                // "login_hint_token", "id_token_hint" or "login_hint".
619                int numHints = 0;
620                
621                if (loginHintTokenString != null) numHints++;
622                this.loginHintTokenString = loginHintTokenString;
623                
624                if (idTokenHint != null) numHints++;
625                this.idTokenHint = idTokenHint;
626                
627                if (loginHint != null) numHints++;
628                this.loginHint = loginHint;
629                
630                if (numHints != 1) {
631                        throw new IllegalArgumentException("One user identity hist must be provided (login_hint_token, id_token_hint or login_hint)");
632                }
633                
634                this.bindingMessage = bindingMessage;
635                
636                this.userCode = userCode;
637                
638                if (requestedExpiry != null && requestedExpiry < 1) {
639                        throw new IllegalArgumentException("The requested expiry must be a positive integer");
640                }
641                this.requestedЕxpiry = requestedExpiry;
642                
643                this.customParams = customParams != null ? customParams : Collections.<String, List<String>>emptyMap();
644                
645                signedRequest = null;
646        }
647        
648        
649        /**
650         * Creates a new CIBA signed request.
651         *
652         * @param uri           The endpoint URI, {@code null} if not
653         *                      specified.
654         * @param clientAuth    The client authentication. Must not be
655         *                      {@code null}.
656         * @param signedRequest The signed request JWT. Must not be
657         *                      {@code null}.
658         */
659        public CIBARequest(final URI uri,
660                           final ClientAuthentication clientAuth,
661                           final SignedJWT signedRequest) {
662                
663                super(uri, clientAuth);
664                
665                if (signedRequest == null) {
666                        throw new IllegalArgumentException("The signed request JWT must not be null");
667                }
668                if (JWSObject.State.UNSIGNED.equals(signedRequest.getState())) {
669                        throw new IllegalArgumentException("The request JWT must be in a signed state");
670                }
671                this.signedRequest = signedRequest;
672                
673                scope = null;
674                clientNotificationToken = null;
675                acrValues = null;
676                loginHintTokenString = null;
677                idTokenHint = null;
678                loginHint = null;
679                bindingMessage = null;
680                userCode = null;
681                requestedЕxpiry = null;
682                customParams = Collections.emptyMap();
683        }
684
685        
686        /**
687         * Returns the registered (standard) CIBA request parameter names.
688         *
689         * @return The registered CIBA request parameter names, as a
690         *         unmodifiable set.
691         */
692        public static Set<String> getRegisteredParameterNames() {
693
694                return REGISTERED_PARAMETER_NAMES;
695        }
696
697        
698        /**
699         * Gets the scope. Corresponds to the optional {@code scope} parameter.
700         *
701         * @return The scope, {@code null} for a {@link #isSigned signed
702         *         request}.
703         */
704        public Scope getScope() {
705
706                return scope;
707        }
708        
709        
710        /**
711         * Gets the client notification token, required for the CIBA ping and
712         * push token delivery modes. Corresponds to the
713         * {@code client_notification_token} parameter.
714         *
715         * @return The client notification token, {@code null} if not
716         *         specified.
717         */
718        public BearerAccessToken getClientNotificationToken() {
719                
720                return clientNotificationToken;
721        }
722        
723        
724        /**
725         * Gets the requested Authentication Context Class Reference values.
726         * Corresponds to the optional {@code acr_values} parameter.
727         *
728         * @return The requested ACR values, {@code null} if not specified.
729         */
730        public List<ACR> getACRValues() {
731                
732                return acrValues;
733        }
734        
735        
736        /**
737         * Gets the login hint token string, containing information
738         * identifying the end-user for whom authentication is being requested.
739         * Corresponds to the {@code login_hint_token} parameter.
740         *
741         * @return The login hint token string, {@code null} if not
742         *         specified.
743         */
744        public String getLoginHintTokenString() {
745                
746                return loginHintTokenString;
747        }
748        
749        
750        /**
751         * Gets the ID Token hint, passed as a hint to identify the end-user
752         * for whom authentication is being requested. Corresponds to the
753         * {@code id_token_hint} parameter.
754         *
755         * @return The ID Token hint, {@code null} if not specified.
756         */
757        public JWT getIDTokenHint() {
758                
759                return idTokenHint;
760        }
761        
762        
763        /**
764         * Gets the login hint (email address, phone number, etc), about the
765         * end-user for whom authentication is being requested. Corresponds to
766         * the {@code login_hint} parameter.
767         *
768         * @return The login hint, {@code null} if not specified.
769         */
770        public String getLoginHint() {
771                
772                return loginHint;
773        }
774        
775        
776        /**
777         * Gets the human readable binding message for the display at the
778         * consumption and authentication devices. Corresponds to the
779         * {@code binding_message} parameter.
780         *
781         * @return The binding message, {@code null} if not specified.
782         */
783        public String getBindingMessage() {
784                
785                return bindingMessage;
786        }
787        
788        
789        /**
790         * Gets the user secret code (password, PIN, etc) to authorise the CIBA
791         * request with the authentication device. Corresponds to the
792         * {@code user_code} parameter.
793         *
794         * @return The user code, {@code null} if not specified.
795         */
796        public Secret getUserCode() {
797                
798                return userCode;
799        }
800        
801        
802        /**
803         * Gets the requested expiration for the {@code auth_req_id}.
804         * Corresponds to the {@code requested_expiry} parameter.
805         *
806         * @return The required expiry (as positive integer), {@code null} if
807         *         not specified.
808         */
809        public Integer getRequestedExpiry() {
810                
811                return requestedЕxpiry;
812        }
813        
814        
815        /**
816         * Returns the additional custom parameters.
817         *
818         * @return The additional custom parameters as a unmodifiable map,
819         *         empty map if none.
820         */
821        public Map<String, List<String>> getCustomParameters() {
822                
823                return customParams;
824        }
825        
826        
827        /**
828         * Returns the specified custom parameter.
829         *
830         * @param name The parameter name. Must not be {@code null}.
831         *
832         * @return The parameter value(s), {@code null} if not specified.
833         */
834        public List<String> getCustomParameter(final String name) {
835                
836                return customParams.get(name);
837        }
838        
839        
840        /**
841         * Returns {@code true} if this request is signed.
842         *
843         * @return {@code true} for a signed request, {@code false} for a plain
844         *         request.
845         */
846        public boolean isSigned() {
847                
848                return signedRequest != null;
849        }
850        
851        
852        /**
853         * Returns the JWT for a signed request.
854         *
855         * @return The request JWT.
856         */
857        public SignedJWT getRequestJWT() {
858                
859                return signedRequest;
860        }
861        
862        
863        /**
864         * Returns the for parameters for this CIBA request. Parameters which
865         * are part of the client authentication are not included.
866         *
867         * @return The parameters.
868         */
869        public Map<String, List<String>> toParameters() {
870                
871                // Put custom params first, so they may be overwritten by std params
872                Map<String, List<String>> params = new LinkedHashMap<>(getCustomParameters());
873                
874                if (isSigned()) {
875                        params.put("request", Collections.singletonList(signedRequest.serialize()));
876                        return params;
877                }
878                
879                params.put("scope", Collections.singletonList(getScope().toString()));
880                
881                if (getClientNotificationToken() != null) {
882                        params.put("client_notification_token", Collections.singletonList(getClientNotificationToken().getValue()));
883                }
884                if (getACRValues() != null) {
885                        params.put("acr_values", Identifier.toStringList(getACRValues()));
886                }
887                if (getLoginHintTokenString() != null) {
888                        params.put("login_hint_token", Collections.singletonList(getLoginHintTokenString()));
889                }
890                if (getIDTokenHint() != null) {
891                        params.put("id_token_hint", Collections.singletonList(getIDTokenHint().serialize()));
892                }
893                if (getLoginHint() != null) {
894                        params.put("login_hint", Collections.singletonList(getLoginHint()));
895                }
896                if (getBindingMessage() != null) {
897                        params.put("binding_message", Collections.singletonList(getBindingMessage()));
898                }
899                if (getUserCode() != null) {
900                        params.put("user_code", Collections.singletonList(getUserCode().getValue()));
901                }
902                if (getRequestedExpiry() != null) {
903                        params.put("requested_expiry", Collections.singletonList(getRequestedExpiry().toString()));
904                }
905                
906                return params;
907        }
908        
909        
910        /**
911         * Returns the parameters for this CIBA request as a JSON Web Token
912         * (JWT) claims set. Intended for creating a signed CIBA request.
913         *
914         * @return The parameters as JWT claim set.
915         */
916        public JWTClaimsSet toJWTClaimsSet() {
917                
918                if (isSigned()) {
919                        throw new IllegalStateException();
920                }
921                
922                return JWTClaimsSetUtils.toJWTClaimsSet(toParameters());
923        }
924        
925        
926        /**
927         * Returns the matching HTTP request.
928         *
929         * @return The HTTP request.
930         */
931        @Override
932        public HTTPRequest toHTTPRequest() {
933
934                if (getEndpointURI() == null)
935                        throw new SerializeException("The endpoint URI is not specified");
936
937                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getEndpointURI());
938                httpRequest.setEntityContentType(ContentType.APPLICATION_URLENCODED);
939
940                getClientAuthentication().applyTo(httpRequest);
941
942                Map<String, List<String>> params = httpRequest.getQueryParameters();
943                params.putAll(toParameters());
944                httpRequest.setQuery(URLUtils.serializeParameters(params));
945                
946                return httpRequest;
947        }
948
949        
950        /**
951         * Parses a CIBA request from the specified HTTP request.
952         *
953         * @param httpRequest The HTTP request. Must not be {@code null}.
954         *
955         * @return The CIBA request.
956         *
957         * @throws ParseException If parsing failed.
958         */
959        public static CIBARequest parse(final HTTPRequest httpRequest) throws ParseException {
960
961                // Only HTTP POST accepted
962                URI uri = httpRequest.getURI();
963                httpRequest.ensureMethod(HTTPRequest.Method.POST);
964                httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED);
965                
966                ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest);
967                
968                if (clientAuth == null) {
969                        throw new ParseException("Missing required client authentication");
970                }
971                
972                Map<String, List<String>> params = httpRequest.getQueryParameters();
973                
974                String v;
975                
976                if (params.containsKey("request")) {
977                        // Signed request
978                        v = MultivaluedMapUtils.getFirstValue(params, "request");
979                        
980                        if (StringUtils.isBlank(v)) {
981                                throw new ParseException("Empty request parameter");
982                        }
983                        
984                        SignedJWT signedRequest;
985                        try {
986                                signedRequest = SignedJWT.parse(v);
987                        } catch (java.text.ParseException e) {
988                                throw new ParseException("Invalid request JWT: " + e.getMessage(), e);
989                        }
990                        
991                        try {
992                                return new CIBARequest(uri, clientAuth, signedRequest);
993                        } catch (IllegalArgumentException e) {
994                                throw new ParseException(e.getMessage(), e);
995                        }
996                }
997                
998                
999                // Plain request
1000                
1001                // Parse required scope
1002                v = MultivaluedMapUtils.getFirstValue(params, "scope");
1003                Scope scope = Scope.parse(v);
1004
1005                v = MultivaluedMapUtils.getFirstValue(params, "client_notification_token");
1006                BearerAccessToken clientNotificationToken = null;
1007                if (StringUtils.isNotBlank(v)) {
1008                        clientNotificationToken = new BearerAccessToken(v);
1009                }
1010                
1011                v = MultivaluedMapUtils.getFirstValue(params, "acr_values");
1012                List<ACR> acrValues = null;
1013                if (StringUtils.isNotBlank(v)) {
1014                        acrValues = new LinkedList<>();
1015                        StringTokenizer st = new StringTokenizer(v, " ");
1016                        while (st.hasMoreTokens()) {
1017                                acrValues.add(new ACR(st.nextToken()));
1018                        }
1019                }
1020                
1021                String loginHintTokenString = MultivaluedMapUtils.getFirstValue(params, "login_hint_token");
1022                
1023                v = MultivaluedMapUtils.getFirstValue(params, "id_token_hint");
1024                JWT idTokenHint = null;
1025                if (StringUtils.isNotBlank(v)) {
1026                        try {
1027                                idTokenHint = JWTParser.parse(v);
1028                        } catch (java.text.ParseException e) {
1029                                throw new ParseException("Invalid id_token_hint parameter: " + e.getMessage());
1030                        }
1031                }
1032                
1033                String loginHint = MultivaluedMapUtils.getFirstValue(params, "login_hint");
1034                
1035                v = MultivaluedMapUtils.getFirstValue(params, "user_code");
1036                
1037                Secret userCode = null;
1038                if (StringUtils.isNotBlank(v)) {
1039                        userCode = new Secret(v);
1040                }
1041                
1042                String bindingMessage = MultivaluedMapUtils.getFirstValue(params, "binding_message");
1043                
1044                v = MultivaluedMapUtils.getFirstValue(params, "requested_expiry");
1045                
1046                Integer requestedExpiry = null;
1047
1048                if (StringUtils.isNotBlank(v)) {
1049                        try {
1050                                requestedExpiry = Integer.valueOf(v);
1051                        } catch (NumberFormatException e) {
1052                                throw new ParseException("The requested_expiry parameter must be an integer");
1053                        }
1054                }
1055                
1056                // Parse additional custom parameters
1057                Map<String,List<String>> customParams = null;
1058                
1059                for (Map.Entry<String,List<String>> p: params.entrySet()) {
1060                        
1061                        if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) {
1062                                // We have a custom parameter
1063                                if (customParams == null) {
1064                                        customParams = new HashMap<>();
1065                                }
1066                                customParams.put(p.getKey(), p.getValue());
1067                        }
1068                }
1069
1070                try {
1071                        return new CIBARequest(
1072                                uri, clientAuth,
1073                                scope, clientNotificationToken, acrValues, loginHintTokenString, idTokenHint, loginHint, bindingMessage, userCode, requestedExpiry,
1074                                customParams);
1075                } catch (IllegalArgumentException e) {
1076                        throw new ParseException(e.getMessage());
1077                }
1078        }
1079}