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