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 com.nimbusds.common.contenttype.ContentType;
022import com.nimbusds.jose.JWSObject;
023import com.nimbusds.jwt.JWT;
024import com.nimbusds.jwt.JWTClaimsSet;
025import com.nimbusds.jwt.JWTParser;
026import com.nimbusds.jwt.SignedJWT;
027import com.nimbusds.langtag.LangTag;
028import com.nimbusds.langtag.LangTagException;
029import com.nimbusds.langtag.LangTagUtils;
030import com.nimbusds.oauth2.sdk.AbstractAuthenticatedRequest;
031import com.nimbusds.oauth2.sdk.ParseException;
032import com.nimbusds.oauth2.sdk.Scope;
033import com.nimbusds.oauth2.sdk.SerializeException;
034import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
035import com.nimbusds.oauth2.sdk.auth.Secret;
036import com.nimbusds.oauth2.sdk.http.HTTPRequest;
037import com.nimbusds.oauth2.sdk.id.Identifier;
038import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
039import com.nimbusds.oauth2.sdk.util.*;
040import com.nimbusds.openid.connect.sdk.OIDCClaimsRequest;
041import com.nimbusds.openid.connect.sdk.claims.ACR;
042import net.jcip.annotations.Immutable;
043
044import java.net.URI;
045import java.util.*;
046
047
048/**
049 * <p>CIBA request to an OpenID provider / OAuth 2.0 authorisation server
050 * backend authentication endpoint. Supports plan as well as signed (JWT)
051 * requests.
052 *
053 * <p>Example HTTP request:
054 * 
055 * <pre>
056 * POST /bc-authorize HTTP/1.1
057 * Host: server.example.com
058 * Content-Type: application/x-www-form-urlencoded
059 *
060 * scope=openid%20email%20example-scope&amp;
061 * client_notification_token=8d67dc78-7faa-4d41-aabd-67707b374255&amp;
062 * binding_message=W4SCT&amp;
063 * login_hint_token=eyJraWQiOiJsdGFjZXNidyIsImFsZyI6IkVTMjU2In0.eyJ
064 * zdWJfaWQiOnsic3ViamVjdF90eXBlIjoicGhvbmUiLCJwaG9uZSI6IisxMzMwMjg
065 * xODAwNCJ9fQ.Kk8jcUbHjJAQkRSHyDuFQr3NMEOSJEZc85VfER74tX6J9CuUllr8
066 * 9WKUHUR7MA0-mWlptMRRhdgW1ZDt7g1uwQ&amp;
067 * client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3A
068 * client-assertion-type%3Ajwt-bearer&amp;
069 * client_assertion=eyJraWQiOiJsdGFjZXNidyIsImFsZyI6IkVTMjU2In0.eyJ
070 * pc3MiOiJzNkJoZFJrcXQzIiwic3ViIjoiczZCaGRSa3F0MyIsImF1ZCI6Imh0dHB
071 * zOi8vc2VydmVyLmV4YW1wbGUuY29tIiwianRpIjoiYmRjLVhzX3NmLTNZTW80RlN
072 * 6SUoyUSIsImlhdCI6MTUzNzgxOTQ4NiwiZXhwIjoxNTM3ODE5Nzc3fQ.Ybr8mg_3
073 * E2OptOSsA8rnelYO_y1L-yFaF_j1iemM3ntB61_GN3APe5cl_-5a6cvGlP154XAK
074 * 7fL-GaZSdnd9kg
075 * </pre>
076 *
077 * <p>Related specifications:
078 *
079 * <ul>
080 *      <li>OpenID Connect CIBA Flow - Core 1.0, section 7.1.
081 * </ul>
082 */
083@Immutable
084public class CIBARequest extends AbstractAuthenticatedRequest {
085        
086        
087        /**
088         * The maximum allowed length of a client notification token.
089         */
090        public static final int CLIENT_NOTIFICATION_TOKEN_MAX_LENGTH = 1024;
091        
092
093        /**
094         * The registered parameter names.
095         */
096        private static final Set<String> REGISTERED_PARAMETER_NAMES;
097
098        static {
099                Set<String> p = new HashSet<>();
100
101                // Plain
102                p.add("scope");
103                p.add("client_notification_token");
104                p.add("acr_values");
105                p.add("login_hint_token");
106                p.add("id_token_hint");
107                p.add("login_hint");
108                p.add("binding_message");
109                p.add("user_code");
110                p.add("requested_expiry");
111                p.add("claims");
112                p.add("claims_locales");
113                p.add("purpose");
114                p.add("resource");
115                
116                // Signed JWT
117                p.add("request");
118
119                REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p);
120        }
121
122        
123        /**
124         * The scope (required), must contain {@code openid}.
125         */
126        private final Scope scope;
127
128        
129        /**
130         * The client notification token, required for the CIBA ping and push
131         * token delivery modes.
132         */
133        private final BearerAccessToken clientNotificationToken;
134        
135        
136        /**
137         * Requested Authentication Context Class Reference values (optional).
138         */
139        private final List<ACR> acrValues;
140        
141        
142        /**
143         * A token containing information identifying the end-user for whom
144         * authentication is being requested (optional).
145         */
146        private final String loginHintTokenString;
147        
148        
149        /**
150         * Previously issued ID token passed as a hint to identify the end-user
151         * for whom authentication is being requested (optional).
152         */
153        private final JWT idTokenHint;
154        
155        
156        /**
157         * Login hint (email address, phone number, etc) about the end-user for
158         * whom authentication is being requested (optional).
159         */
160        private final String loginHint;
161        
162        
163        /**
164         * Human-readable binding message for the display at the consumption
165         * and authentication devices (optional).
166         */
167        private final String bindingMessage;
168        
169        
170        /**
171         * User secret code (password, PIN, etc.) to authorise the CIBA request
172         * with the authentication device (optional).
173         */
174        private final Secret userCode;
175        
176        
177        /**
178         * Requested expiration for the {@code auth_req_id} (optional).
179         */
180        private final Integer requestedExpiry;
181        
182        
183        /**
184         * Individual claims to be returned (optional).
185         */
186        private final OIDCClaimsRequest claims;
187        
188        
189        /**
190         * The end-user's preferred languages and scripts for claims being
191         * returned (optional).
192         */
193        private final List<LangTag> claimsLocales;
194        
195        
196        /**
197         * The transaction specific purpose, for use in OpenID Connect Identity
198         * Assurance.
199         */
200        private final String purpose;
201        
202        
203        /**
204         * The resource URI(s) (optional).
205         */
206        private final List<URI> resources;
207        
208        
209        /**
210         * Custom parameters.
211         */
212        private final Map<String,List<String>> customParams;
213        
214        
215        /**
216         * The JWT for a signed request.
217         */
218        private final SignedJWT signedRequest;
219        
220
221        /**
222         * Builder for constructing CIBA requests.
223         */
224        public static class Builder {
225
226                
227                /**
228                 * The endpoint URI (optional).
229                 */
230                private URI uri;
231                
232                
233                /**
234                 * The client authentication (required).
235                 */
236                private final ClientAuthentication clientAuth;
237                
238                
239                /**
240                 * The scope (required).
241                 */
242                private final Scope scope;
243                
244                
245                /**
246                 * The client notification type, required for the CIBA ping and
247                 * push token delivery modes.
248                 */
249                private BearerAccessToken clientNotificationToken;
250                
251                
252                /**
253                 * Requested Authentication Context Class Reference values
254                 * (optional).
255                 */
256                private List<ACR> acrValues;
257                
258                
259                /**
260                 * A token containing information identifying the end-user for
261                 * whom authentication is being requested (optional).
262                 */
263                private String loginHintTokenString;
264                
265                
266                /**
267                 * Previously issued ID token passed as a hint to identify the
268                 * end-user for whom authentication is being requested
269                 * (optional).
270                 */
271                private JWT idTokenHint;
272                
273                
274                /**
275                 * Identity hint (email address, phone number, etc) about the
276                 * end-user for whom authentication is being requested
277                 * (optional).
278                 */
279                private String loginHint;
280                
281                
282                /**
283                 * Human readable binding message for the display at the
284                 * consumption and authentication devices (optional).
285                 */
286                private String bindingMessage;
287                
288                
289                /**
290                 * User secret code (password, PIN, etc) to authorise the CIBA
291                 * request with the authentication device (optional).
292                 */
293                private Secret userCode;
294                
295                
296                /**
297                 * Requested expiration for the {@code auth_req_id} (optional).
298                 */
299                private Integer requestedExpiry;
300                
301                
302                /**
303                 * Individual claims to be returned (optional).
304                 */
305                private OIDCClaimsRequest claims;
306                
307                
308                /**
309                 * The end-user's preferred languages and scripts for claims
310                 * being returned (optional).
311                 */
312                private List<LangTag> claimsLocales;
313                
314                
315                /**
316                 * The transaction specific purpose (optional).
317                 */
318                private String purpose;
319                
320                
321                /**
322                 * The resource URI(s) (optional).
323                 */
324                private List<URI> resources;
325                
326                
327                /**
328                 * Custom parameters.
329                 */
330                private Map<String,List<String>> customParams = new HashMap<>();
331                
332                
333                /**
334                 * The JWT for a signed request.
335                 */
336                private final SignedJWT signedRequest;
337
338                
339                /**
340                 * Creates a new CIBA request builder.
341                 *
342                 * @param clientAuth The client authentication. Must not be
343                 *                   {@code null}.
344                 * @param scope      The requested scope, {@code null} if not
345                 *                   specified.
346                 */
347                public Builder(final ClientAuthentication clientAuth,
348                               final Scope scope) {
349                        
350                        if (clientAuth == null) {
351                                throw new IllegalArgumentException("The client authentication must not be null");
352                        }
353                        this.clientAuth = clientAuth;
354                        
355                        this.scope = scope;
356                        
357                        signedRequest = null;
358                }
359                
360                
361                /**
362                 * Creates a new CIBA signed request builder.
363                 *
364                 * @param clientAuth    The client authentication. Must not be
365                 *                      {@code null}.
366                 * @param signedRequest The signed request JWT. Must not be
367                 *                      {@code null}.
368                 */
369                public Builder(final ClientAuthentication clientAuth,
370                               final SignedJWT signedRequest) {
371                        
372                        if (clientAuth == null) {
373                                throw new IllegalArgumentException("The client authentication must not be null");
374                        }
375                        this.clientAuth = clientAuth;
376                        
377                        if (signedRequest == null) {
378                                throw new IllegalArgumentException("The signed request JWT must not be null");
379                        }
380                        this.signedRequest = signedRequest;
381                        
382                        scope = null;
383                }
384                
385
386                /**
387                 * Creates a new CIBA request builder from the specified
388                 * request.
389                 *
390                 * @param request The CIBA request. Must not be {@code null}.
391                 */
392                public Builder(final CIBARequest request) {
393                        
394                        uri = request.getEndpointURI();
395                        clientAuth = request.getClientAuthentication();
396                        scope = request.getScope();
397                        clientNotificationToken = request.getClientNotificationToken();
398                        acrValues = request.getACRValues();
399                        loginHintTokenString = request.getLoginHintTokenString();
400                        idTokenHint = request.getIDTokenHint();
401                        loginHint = request.getLoginHint();
402                        bindingMessage = request.getBindingMessage();
403                        userCode = request.getUserCode();
404                        requestedExpiry = request.getRequestedExpiry();
405                        claims = request.getOIDCClaims();
406                        claimsLocales = request.getClaimsLocales();
407                        purpose = request.getPurpose();
408                        resources = request.getResources();
409                        customParams = request.getCustomParameters();
410                        signedRequest = request.getRequestJWT();
411                }
412                
413                
414                /**
415                 * Sets the client notification token, required for the CIBA
416                 * ping and push token delivery modes. Corresponds to the
417                 * {@code client_notification_token} parameter.
418                 *
419                 * @param token The client notification token, {@code null} if
420                 *              not specified.
421                 *
422                 * @return This builder.
423                 */
424                public Builder clientNotificationToken(final BearerAccessToken token) {
425                        this.clientNotificationToken = token;
426                        return this;
427                }
428
429                
430                /**
431                 * Sets the requested Authentication Context Class Reference
432                 * values. Corresponds to the optional {@code acr_values}
433                 * parameter.
434                 *
435                 * @param acrValues The requested ACR values, {@code null} if
436                 *                  not specified.
437                 *
438                 * @return This builder.
439                 */
440                public Builder acrValues(final List<ACR> acrValues) {
441                        this.acrValues = acrValues;
442                        return this;
443                }
444                
445                
446                /**
447                 * Sets the login hint token string, containing information
448                 * identifying the end-user for whom authentication is being requested.
449                 * Corresponds to the {@code login_hint_token} parameter.
450                 *
451                 * @param loginHintTokenString The login hint token string,
452                 *                             {@code null} if not specified.
453                 *
454                 * @return This builder.
455                 */
456                public Builder loginHintTokenString(final String loginHintTokenString) {
457                        this.loginHintTokenString = loginHintTokenString;
458                        return this;
459                }
460                
461                
462                /**
463                 * Sets the ID Token hint, passed as a hint to identify the
464                 * end-user for whom authentication is being requested.
465                 * Corresponds to the {@code id_token_hint} parameter.
466                 *
467                 * @param idTokenHint The ID Token hint, {@code null} if not
468                 *                    specified.
469                 *
470                 * @return This builder.
471                 */
472                public Builder idTokenHint(final JWT idTokenHint) {
473                        this.idTokenHint = idTokenHint;
474                        return this;
475                }
476                
477                
478                /**
479                 * Sets the login hint (email address, phone number, etc),
480                 * about the end-user for whom authentication is being
481                 * requested. Corresponds to the {@code login_hint} parameter.
482                 *
483                 * @param loginHint The login hint, {@code null} if not
484                 *                  specified.
485                 *
486                 * @return This builder.
487                 */
488                public Builder loginHint(final String loginHint) {
489                        this.loginHint = loginHint;
490                        return this;
491                }
492                
493                
494                /**
495                 * Sets the human readable binding message for the display at
496                 * the consumption and authentication devices. Corresponds to
497                 * the {@code binding_message} parameter.
498                 *
499                 * @param bindingMessage The binding message, {@code null} if
500                 *                       not specified.
501                 *
502                 * @return This builder.
503                 */
504                public Builder bindingMessage(final String bindingMessage) {
505                        this.bindingMessage = bindingMessage;
506                        return this;
507                }
508                
509                
510                /**
511                 * Gets the user secret code (password, PIN, etc) to authorise
512                 * the CIBA request with the authentication device. Corresponds
513                 * to the {@code user_code} parameter.
514                 *
515                 * @param userCode The user code, {@code null} if not
516                 *                 specified.
517                 *
518                 * @return This builder.
519                 */
520                public Builder userCode(final Secret userCode) {
521                        this.userCode = userCode;
522                        return this;
523                }
524                
525                
526                /**
527                 * Sets the requested expiration for the {@code auth_req_id}.
528                 * Corresponds to the {@code requested_expiry} parameter.
529                 *
530                 * @param requestedExpiry The required expiry (as positive
531                 *                        integer), {@code null} if not
532                 *                        specified.
533                 *
534                 * @return This builder.
535                 */
536                public Builder requestedExpiry(final Integer requestedExpiry) {
537                        this.requestedExpiry = requestedExpiry;
538                        return this;
539                }
540                
541                
542                /**
543                 * Sets the individual OpenID claims to be returned.
544                 * Corresponds to the optional {@code claims} parameter.
545                 *
546                 * @param claims The individual OpenID claims to be returned,
547                 *               {@code null} if not specified.
548                 *
549                 * @return This builder.
550                 */
551                public Builder claims(final OIDCClaimsRequest claims) {
552                        
553                        this.claims = claims;
554                        return this;
555                }
556                
557                
558                /**
559                 * Sets the end-user's preferred languages and scripts for the
560                 * claims being returned, ordered by preference. Corresponds to
561                 * the optional {@code claims_locales} parameter.
562                 *
563                 * @param claimsLocales The preferred claims locales,
564                 *                      {@code null} if not specified.
565                 *
566                 * @return This builder.
567                 */
568                public Builder claimsLocales(final List<LangTag> claimsLocales) {
569                        
570                        this.claimsLocales = claimsLocales;
571                        return this;
572                }
573                
574                
575                /**
576                 * Sets the transaction specific purpose. Corresponds to the
577                 * optional {@code purpose} parameter.
578                 *
579                 * @param purpose The purpose, {@code null} if not specified.
580                 *
581                 * @return This builder.
582                 */
583                public Builder purpose(final String purpose) {
584                        
585                        this.purpose = purpose;
586                        return this;
587                }
588                
589                
590                /**
591                 * Sets the resource server URI.
592                 *
593                 * @param resource The resource URI, {@code null} if not
594                 *                 specified.
595                 *
596                 * @return This builder.
597                 */
598                public Builder resource(final URI resource) {
599                        if (resource != null) {
600                                this.resources = Collections.singletonList(resource);
601                        } else {
602                                this.resources = null;
603                        }
604                        return this;
605                }
606                
607                
608                /**
609                 * Sets the resource server URI(s).
610                 *
611                 * @param resources The resource URI(s), {@code null} if not
612                 *                  specified.
613                 *
614                 * @return This builder.
615                 */
616                public Builder resources(final URI ... resources) {
617                        if (resources != null) {
618                                this.resources = Arrays.asList(resources);
619                        } else {
620                                this.resources = null;
621                        }
622                        return this;
623                }
624                
625                
626                /**
627                 * Sets a custom parameter.
628                 *
629                 * @param name   The parameter name. Must not be {@code null}.
630                 * @param values The parameter values, {@code null} if not
631                 *               specified.
632                 *
633                 * @return This builder.
634                 */
635                public Builder customParameter(final String name, final String ... values) {
636                        
637                        if (values == null || values.length == 0) {
638                                customParams.remove(name);
639                        } else {
640                                customParams.put(name, Arrays.asList(values));
641                        }
642                        
643                        return this;
644                }
645                
646                
647                /**
648                 * Sets the URI of the endpoint (HTTP or HTTPS) for which the
649                 * request is intended.
650                 *
651                 * @param uri The endpoint URI, {@code null} if not specified.
652                 *
653                 * @return This builder.
654                 */
655                public Builder endpointURI(final URI uri) {
656                        
657                        this.uri = uri;
658                        return this;
659                }
660                
661                
662                /**
663                 * Builds a new CIBA request.
664                 *
665                 * @return The CIBA request.
666                 */
667                public CIBARequest build() {
668                        
669                        try {
670                                if (signedRequest != null) {
671                                        return new CIBARequest(
672                                                uri,
673                                                clientAuth,
674                                                signedRequest
675                                        );
676                                }
677                                
678                                // Plain request
679                                return new CIBARequest(
680                                        uri,
681                                        clientAuth,
682                                        scope,
683                                        clientNotificationToken,
684                                        acrValues,
685                                        loginHintTokenString,
686                                        idTokenHint,
687                                        loginHint,
688                                        bindingMessage,
689                                        userCode,
690                                        requestedExpiry,
691                                        claims,
692                                        claimsLocales,
693                                        purpose,
694                                        resources,
695                                        customParams
696                                );
697                        } catch (IllegalArgumentException e) {
698                                throw new IllegalArgumentException(e.getMessage(), e);
699                        }
700                }
701        }
702        
703        
704        /**
705         * Creates a new CIBA request.
706         *
707         * @param uri                     The endpoint URI, {@code null} if not
708         *                                specified.
709         * @param clientAuth              The client authentication. Must not
710         *                                be {@code null}.
711         * @param scope                   The requested scope. Must not be
712         *                                empty or {@code null}.
713         * @param clientNotificationToken The client notification token,
714         *                                {@code null} if not specified.
715         * @param acrValues               The requested ACR values,
716         *                                {@code null} if not specified.
717         * @param loginHintTokenString    The login hint token string,
718         *                                {@code null} if not specified.
719         * @param idTokenHint             The ID Token hint, {@code null} if
720         *                                not specified.
721         * @param loginHint               The login hint, {@code null} if not
722         *                                specified.
723         * @param bindingMessage          The binding message, {@code null} if
724         *                                not specified.
725         * @param userCode                The user code, {@code null} if not
726         *                                specified.
727         * @param requestedExpiry         The required expiry (as positive
728         *                                integer), {@code null} if not
729         *                                specified.
730         * @param customParams            Custom parameters, empty or
731         *                                {@code null} if not specified.
732         */
733        @Deprecated
734        public CIBARequest(final URI uri,
735                           final ClientAuthentication clientAuth,
736                           final Scope scope,
737                           final BearerAccessToken clientNotificationToken,
738                           final List<ACR> acrValues,
739                           final String loginHintTokenString,
740                           final JWT idTokenHint,
741                           final String loginHint,
742                           final String bindingMessage,
743                           final Secret userCode,
744                           final Integer requestedExpiry,
745                           final Map<String, List<String>> customParams) {
746                
747                this(uri, clientAuth,
748                        scope, clientNotificationToken, acrValues,
749                        loginHintTokenString, idTokenHint, loginHint,
750                        bindingMessage, userCode, requestedExpiry,
751                        null, customParams);
752        }
753        
754        
755        /**
756         * Creates a new CIBA request.
757         *
758         * @param uri                     The endpoint URI, {@code null} if not
759         *                                specified.
760         * @param clientAuth              The client authentication. Must not
761         *                                be {@code null}.
762         * @param scope                   The requested scope. Must not be
763         *                                empty or {@code null}.
764         * @param clientNotificationToken The client notification token,
765         *                                {@code null} if not specified.
766         * @param acrValues               The requested ACR values,
767         *                                {@code null} if not specified.
768         * @param loginHintTokenString    The login hint token string,
769         *                                {@code null} if not specified.
770         * @param idTokenHint             The ID Token hint, {@code null} if
771         *                                not specified.
772         * @param loginHint               The login hint, {@code null} if not
773         *                                specified.
774         * @param bindingMessage          The binding message, {@code null} if
775         *                                not specified.
776         * @param userCode                The user code, {@code null} if not
777         *                                specified.
778         * @param requestedExpiry         The required expiry (as positive
779         *                                integer), {@code null} if not
780         *                                specified.
781         * @param claims                  The individual claims to be returned,
782         *                                {@code null} if not specified.
783         * @param customParams            Custom parameters, empty or
784         *                                {@code null} if not specified.
785         */
786        @Deprecated
787        public CIBARequest(final URI uri,
788                           final ClientAuthentication clientAuth,
789                           final Scope scope,
790                           final BearerAccessToken clientNotificationToken,
791                           final List<ACR> acrValues,
792                           final String loginHintTokenString,
793                           final JWT idTokenHint,
794                           final String loginHint,
795                           final String bindingMessage,
796                           final Secret userCode,
797                           final Integer requestedExpiry,
798                           final OIDCClaimsRequest claims,
799                           final Map<String, List<String>> customParams) {
800                
801                this(uri, clientAuth,
802                        scope, clientNotificationToken, acrValues,
803                        loginHintTokenString, idTokenHint, loginHint,
804                        bindingMessage, userCode, requestedExpiry,
805                        claims, null, null,
806                        null,
807                        customParams);
808        }
809        
810        
811        /**
812         * Creates a new CIBA request.
813         *
814         * @param uri                     The endpoint URI, {@code null} if not
815         *                                specified.
816         * @param clientAuth              The client authentication. Must not
817         *                                be {@code null}.
818         * @param scope                   The requested scope. Must not be
819         *                                empty or {@code null}.
820         * @param clientNotificationToken The client notification token,
821         *                                {@code null} if not specified.
822         * @param acrValues               The requested ACR values,
823         *                                {@code null} if not specified.
824         * @param loginHintTokenString    The login hint token string,
825         *                                {@code null} if not specified.
826         * @param idTokenHint             The ID Token hint, {@code null} if
827         *                                not specified.
828         * @param loginHint               The login hint, {@code null} if not
829         *                                specified.
830         * @param bindingMessage          The binding message, {@code null} if
831         *                                not specified.
832         * @param userCode                The user code, {@code null} if not
833         *                                specified.
834         * @param requestedExpiry         The required expiry (as positive
835         *                                integer), {@code null} if not
836         *                                specified.
837         * @param claims                  The individual claims to be
838         *                                returned, {@code null} if not
839         *                                specified.
840         * @param claimsLocales           The preferred languages and scripts
841         *                                for claims being returned,
842         *                                {@code null} if not specified.
843         * @param purpose                 The transaction specific purpose,
844         *                                {@code null} if not specified.
845         * @param resources               The resource URI(s), {@code null} if
846         *                                not specified.
847         * @param customParams            Custom parameters, empty or
848         *                                {@code null} if not specified.
849         */
850        public CIBARequest(final URI uri,
851                           final ClientAuthentication clientAuth,
852                           final Scope scope,
853                           final BearerAccessToken clientNotificationToken,
854                           final List<ACR> acrValues,
855                           final String loginHintTokenString,
856                           final JWT idTokenHint,
857                           final String loginHint,
858                           final String bindingMessage,
859                           final Secret userCode,
860                           final Integer requestedExpiry,
861                           final OIDCClaimsRequest claims,
862                           final List<LangTag> claimsLocales,
863                           final String purpose,
864                           final List<URI> resources,
865                           final Map<String, List<String>> customParams) {
866                
867                super(uri, clientAuth);
868                
869                this.scope = scope;
870                
871                if (clientNotificationToken != null && clientNotificationToken.getValue().length() > CLIENT_NOTIFICATION_TOKEN_MAX_LENGTH) {
872                        throw new IllegalArgumentException("The client notification token must not exceed " + CLIENT_NOTIFICATION_TOKEN_MAX_LENGTH + " chars");
873                }
874                this.clientNotificationToken = clientNotificationToken;
875                
876                this.acrValues = acrValues;
877                
878                // https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0-03.html#rfc.section.7.1
879                // As in the CIBA flow the OP does not have an interaction with
880                // the end-user through the consumption device, it is REQUIRED
881                // that the Client provides one (and only one) of the hints
882                // specified above in the authentication request, that is
883                // "login_hint_token", "id_token_hint" or "login_hint".
884                int numHints = 0;
885                
886                if (loginHintTokenString != null) numHints++;
887                this.loginHintTokenString = loginHintTokenString;
888                
889                if (idTokenHint != null) numHints++;
890                this.idTokenHint = idTokenHint;
891                
892                if (loginHint != null) numHints++;
893                this.loginHint = loginHint;
894                
895                if (numHints != 1) {
896                        throw new IllegalArgumentException("One user identity hist must be provided (login_hint_token, id_token_hint or login_hint)");
897                }
898                
899                this.bindingMessage = bindingMessage;
900                
901                this.userCode = userCode;
902                
903                if (requestedExpiry != null && requestedExpiry < 1) {
904                        throw new IllegalArgumentException("The requested expiry must be a positive integer");
905                }
906                this.requestedExpiry = requestedExpiry;
907                
908                this.claims = claims;
909                
910                if (claimsLocales != null) {
911                        this.claimsLocales = Collections.unmodifiableList(claimsLocales);
912                } else {
913                        this.claimsLocales = null;
914                }
915                
916                this.purpose = purpose;
917                
918                this.resources = ResourceUtils.ensureLegalResourceURIs(resources);
919                
920                this.customParams = customParams != null ? customParams : Collections.<String, List<String>>emptyMap();
921                
922                signedRequest = null;
923        }
924        
925        
926        /**
927         * Creates a new CIBA signed request.
928         *
929         * @param uri           The endpoint URI, {@code null} if not
930         *                      specified.
931         * @param clientAuth    The client authentication. Must not be
932         *                      {@code null}.
933         * @param signedRequest The signed request JWT. Must not be
934         *                      {@code null}.
935         */
936        public CIBARequest(final URI uri,
937                           final ClientAuthentication clientAuth,
938                           final SignedJWT signedRequest) {
939                
940                super(uri, clientAuth);
941                
942                if (signedRequest == null) {
943                        throw new IllegalArgumentException("The signed request JWT must not be null");
944                }
945                if (JWSObject.State.UNSIGNED.equals(signedRequest.getState())) {
946                        throw new IllegalArgumentException("The request JWT must be in a signed state");
947                }
948                this.signedRequest = signedRequest;
949                
950                scope = null;
951                clientNotificationToken = null;
952                acrValues = null;
953                loginHintTokenString = null;
954                idTokenHint = null;
955                loginHint = null;
956                bindingMessage = null;
957                userCode = null;
958                requestedExpiry = null;
959                claims = null;
960                claimsLocales = null;
961                purpose = null;
962                resources = null;
963                customParams = Collections.emptyMap();
964        }
965
966        
967        /**
968         * Returns the registered (standard) CIBA request parameter names.
969         *
970         * @return The registered CIBA request parameter names, as a
971         *         unmodifiable set.
972         */
973        public static Set<String> getRegisteredParameterNames() {
974
975                return REGISTERED_PARAMETER_NAMES;
976        }
977
978        
979        /**
980         * Returns the scope. Corresponds to the optional {@code scope}
981         * parameter.
982         *
983         * @return The scope, {@code null} if not specified.
984         */
985        public Scope getScope() {
986
987                return scope;
988        }
989        
990        
991        /**
992         * Returns the client notification token, required for the CIBA ping
993         * and push token delivery modes. Corresponds to the
994         * {@code client_notification_token} parameter.
995         *
996         * @return The client notification token, {@code null} if not
997         *         specified.
998         */
999        public BearerAccessToken getClientNotificationToken() {
1000                
1001                return clientNotificationToken;
1002        }
1003        
1004        
1005        /**
1006         * Returns the requested Authentication Context Class Reference values.
1007         * Corresponds to the optional {@code acr_values} parameter.
1008         *
1009         * @return The requested ACR values, {@code null} if not specified.
1010         */
1011        public List<ACR> getACRValues() {
1012                
1013                return acrValues;
1014        }
1015        
1016        
1017        /**
1018         * Returns the hint type.
1019         *
1020         * @return The hint type.
1021         */
1022        public CIBAHintType getHintType() {
1023                
1024                if (getLoginHintTokenString() != null) {
1025                        return CIBAHintType.LOGIN_HINT_TOKEN;
1026                } else if (getIDTokenHint() != null) {
1027                        return CIBAHintType.ID_TOKEN_HINT;
1028                } else {
1029                        return CIBAHintType.LOGIN_HINT;
1030                }
1031        }
1032        
1033        
1034        /**
1035         * Returns the login hint token string, containing information
1036         * identifying the end-user for whom authentication is being requested.
1037         * Corresponds to the {@code login_hint_token} parameter.
1038         *
1039         * @return The login hint token string, {@code null} if not
1040         *         specified.
1041         */
1042        public String getLoginHintTokenString() {
1043                
1044                return loginHintTokenString;
1045        }
1046        
1047        
1048        /**
1049         * Returns the ID Token hint, passed as a hint to identify the end-user
1050         * for whom authentication is being requested. Corresponds to the
1051         * {@code id_token_hint} parameter.
1052         *
1053         * @return The ID Token hint, {@code null} if not specified.
1054         */
1055        public JWT getIDTokenHint() {
1056                
1057                return idTokenHint;
1058        }
1059        
1060        
1061        /**
1062         * Returns the login hint (email address, phone number, etc), about the
1063         * end-user for whom authentication is being requested. Corresponds to
1064         * the {@code login_hint} parameter.
1065         *
1066         * @return The login hint, {@code null} if not specified.
1067         */
1068        public String getLoginHint() {
1069                
1070                return loginHint;
1071        }
1072        
1073        
1074        /**
1075         * Returns the human-readable binding message for the display at the
1076         * consumption and authentication devices. Corresponds to the
1077         * {@code binding_message} parameter.
1078         *
1079         * @return The binding message, {@code null} if not specified.
1080         */
1081        public String getBindingMessage() {
1082                
1083                return bindingMessage;
1084        }
1085        
1086        
1087        /**
1088         * Returns the user secret code (password, PIN, etc) to authorise the
1089         * CIBA request with the authentication device. Corresponds to the
1090         * {@code user_code} parameter.
1091         *
1092         * @return The user code, {@code null} if not specified.
1093         */
1094        public Secret getUserCode() {
1095                
1096                return userCode;
1097        }
1098        
1099        
1100        /**
1101         * Returns the requested expiration for the {@code auth_req_id}.
1102         * Corresponds to the {@code requested_expiry} parameter.
1103         *
1104         * @return The required expiry (as positive integer), {@code null} if
1105         *         not specified.
1106         */
1107        public Integer getRequestedExpiry() {
1108                
1109                return requestedExpiry;
1110        }
1111        
1112        
1113        /**
1114         * Returns the individual claims to be returned. Corresponds to the
1115         * optional {@code claims} parameter.
1116         *
1117         * @return The individual claims to be returned, {@code null} if not
1118         *         specified.
1119         */
1120        public OIDCClaimsRequest getOIDCClaims() {
1121                
1122                return claims;
1123        }
1124        
1125        
1126        /**
1127         * Returns the end-user's preferred languages and scripts for the
1128         * claims being returned, ordered by preference. Corresponds to the
1129         * optional {@code claims_locales} parameter.
1130         *
1131         * @return The preferred claims locales, {@code null} if not specified.
1132         */
1133        public List<LangTag> getClaimsLocales() {
1134                
1135                return claimsLocales;
1136        }
1137        
1138        
1139        /**
1140         * Returns the transaction specific purpose. Corresponds to the
1141         * optional {@code purpose} parameter.
1142         *
1143         * @return The purpose, {@code null} if not specified.
1144         */
1145        public String getPurpose() {
1146                
1147                return purpose;
1148        }
1149        
1150        
1151        /**
1152         * Returns the resource server URI.
1153         *
1154         * @return The resource URI(s), {@code null} if not specified.
1155         */
1156        public List<URI> getResources() {
1157                
1158                return resources;
1159        }
1160        
1161        
1162        /**
1163         * Returns the additional custom parameters.
1164         *
1165         * @return The additional custom parameters as a unmodifiable map,
1166         *         empty map if none.
1167         */
1168        public Map<String, List<String>> getCustomParameters() {
1169                
1170                return customParams;
1171        }
1172        
1173        
1174        /**
1175         * Returns the specified custom parameter.
1176         *
1177         * @param name The parameter name. Must not be {@code null}.
1178         *
1179         * @return The parameter value(s), {@code null} if not specified.
1180         */
1181        public List<String> getCustomParameter(final String name) {
1182                
1183                return customParams.get(name);
1184        }
1185        
1186        
1187        /**
1188         * Returns {@code true} if this request is signed.
1189         *
1190         * @return {@code true} for a signed request, {@code false} for a plain
1191         *         request.
1192         */
1193        public boolean isSigned() {
1194                
1195                return signedRequest != null;
1196        }
1197        
1198        
1199        /**
1200         * Returns the JWT for a signed request.
1201         *
1202         * @return The request JWT.
1203         */
1204        public SignedJWT getRequestJWT() {
1205                
1206                return signedRequest;
1207        }
1208        
1209        
1210        /**
1211         * Returns the for parameters for this CIBA request. Parameters which
1212         * are part of the client authentication are not included.
1213         *
1214         * @return The parameters.
1215         */
1216        public Map<String, List<String>> toParameters() {
1217                
1218                // Put custom params first, so they may be overwritten by std params
1219                Map<String, List<String>> params = new LinkedHashMap<>(getCustomParameters());
1220                
1221                if (isSigned()) {
1222                        params.put("request", Collections.singletonList(signedRequest.serialize()));
1223                        return params;
1224                }
1225
1226                if (CollectionUtils.isNotEmpty(getScope())) {
1227                        params.put("scope", Collections.singletonList(getScope().toString()));
1228                }
1229                
1230                if (getClientNotificationToken() != null) {
1231                        params.put("client_notification_token", Collections.singletonList(getClientNotificationToken().getValue()));
1232                }
1233                if (getACRValues() != null) {
1234                        params.put("acr_values", Identifier.toStringList(getACRValues()));
1235                }
1236                if (getLoginHintTokenString() != null) {
1237                        params.put("login_hint_token", Collections.singletonList(getLoginHintTokenString()));
1238                }
1239                if (getIDTokenHint() != null) {
1240                        params.put("id_token_hint", Collections.singletonList(getIDTokenHint().serialize()));
1241                }
1242                if (getLoginHint() != null) {
1243                        params.put("login_hint", Collections.singletonList(getLoginHint()));
1244                }
1245                if (getBindingMessage() != null) {
1246                        params.put("binding_message", Collections.singletonList(getBindingMessage()));
1247                }
1248                if (getUserCode() != null) {
1249                        params.put("user_code", Collections.singletonList(getUserCode().getValue()));
1250                }
1251                if (getRequestedExpiry() != null) {
1252                        params.put("requested_expiry", Collections.singletonList(getRequestedExpiry().toString()));
1253                }
1254                if (getOIDCClaims() != null) {
1255                        params.put("claims", Collections.singletonList(getOIDCClaims().toJSONString()));
1256                }
1257                if (CollectionUtils.isNotEmpty(getClaimsLocales())) {
1258                        params.put("claims_locales", Collections.singletonList(LangTagUtils.concat(getClaimsLocales())));
1259                }
1260                if (getPurpose() != null) {
1261                        params.put("purpose", Collections.singletonList(purpose));
1262                }
1263                if (CollectionUtils.isNotEmpty(getResources())) {
1264                        params.put("resource", URIUtils.toStringList(getResources(), true));
1265                }
1266                
1267                return params;
1268        }
1269        
1270        
1271        /**
1272         * Returns the parameters for this CIBA request as a JSON Web Token
1273         * (JWT) claims set. Intended for creating a signed CIBA request.
1274         *
1275         * @return The parameters as JWT claim set.
1276         */
1277        public JWTClaimsSet toJWTClaimsSet() {
1278                
1279                if (isSigned()) {
1280                        throw new IllegalStateException();
1281                }
1282                
1283                return JWTClaimsSetUtils.toJWTClaimsSet(toParameters());
1284        }
1285        
1286        
1287        /**
1288         * Returns the matching HTTP request.
1289         *
1290         * @return The HTTP request.
1291         */
1292        @Override
1293        public HTTPRequest toHTTPRequest() {
1294
1295                if (getEndpointURI() == null)
1296                        throw new SerializeException("The endpoint URI is not specified");
1297
1298                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getEndpointURI());
1299                httpRequest.setEntityContentType(ContentType.APPLICATION_URLENCODED);
1300
1301                getClientAuthentication().applyTo(httpRequest);
1302
1303                Map<String, List<String>> params;
1304                try {
1305                        params = new LinkedHashMap<>(httpRequest.getBodyAsFormParameters());
1306                } catch (ParseException e) {
1307                        throw new SerializeException(e.getMessage(), e);
1308                }
1309                params.putAll(toParameters());
1310                httpRequest.setBody(URLUtils.serializeParameters(params));
1311                
1312                return httpRequest;
1313        }
1314
1315        
1316        /**
1317         * Parses a CIBA request from the specified HTTP request.
1318         *
1319         * @param httpRequest The HTTP request. Must not be {@code null}.
1320         *
1321         * @return The CIBA request.
1322         *
1323         * @throws ParseException If parsing failed.
1324         */
1325        public static CIBARequest parse(final HTTPRequest httpRequest) throws ParseException {
1326
1327                // Only HTTP POST accepted
1328                URI uri = httpRequest.getURI();
1329                httpRequest.ensureMethod(HTTPRequest.Method.POST);
1330                httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED);
1331                
1332                ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest);
1333                
1334                if (clientAuth == null) {
1335                        throw new ParseException("Missing required client authentication");
1336                }
1337                
1338                Map<String, List<String>> params = httpRequest.getQueryParameters();
1339                
1340                String v;
1341                
1342                if (params.containsKey("request")) {
1343                        // Signed request
1344                        v = MultivaluedMapUtils.getFirstValue(params, "request");
1345                        
1346                        if (StringUtils.isBlank(v)) {
1347                                throw new ParseException("Empty request parameter");
1348                        }
1349                        
1350                        SignedJWT signedRequest;
1351                        try {
1352                                signedRequest = SignedJWT.parse(v);
1353                        } catch (java.text.ParseException e) {
1354                                throw new ParseException("Invalid request JWT: " + e.getMessage(), e);
1355                        }
1356                        
1357                        try {
1358                                return new CIBARequest(uri, clientAuth, signedRequest);
1359                        } catch (IllegalArgumentException e) {
1360                                throw new ParseException(e.getMessage(), e);
1361                        }
1362                }
1363                
1364                
1365                // Plain request
1366                
1367                // Parse required scope
1368                v = MultivaluedMapUtils.getFirstValue(params, "scope");
1369                Scope scope = Scope.parse(v);
1370
1371                v = MultivaluedMapUtils.getFirstValue(params, "client_notification_token");
1372                BearerAccessToken clientNotificationToken = null;
1373                if (StringUtils.isNotBlank(v)) {
1374                        clientNotificationToken = new BearerAccessToken(v);
1375                }
1376                
1377                v = MultivaluedMapUtils.getFirstValue(params, "acr_values");
1378                List<ACR> acrValues = null;
1379                if (StringUtils.isNotBlank(v)) {
1380                        acrValues = new LinkedList<>();
1381                        StringTokenizer st = new StringTokenizer(v, " ");
1382                        while (st.hasMoreTokens()) {
1383                                acrValues.add(new ACR(st.nextToken()));
1384                        }
1385                }
1386                
1387                String loginHintTokenString = MultivaluedMapUtils.getFirstValue(params, "login_hint_token");
1388                
1389                v = MultivaluedMapUtils.getFirstValue(params, "id_token_hint");
1390                JWT idTokenHint = null;
1391                if (StringUtils.isNotBlank(v)) {
1392                        try {
1393                                idTokenHint = JWTParser.parse(v);
1394                        } catch (java.text.ParseException e) {
1395                                throw new ParseException("Invalid id_token_hint parameter: " + e.getMessage());
1396                        }
1397                }
1398                
1399                String loginHint = MultivaluedMapUtils.getFirstValue(params, "login_hint");
1400                
1401                v = MultivaluedMapUtils.getFirstValue(params, "user_code");
1402                
1403                Secret userCode = null;
1404                if (StringUtils.isNotBlank(v)) {
1405                        userCode = new Secret(v);
1406                }
1407                
1408                String bindingMessage = MultivaluedMapUtils.getFirstValue(params, "binding_message");
1409                
1410                v = MultivaluedMapUtils.getFirstValue(params, "requested_expiry");
1411                
1412                Integer requestedExpiry = null;
1413                if (StringUtils.isNotBlank(v)) {
1414                        try {
1415                                requestedExpiry = Integer.valueOf(v);
1416                        } catch (NumberFormatException e) {
1417                                throw new ParseException("The requested_expiry parameter must be an integer");
1418                        }
1419                }
1420                
1421                v = MultivaluedMapUtils.getFirstValue(params, "claims");
1422                OIDCClaimsRequest claims = null;
1423                if (StringUtils.isNotBlank(v)) {
1424                        try {
1425                                claims = OIDCClaimsRequest.parse(v);
1426                        } catch (ParseException e) {
1427                                throw new ParseException("Invalid claims parameter: " + e.getMessage(), e);
1428                        }
1429                }
1430                
1431                
1432                List<LangTag> claimsLocales;
1433                try {
1434                        claimsLocales = LangTagUtils.parseLangTagList(MultivaluedMapUtils.getFirstValue(params, "claims_locales"));
1435                } catch (LangTagException e) {
1436                        throw new ParseException("Invalid claims_locales parameter: " + e.getMessage(), e);
1437                }
1438                
1439                String purpose = MultivaluedMapUtils.getFirstValue(params, "purpose");
1440                
1441                List<URI> resources = ResourceUtils.parseResourceURIs(params.get("resource"));
1442                
1443                // Parse additional custom parameters
1444                Map<String,List<String>> customParams = null;
1445                
1446                for (Map.Entry<String,List<String>> p: params.entrySet()) {
1447                        
1448                        if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey()) && ! clientAuth.getFormParameterNames().contains(p.getKey())) {
1449                                // We have a custom parameter
1450                                if (customParams == null) {
1451                                        customParams = new HashMap<>();
1452                                }
1453                                customParams.put(p.getKey(), p.getValue());
1454                        }
1455                }
1456
1457                try {
1458                        return new CIBARequest(
1459                                uri, clientAuth,
1460                                scope, clientNotificationToken, acrValues,
1461                                loginHintTokenString, idTokenHint, loginHint,
1462                                bindingMessage, userCode, requestedExpiry,
1463                                claims, claimsLocales, purpose,
1464                                resources,
1465                                customParams);
1466                } catch (IllegalArgumentException e) {
1467                        throw new ParseException(e.getMessage());
1468                }
1469        }
1470}