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