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