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 login hint token string, containing information
1020         * identifying the end-user for whom authentication is being requested.
1021         * Corresponds to the {@code login_hint_token} parameter.
1022         *
1023         * @return The login hint token string, {@code null} if not
1024         *         specified.
1025         */
1026        public String getLoginHintTokenString() {
1027                
1028                return loginHintTokenString;
1029        }
1030        
1031        
1032        /**
1033         * Returns the ID Token hint, passed as a hint to identify the end-user
1034         * for whom authentication is being requested. Corresponds to the
1035         * {@code id_token_hint} parameter.
1036         *
1037         * @return The ID Token hint, {@code null} if not specified.
1038         */
1039        public JWT getIDTokenHint() {
1040                
1041                return idTokenHint;
1042        }
1043        
1044        
1045        /**
1046         * Returns the login hint (email address, phone number, etc), about the
1047         * end-user for whom authentication is being requested. Corresponds to
1048         * the {@code login_hint} parameter.
1049         *
1050         * @return The login hint, {@code null} if not specified.
1051         */
1052        public String getLoginHint() {
1053                
1054                return loginHint;
1055        }
1056        
1057        
1058        /**
1059         * Returns the human-readable binding message for the display at the
1060         * consumption and authentication devices. Corresponds to the
1061         * {@code binding_message} parameter.
1062         *
1063         * @return The binding message, {@code null} if not specified.
1064         */
1065        public String getBindingMessage() {
1066                
1067                return bindingMessage;
1068        }
1069        
1070        
1071        /**
1072         * Returns the user secret code (password, PIN, etc) to authorise the
1073         * CIBA request with the authentication device. Corresponds to the
1074         * {@code user_code} parameter.
1075         *
1076         * @return The user code, {@code null} if not specified.
1077         */
1078        public Secret getUserCode() {
1079                
1080                return userCode;
1081        }
1082        
1083        
1084        /**
1085         * Returns the requested expiration for the {@code auth_req_id}.
1086         * Corresponds to the {@code requested_expiry} parameter.
1087         *
1088         * @return The required expiry (as positive integer), {@code null} if
1089         *         not specified.
1090         */
1091        public Integer getRequestedExpiry() {
1092                
1093                return requestedExpiry;
1094        }
1095        
1096        
1097        /**
1098         * Returns the individual claims to be returned. Corresponds to the
1099         * optional {@code claims} parameter.
1100         *
1101         * @return The individual claims to be returned, {@code null} if not
1102         *         specified.
1103         */
1104        public OIDCClaimsRequest getOIDCClaims() {
1105                
1106                return claims;
1107        }
1108        
1109        
1110        /**
1111         * Returns the end-user's preferred languages and scripts for the
1112         * claims being returned, ordered by preference. Corresponds to the
1113         * optional {@code claims_locales} parameter.
1114         *
1115         * @return The preferred claims locales, {@code null} if not specified.
1116         */
1117        public List<LangTag> getClaimsLocales() {
1118                
1119                return claimsLocales;
1120        }
1121        
1122        
1123        /**
1124         * Returns the transaction specific purpose. Corresponds to the
1125         * optional {@code purpose} parameter.
1126         *
1127         * @return The purpose, {@code null} if not specified.
1128         */
1129        public String getPurpose() {
1130                
1131                return purpose;
1132        }
1133        
1134        
1135        /**
1136         * Returns the resource server URI.
1137         *
1138         * @return The resource URI(s), {@code null} if not specified.
1139         */
1140        public List<URI> getResources() {
1141                
1142                return resources;
1143        }
1144        
1145        
1146        /**
1147         * Returns the additional custom parameters.
1148         *
1149         * @return The additional custom parameters as a unmodifiable map,
1150         *         empty map if none.
1151         */
1152        public Map<String, List<String>> getCustomParameters() {
1153                
1154                return customParams;
1155        }
1156        
1157        
1158        /**
1159         * Returns the specified custom parameter.
1160         *
1161         * @param name The parameter name. Must not be {@code null}.
1162         *
1163         * @return The parameter value(s), {@code null} if not specified.
1164         */
1165        public List<String> getCustomParameter(final String name) {
1166                
1167                return customParams.get(name);
1168        }
1169        
1170        
1171        /**
1172         * Returns {@code true} if this request is signed.
1173         *
1174         * @return {@code true} for a signed request, {@code false} for a plain
1175         *         request.
1176         */
1177        public boolean isSigned() {
1178                
1179                return signedRequest != null;
1180        }
1181        
1182        
1183        /**
1184         * Returns the JWT for a signed request.
1185         *
1186         * @return The request JWT.
1187         */
1188        public SignedJWT getRequestJWT() {
1189                
1190                return signedRequest;
1191        }
1192        
1193        
1194        /**
1195         * Returns the for parameters for this CIBA request. Parameters which
1196         * are part of the client authentication are not included.
1197         *
1198         * @return The parameters.
1199         */
1200        public Map<String, List<String>> toParameters() {
1201                
1202                // Put custom params first, so they may be overwritten by std params
1203                Map<String, List<String>> params = new LinkedHashMap<>(getCustomParameters());
1204                
1205                if (isSigned()) {
1206                        params.put("request", Collections.singletonList(signedRequest.serialize()));
1207                        return params;
1208                }
1209
1210                if (CollectionUtils.isNotEmpty(getScope())) {
1211                        params.put("scope", Collections.singletonList(getScope().toString()));
1212                }
1213                
1214                if (getClientNotificationToken() != null) {
1215                        params.put("client_notification_token", Collections.singletonList(getClientNotificationToken().getValue()));
1216                }
1217                if (getACRValues() != null) {
1218                        params.put("acr_values", Identifier.toStringList(getACRValues()));
1219                }
1220                if (getLoginHintTokenString() != null) {
1221                        params.put("login_hint_token", Collections.singletonList(getLoginHintTokenString()));
1222                }
1223                if (getIDTokenHint() != null) {
1224                        params.put("id_token_hint", Collections.singletonList(getIDTokenHint().serialize()));
1225                }
1226                if (getLoginHint() != null) {
1227                        params.put("login_hint", Collections.singletonList(getLoginHint()));
1228                }
1229                if (getBindingMessage() != null) {
1230                        params.put("binding_message", Collections.singletonList(getBindingMessage()));
1231                }
1232                if (getUserCode() != null) {
1233                        params.put("user_code", Collections.singletonList(getUserCode().getValue()));
1234                }
1235                if (getRequestedExpiry() != null) {
1236                        params.put("requested_expiry", Collections.singletonList(getRequestedExpiry().toString()));
1237                }
1238                if (getOIDCClaims() != null) {
1239                        params.put("claims", Collections.singletonList(getOIDCClaims().toJSONString()));
1240                }
1241                if (CollectionUtils.isNotEmpty(getClaimsLocales())) {
1242                        params.put("claims_locales", Collections.singletonList(LangTagUtils.concat(getClaimsLocales())));
1243                }
1244                if (getPurpose() != null) {
1245                        params.put("purpose", Collections.singletonList(purpose));
1246                }
1247                if (CollectionUtils.isNotEmpty(getResources())) {
1248                        params.put("resource", URIUtils.toStringList(getResources(), true));
1249                }
1250                
1251                return params;
1252        }
1253        
1254        
1255        /**
1256         * Returns the parameters for this CIBA request as a JSON Web Token
1257         * (JWT) claims set. Intended for creating a signed CIBA request.
1258         *
1259         * @return The parameters as JWT claim set.
1260         */
1261        public JWTClaimsSet toJWTClaimsSet() {
1262                
1263                if (isSigned()) {
1264                        throw new IllegalStateException();
1265                }
1266                
1267                return JWTClaimsSetUtils.toJWTClaimsSet(toParameters());
1268        }
1269        
1270        
1271        /**
1272         * Returns the matching HTTP request.
1273         *
1274         * @return The HTTP request.
1275         */
1276        @Override
1277        public HTTPRequest toHTTPRequest() {
1278
1279                if (getEndpointURI() == null)
1280                        throw new SerializeException("The endpoint URI is not specified");
1281
1282                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getEndpointURI());
1283                httpRequest.setEntityContentType(ContentType.APPLICATION_URLENCODED);
1284
1285                getClientAuthentication().applyTo(httpRequest);
1286
1287                Map<String, List<String>> params = httpRequest.getQueryParameters();
1288                params.putAll(toParameters());
1289                httpRequest.setQuery(URLUtils.serializeParameters(params));
1290                
1291                return httpRequest;
1292        }
1293
1294        
1295        /**
1296         * Parses a CIBA request from the specified HTTP request.
1297         *
1298         * @param httpRequest The HTTP request. Must not be {@code null}.
1299         *
1300         * @return The CIBA request.
1301         *
1302         * @throws ParseException If parsing failed.
1303         */
1304        public static CIBARequest parse(final HTTPRequest httpRequest) throws ParseException {
1305
1306                // Only HTTP POST accepted
1307                URI uri = httpRequest.getURI();
1308                httpRequest.ensureMethod(HTTPRequest.Method.POST);
1309                httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED);
1310                
1311                ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest);
1312                
1313                if (clientAuth == null) {
1314                        throw new ParseException("Missing required client authentication");
1315                }
1316                
1317                Map<String, List<String>> params = httpRequest.getQueryParameters();
1318                
1319                String v;
1320                
1321                if (params.containsKey("request")) {
1322                        // Signed request
1323                        v = MultivaluedMapUtils.getFirstValue(params, "request");
1324                        
1325                        if (StringUtils.isBlank(v)) {
1326                                throw new ParseException("Empty request parameter");
1327                        }
1328                        
1329                        SignedJWT signedRequest;
1330                        try {
1331                                signedRequest = SignedJWT.parse(v);
1332                        } catch (java.text.ParseException e) {
1333                                throw new ParseException("Invalid request JWT: " + e.getMessage(), e);
1334                        }
1335                        
1336                        try {
1337                                return new CIBARequest(uri, clientAuth, signedRequest);
1338                        } catch (IllegalArgumentException e) {
1339                                throw new ParseException(e.getMessage(), e);
1340                        }
1341                }
1342                
1343                
1344                // Plain request
1345                
1346                // Parse required scope
1347                v = MultivaluedMapUtils.getFirstValue(params, "scope");
1348                Scope scope = Scope.parse(v);
1349
1350                v = MultivaluedMapUtils.getFirstValue(params, "client_notification_token");
1351                BearerAccessToken clientNotificationToken = null;
1352                if (StringUtils.isNotBlank(v)) {
1353                        clientNotificationToken = new BearerAccessToken(v);
1354                }
1355                
1356                v = MultivaluedMapUtils.getFirstValue(params, "acr_values");
1357                List<ACR> acrValues = null;
1358                if (StringUtils.isNotBlank(v)) {
1359                        acrValues = new LinkedList<>();
1360                        StringTokenizer st = new StringTokenizer(v, " ");
1361                        while (st.hasMoreTokens()) {
1362                                acrValues.add(new ACR(st.nextToken()));
1363                        }
1364                }
1365                
1366                String loginHintTokenString = MultivaluedMapUtils.getFirstValue(params, "login_hint_token");
1367                
1368                v = MultivaluedMapUtils.getFirstValue(params, "id_token_hint");
1369                JWT idTokenHint = null;
1370                if (StringUtils.isNotBlank(v)) {
1371                        try {
1372                                idTokenHint = JWTParser.parse(v);
1373                        } catch (java.text.ParseException e) {
1374                                throw new ParseException("Invalid id_token_hint parameter: " + e.getMessage());
1375                        }
1376                }
1377                
1378                String loginHint = MultivaluedMapUtils.getFirstValue(params, "login_hint");
1379                
1380                v = MultivaluedMapUtils.getFirstValue(params, "user_code");
1381                
1382                Secret userCode = null;
1383                if (StringUtils.isNotBlank(v)) {
1384                        userCode = new Secret(v);
1385                }
1386                
1387                String bindingMessage = MultivaluedMapUtils.getFirstValue(params, "binding_message");
1388                
1389                v = MultivaluedMapUtils.getFirstValue(params, "requested_expiry");
1390                
1391                Integer requestedExpiry = null;
1392                if (StringUtils.isNotBlank(v)) {
1393                        try {
1394                                requestedExpiry = Integer.valueOf(v);
1395                        } catch (NumberFormatException e) {
1396                                throw new ParseException("The requested_expiry parameter must be an integer");
1397                        }
1398                }
1399                
1400                v = MultivaluedMapUtils.getFirstValue(params, "claims");
1401                OIDCClaimsRequest claims = null;
1402                if (StringUtils.isNotBlank(v)) {
1403                        try {
1404                                claims = OIDCClaimsRequest.parse(v);
1405                        } catch (ParseException e) {
1406                                throw new ParseException("Invalid claims parameter: " + e.getMessage(), e);
1407                        }
1408                }
1409                
1410                v = MultivaluedMapUtils.getFirstValue(params, "claims_locales");
1411                List<LangTag> claimsLocales = null;
1412                if (StringUtils.isNotBlank(v)) {
1413                        claimsLocales = new LinkedList<>();
1414                        StringTokenizer st = new StringTokenizer(v, " ");
1415                        while (st.hasMoreTokens()) {
1416                                try {
1417                                        claimsLocales.add(LangTag.parse(st.nextToken()));
1418                                } catch (LangTagException e) {
1419                                        throw new ParseException("Invalid claims_locales parameter: " + e.getMessage(), e);
1420                                }
1421                        }
1422                }
1423                
1424                String purpose = MultivaluedMapUtils.getFirstValue(params, "purpose");
1425                
1426                List<URI> resources = ResourceUtils.parseResourceURIs(params.get("resource"));
1427                
1428                // Parse additional custom parameters
1429                Map<String,List<String>> customParams = null;
1430                
1431                for (Map.Entry<String,List<String>> p: params.entrySet()) {
1432                        
1433                        if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey()) && ! clientAuth.getFormParameterNames().contains(p.getKey())) {
1434                                // We have a custom parameter
1435                                if (customParams == null) {
1436                                        customParams = new HashMap<>();
1437                                }
1438                                customParams.put(p.getKey(), p.getValue());
1439                        }
1440                }
1441
1442                try {
1443                        return new CIBARequest(
1444                                uri, clientAuth,
1445                                scope, clientNotificationToken, acrValues,
1446                                loginHintTokenString, idTokenHint, loginHint,
1447                                bindingMessage, userCode, requestedExpiry,
1448                                claims, claimsLocales, purpose,
1449                                resources,
1450                                customParams);
1451                } catch (IllegalArgumentException e) {
1452                        throw new ParseException(e.getMessage());
1453                }
1454        }
1455}