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 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}