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 String loginHintTokenString; 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 for 158 * 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 String loginHintTokenString; 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 loginHintTokenString = request.getLoginHintTokenString(); 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 string, containing information 448 * identifying the end-user for whom authentication is being requested. 449 * Corresponds to the {@code login_hint_token} parameter. 450 * 451 * @param loginHintTokenString The login hint token string, 452 * {@code null} if not specified. 453 * 454 * @return This builder. 455 */ 456 public Builder loginHintTokenString(final String loginHintTokenString) { 457 this.loginHintTokenString = loginHintTokenString; 458 return this; 459 } 460 461 462 /** 463 * Sets the ID Token hint, passed as a hint to identify the 464 * end-user for whom authentication is being requested. 465 * Corresponds to the {@code id_token_hint} parameter. 466 * 467 * @param idTokenHint The ID Token hint, {@code null} if not 468 * specified. 469 * 470 * @return This builder. 471 */ 472 public Builder idTokenHint(final JWT idTokenHint) { 473 this.idTokenHint = idTokenHint; 474 return this; 475 } 476 477 478 /** 479 * Sets the login hint (email address, phone number, etc), 480 * about the end-user for whom authentication is being 481 * requested. Corresponds to the {@code login_hint} parameter. 482 * 483 * @param loginHint The login hint, {@code null} if not 484 * specified. 485 * 486 * @return This builder. 487 */ 488 public Builder loginHint(final String loginHint) { 489 this.loginHint = loginHint; 490 return this; 491 } 492 493 494 /** 495 * Sets the human readable binding message for the display at 496 * the consumption and authentication devices. Corresponds to 497 * the {@code binding_message} parameter. 498 * 499 * @param bindingMessage The binding message, {@code null} if 500 * not specified. 501 * 502 * @return This builder. 503 */ 504 public Builder bindingMessage(final String bindingMessage) { 505 this.bindingMessage = bindingMessage; 506 return this; 507 } 508 509 510 /** 511 * Gets the user secret code (password, PIN, etc) to authorise 512 * the CIBA request with the authentication device. Corresponds 513 * to the {@code user_code} parameter. 514 * 515 * @param userCode The user code, {@code null} if not 516 * specified. 517 * 518 * @return This builder. 519 */ 520 public Builder userCode(final Secret userCode) { 521 this.userCode = userCode; 522 return this; 523 } 524 525 526 /** 527 * Sets the requested expiration for the {@code auth_req_id}. 528 * Corresponds to the {@code requested_expiry} parameter. 529 * 530 * @param requestedExpiry The required expiry (as positive 531 * integer), {@code null} if not 532 * specified. 533 * 534 * @return This builder. 535 */ 536 public Builder requestedExpiry(final Integer requestedExpiry) { 537 this.requestedExpiry = requestedExpiry; 538 return this; 539 } 540 541 542 /** 543 * Sets the individual OpenID claims to be returned. 544 * Corresponds to the optional {@code claims} parameter. 545 * 546 * @param claims The individual OpenID claims to be returned, 547 * {@code null} if not specified. 548 * 549 * @return This builder. 550 */ 551 public Builder claims(final OIDCClaimsRequest claims) { 552 553 this.claims = claims; 554 return this; 555 } 556 557 558 /** 559 * Sets the end-user's preferred languages and scripts for the 560 * claims being returned, ordered by preference. Corresponds to 561 * the optional {@code claims_locales} parameter. 562 * 563 * @param claimsLocales The preferred claims locales, 564 * {@code null} if not specified. 565 * 566 * @return This builder. 567 */ 568 public Builder claimsLocales(final List<LangTag> claimsLocales) { 569 570 this.claimsLocales = claimsLocales; 571 return this; 572 } 573 574 575 /** 576 * Sets the transaction specific purpose. Corresponds to the 577 * optional {@code purpose} parameter. 578 * 579 * @param purpose The purpose, {@code null} if not specified. 580 * 581 * @return This builder. 582 */ 583 public Builder purpose(final String purpose) { 584 585 this.purpose = purpose; 586 return this; 587 } 588 589 590 /** 591 * Sets the resource server URI. 592 * 593 * @param resource The resource URI, {@code null} if not 594 * specified. 595 * 596 * @return This builder. 597 */ 598 public Builder resource(final URI resource) { 599 if (resource != null) { 600 this.resources = Collections.singletonList(resource); 601 } else { 602 this.resources = null; 603 } 604 return this; 605 } 606 607 608 /** 609 * Sets the resource server URI(s). 610 * 611 * @param resources The resource URI(s), {@code null} if not 612 * specified. 613 * 614 * @return This builder. 615 */ 616 public Builder resources(final URI ... resources) { 617 if (resources != null) { 618 this.resources = Arrays.asList(resources); 619 } else { 620 this.resources = null; 621 } 622 return this; 623 } 624 625 626 /** 627 * Sets a custom parameter. 628 * 629 * @param name The parameter name. Must not be {@code null}. 630 * @param values The parameter values, {@code null} if not 631 * specified. 632 * 633 * @return This builder. 634 */ 635 public Builder customParameter(final String name, final String ... values) { 636 637 if (values == null || values.length == 0) { 638 customParams.remove(name); 639 } else { 640 customParams.put(name, Arrays.asList(values)); 641 } 642 643 return this; 644 } 645 646 647 /** 648 * Sets the URI of the endpoint (HTTP or HTTPS) for which the 649 * request is intended. 650 * 651 * @param uri The endpoint URI, {@code null} if not specified. 652 * 653 * @return This builder. 654 */ 655 public Builder endpointURI(final URI uri) { 656 657 this.uri = uri; 658 return this; 659 } 660 661 662 /** 663 * Builds a new CIBA request. 664 * 665 * @return The CIBA request. 666 */ 667 public CIBARequest build() { 668 669 try { 670 if (signedRequest != null) { 671 return new CIBARequest( 672 uri, 673 clientAuth, 674 signedRequest 675 ); 676 } 677 678 // Plain request 679 return new CIBARequest( 680 uri, 681 clientAuth, 682 scope, 683 clientNotificationToken, 684 acrValues, 685 loginHintTokenString, 686 idTokenHint, 687 loginHint, 688 bindingMessage, 689 userCode, 690 requestedExpiry, 691 claims, 692 claimsLocales, 693 purpose, 694 resources, 695 customParams 696 ); 697 } catch (IllegalArgumentException e) { 698 throw new IllegalArgumentException(e.getMessage(), e); 699 } 700 } 701 } 702 703 704 /** 705 * Creates a new CIBA request. 706 * 707 * @param uri The endpoint URI, {@code null} if not 708 * specified. 709 * @param clientAuth The client authentication. Must not 710 * be {@code null}. 711 * @param scope The requested scope. Must not be 712 * empty or {@code null}. 713 * @param clientNotificationToken The client notification token, 714 * {@code null} if not specified. 715 * @param acrValues The requested ACR values, 716 * {@code null} if not specified. 717 * @param loginHintTokenString The login hint token string, 718 * {@code null} if not specified. 719 * @param idTokenHint The ID Token hint, {@code null} if 720 * not specified. 721 * @param loginHint The login hint, {@code null} if not 722 * specified. 723 * @param bindingMessage The binding message, {@code null} if 724 * not specified. 725 * @param userCode The user code, {@code null} if not 726 * specified. 727 * @param requestedExpiry The required expiry (as positive 728 * integer), {@code null} if not 729 * specified. 730 * @param customParams Custom parameters, empty or 731 * {@code null} if not specified. 732 */ 733 @Deprecated 734 public CIBARequest(final URI uri, 735 final ClientAuthentication clientAuth, 736 final Scope scope, 737 final BearerAccessToken clientNotificationToken, 738 final List<ACR> acrValues, 739 final String loginHintTokenString, 740 final JWT idTokenHint, 741 final String loginHint, 742 final String bindingMessage, 743 final Secret userCode, 744 final Integer requestedExpiry, 745 final Map<String, List<String>> customParams) { 746 747 this(uri, clientAuth, 748 scope, clientNotificationToken, acrValues, 749 loginHintTokenString, idTokenHint, loginHint, 750 bindingMessage, userCode, requestedExpiry, 751 null, customParams); 752 } 753 754 755 /** 756 * Creates a new CIBA request. 757 * 758 * @param uri The endpoint URI, {@code null} if not 759 * specified. 760 * @param clientAuth The client authentication. Must not 761 * be {@code null}. 762 * @param scope The requested scope. Must not be 763 * empty or {@code null}. 764 * @param clientNotificationToken The client notification token, 765 * {@code null} if not specified. 766 * @param acrValues The requested ACR values, 767 * {@code null} if not specified. 768 * @param loginHintTokenString The login hint token string, 769 * {@code null} if not specified. 770 * @param idTokenHint The ID Token hint, {@code null} if 771 * not specified. 772 * @param loginHint The login hint, {@code null} if not 773 * specified. 774 * @param bindingMessage The binding message, {@code null} if 775 * not specified. 776 * @param userCode The user code, {@code null} if not 777 * specified. 778 * @param requestedExpiry The required expiry (as positive 779 * integer), {@code null} if not 780 * specified. 781 * @param claims The individual claims to be returned, 782 * {@code null} if not specified. 783 * @param customParams Custom parameters, empty or 784 * {@code null} if not specified. 785 */ 786 @Deprecated 787 public CIBARequest(final URI uri, 788 final ClientAuthentication clientAuth, 789 final Scope scope, 790 final BearerAccessToken clientNotificationToken, 791 final List<ACR> acrValues, 792 final String loginHintTokenString, 793 final JWT idTokenHint, 794 final String loginHint, 795 final String bindingMessage, 796 final Secret userCode, 797 final Integer requestedExpiry, 798 final OIDCClaimsRequest claims, 799 final Map<String, List<String>> customParams) { 800 801 this(uri, clientAuth, 802 scope, clientNotificationToken, acrValues, 803 loginHintTokenString, idTokenHint, loginHint, 804 bindingMessage, userCode, requestedExpiry, 805 claims, null, null, 806 null, 807 customParams); 808 } 809 810 811 /** 812 * Creates a new CIBA request. 813 * 814 * @param uri The endpoint URI, {@code null} if not 815 * specified. 816 * @param clientAuth The client authentication. Must not 817 * be {@code null}. 818 * @param scope The requested scope. Must not be 819 * empty or {@code null}. 820 * @param clientNotificationToken The client notification token, 821 * {@code null} if not specified. 822 * @param acrValues The requested ACR values, 823 * {@code null} if not specified. 824 * @param loginHintTokenString The login hint token string, 825 * {@code null} if not specified. 826 * @param idTokenHint The ID Token hint, {@code null} if 827 * not specified. 828 * @param loginHint The login hint, {@code null} if not 829 * specified. 830 * @param bindingMessage The binding message, {@code null} if 831 * not specified. 832 * @param userCode The user code, {@code null} if not 833 * specified. 834 * @param requestedExpiry The required expiry (as positive 835 * integer), {@code null} if not 836 * specified. 837 * @param claims The individual claims to be 838 * returned, {@code null} if not 839 * specified. 840 * @param claimsLocales The preferred languages and scripts 841 * for claims being returned, 842 * {@code null} if not specified. 843 * @param purpose The transaction specific purpose, 844 * {@code null} if not specified. 845 * @param resources The resource URI(s), {@code null} if 846 * not specified. 847 * @param customParams Custom parameters, empty or 848 * {@code null} if not specified. 849 */ 850 public CIBARequest(final URI uri, 851 final ClientAuthentication clientAuth, 852 final Scope scope, 853 final BearerAccessToken clientNotificationToken, 854 final List<ACR> acrValues, 855 final String loginHintTokenString, 856 final JWT idTokenHint, 857 final String loginHint, 858 final String bindingMessage, 859 final Secret userCode, 860 final Integer requestedExpiry, 861 final OIDCClaimsRequest claims, 862 final List<LangTag> claimsLocales, 863 final String purpose, 864 final List<URI> resources, 865 final Map<String, List<String>> customParams) { 866 867 super(uri, clientAuth); 868 869 this.scope = scope; 870 871 if (clientNotificationToken != null && clientNotificationToken.getValue().length() > CLIENT_NOTIFICATION_TOKEN_MAX_LENGTH) { 872 throw new IllegalArgumentException("The client notification token must not exceed " + CLIENT_NOTIFICATION_TOKEN_MAX_LENGTH + " chars"); 873 } 874 this.clientNotificationToken = clientNotificationToken; 875 876 this.acrValues = acrValues; 877 878 // https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0-03.html#rfc.section.7.1 879 // As in the CIBA flow the OP does not have an interaction with 880 // the end-user through the consumption device, it is REQUIRED 881 // that the Client provides one (and only one) of the hints 882 // specified above in the authentication request, that is 883 // "login_hint_token", "id_token_hint" or "login_hint". 884 int numHints = 0; 885 886 if (loginHintTokenString != null) numHints++; 887 this.loginHintTokenString = loginHintTokenString; 888 889 if (idTokenHint != null) numHints++; 890 this.idTokenHint = idTokenHint; 891 892 if (loginHint != null) numHints++; 893 this.loginHint = loginHint; 894 895 if (numHints != 1) { 896 throw new IllegalArgumentException("One user identity hist must be provided (login_hint_token, id_token_hint or login_hint)"); 897 } 898 899 this.bindingMessage = bindingMessage; 900 901 this.userCode = userCode; 902 903 if (requestedExpiry != null && requestedExpiry < 1) { 904 throw new IllegalArgumentException("The requested expiry must be a positive integer"); 905 } 906 this.requestedExpiry = requestedExpiry; 907 908 this.claims = claims; 909 910 if (claimsLocales != null) { 911 this.claimsLocales = Collections.unmodifiableList(claimsLocales); 912 } else { 913 this.claimsLocales = null; 914 } 915 916 this.purpose = purpose; 917 918 this.resources = ResourceUtils.ensureLegalResourceURIs(resources); 919 920 this.customParams = customParams != null ? customParams : Collections.<String, List<String>>emptyMap(); 921 922 signedRequest = null; 923 } 924 925 926 /** 927 * Creates a new CIBA signed request. 928 * 929 * @param uri The endpoint URI, {@code null} if not 930 * specified. 931 * @param clientAuth The client authentication. Must not be 932 * {@code null}. 933 * @param signedRequest The signed request JWT. Must not be 934 * {@code null}. 935 */ 936 public CIBARequest(final URI uri, 937 final ClientAuthentication clientAuth, 938 final SignedJWT signedRequest) { 939 940 super(uri, clientAuth); 941 942 if (signedRequest == null) { 943 throw new IllegalArgumentException("The signed request JWT must not be null"); 944 } 945 if (JWSObject.State.UNSIGNED.equals(signedRequest.getState())) { 946 throw new IllegalArgumentException("The request JWT must be in a signed state"); 947 } 948 this.signedRequest = signedRequest; 949 950 scope = null; 951 clientNotificationToken = null; 952 acrValues = null; 953 loginHintTokenString = null; 954 idTokenHint = null; 955 loginHint = null; 956 bindingMessage = null; 957 userCode = null; 958 requestedExpiry = null; 959 claims = null; 960 claimsLocales = null; 961 purpose = null; 962 resources = null; 963 customParams = Collections.emptyMap(); 964 } 965 966 967 /** 968 * Returns the registered (standard) CIBA request parameter names. 969 * 970 * @return The registered CIBA request parameter names, as a 971 * unmodifiable set. 972 */ 973 public static Set<String> getRegisteredParameterNames() { 974 975 return REGISTERED_PARAMETER_NAMES; 976 } 977 978 979 /** 980 * Returns the scope. Corresponds to the optional {@code scope} 981 * parameter. 982 * 983 * @return The scope, {@code null} if not specified. 984 */ 985 public Scope getScope() { 986 987 return scope; 988 } 989 990 991 /** 992 * Returns the client notification token, required for the CIBA ping 993 * and push token delivery modes. Corresponds to the 994 * {@code client_notification_token} parameter. 995 * 996 * @return The client notification token, {@code null} if not 997 * specified. 998 */ 999 public BearerAccessToken getClientNotificationToken() { 1000 1001 return clientNotificationToken; 1002 } 1003 1004 1005 /** 1006 * Returns the requested Authentication Context Class Reference values. 1007 * Corresponds to the optional {@code acr_values} parameter. 1008 * 1009 * @return The requested ACR values, {@code null} if not specified. 1010 */ 1011 public List<ACR> getACRValues() { 1012 1013 return acrValues; 1014 } 1015 1016 1017 /** 1018 * Returns the hint type. 1019 * 1020 * @return The hint type. 1021 */ 1022 public CIBAHintType getHintType() { 1023 1024 if (getLoginHintTokenString() != null) { 1025 return CIBAHintType.LOGIN_HINT_TOKEN; 1026 } else if (getIDTokenHint() != null) { 1027 return CIBAHintType.ID_TOKEN_HINT; 1028 } else { 1029 return CIBAHintType.LOGIN_HINT; 1030 } 1031 } 1032 1033 1034 /** 1035 * Returns the login hint token string, containing information 1036 * identifying the end-user for whom authentication is being requested. 1037 * Corresponds to the {@code login_hint_token} parameter. 1038 * 1039 * @return The login hint token string, {@code null} if not 1040 * specified. 1041 */ 1042 public String getLoginHintTokenString() { 1043 1044 return loginHintTokenString; 1045 } 1046 1047 1048 /** 1049 * Returns the ID Token hint, passed as a hint to identify the end-user 1050 * for whom authentication is being requested. Corresponds to the 1051 * {@code id_token_hint} parameter. 1052 * 1053 * @return The ID Token hint, {@code null} if not specified. 1054 */ 1055 public JWT getIDTokenHint() { 1056 1057 return idTokenHint; 1058 } 1059 1060 1061 /** 1062 * Returns the login hint (email address, phone number, etc), about the 1063 * end-user for whom authentication is being requested. Corresponds to 1064 * the {@code login_hint} parameter. 1065 * 1066 * @return The login hint, {@code null} if not specified. 1067 */ 1068 public String getLoginHint() { 1069 1070 return loginHint; 1071 } 1072 1073 1074 /** 1075 * Returns the human-readable binding message for the display at the 1076 * consumption and authentication devices. Corresponds to the 1077 * {@code binding_message} parameter. 1078 * 1079 * @return The binding message, {@code null} if not specified. 1080 */ 1081 public String getBindingMessage() { 1082 1083 return bindingMessage; 1084 } 1085 1086 1087 /** 1088 * Returns the user secret code (password, PIN, etc) to authorise the 1089 * CIBA request with the authentication device. Corresponds to the 1090 * {@code user_code} parameter. 1091 * 1092 * @return The user code, {@code null} if not specified. 1093 */ 1094 public Secret getUserCode() { 1095 1096 return userCode; 1097 } 1098 1099 1100 /** 1101 * Returns the requested expiration for the {@code auth_req_id}. 1102 * Corresponds to the {@code requested_expiry} parameter. 1103 * 1104 * @return The required expiry (as positive integer), {@code null} if 1105 * not specified. 1106 */ 1107 public Integer getRequestedExpiry() { 1108 1109 return requestedExpiry; 1110 } 1111 1112 1113 /** 1114 * Returns the individual claims to be returned. Corresponds to the 1115 * optional {@code claims} parameter. 1116 * 1117 * @return The individual claims to be returned, {@code null} if not 1118 * specified. 1119 */ 1120 public OIDCClaimsRequest getOIDCClaims() { 1121 1122 return claims; 1123 } 1124 1125 1126 /** 1127 * Returns the end-user's preferred languages and scripts for the 1128 * claims being returned, ordered by preference. Corresponds to the 1129 * optional {@code claims_locales} parameter. 1130 * 1131 * @return The preferred claims locales, {@code null} if not specified. 1132 */ 1133 public List<LangTag> getClaimsLocales() { 1134 1135 return claimsLocales; 1136 } 1137 1138 1139 /** 1140 * Returns the transaction specific purpose. Corresponds to the 1141 * optional {@code purpose} parameter. 1142 * 1143 * @return The purpose, {@code null} if not specified. 1144 */ 1145 public String getPurpose() { 1146 1147 return purpose; 1148 } 1149 1150 1151 /** 1152 * Returns the resource server URI. 1153 * 1154 * @return The resource URI(s), {@code null} if not specified. 1155 */ 1156 public List<URI> getResources() { 1157 1158 return resources; 1159 } 1160 1161 1162 /** 1163 * Returns the additional custom parameters. 1164 * 1165 * @return The additional custom parameters as a unmodifiable map, 1166 * empty map if none. 1167 */ 1168 public Map<String, List<String>> getCustomParameters() { 1169 1170 return customParams; 1171 } 1172 1173 1174 /** 1175 * Returns the specified custom parameter. 1176 * 1177 * @param name The parameter name. Must not be {@code null}. 1178 * 1179 * @return The parameter value(s), {@code null} if not specified. 1180 */ 1181 public List<String> getCustomParameter(final String name) { 1182 1183 return customParams.get(name); 1184 } 1185 1186 1187 /** 1188 * Returns {@code true} if this request is signed. 1189 * 1190 * @return {@code true} for a signed request, {@code false} for a plain 1191 * request. 1192 */ 1193 public boolean isSigned() { 1194 1195 return signedRequest != null; 1196 } 1197 1198 1199 /** 1200 * Returns the JWT for a signed request. 1201 * 1202 * @return The request JWT. 1203 */ 1204 public SignedJWT getRequestJWT() { 1205 1206 return signedRequest; 1207 } 1208 1209 1210 /** 1211 * Returns the for parameters for this CIBA request. Parameters which 1212 * are part of the client authentication are not included. 1213 * 1214 * @return The parameters. 1215 */ 1216 public Map<String, List<String>> toParameters() { 1217 1218 // Put custom params first, so they may be overwritten by std params 1219 Map<String, List<String>> params = new LinkedHashMap<>(getCustomParameters()); 1220 1221 if (isSigned()) { 1222 params.put("request", Collections.singletonList(signedRequest.serialize())); 1223 return params; 1224 } 1225 1226 if (CollectionUtils.isNotEmpty(getScope())) { 1227 params.put("scope", Collections.singletonList(getScope().toString())); 1228 } 1229 1230 if (getClientNotificationToken() != null) { 1231 params.put("client_notification_token", Collections.singletonList(getClientNotificationToken().getValue())); 1232 } 1233 if (getACRValues() != null) { 1234 params.put("acr_values", Identifier.toStringList(getACRValues())); 1235 } 1236 if (getLoginHintTokenString() != null) { 1237 params.put("login_hint_token", Collections.singletonList(getLoginHintTokenString())); 1238 } 1239 if (getIDTokenHint() != null) { 1240 params.put("id_token_hint", Collections.singletonList(getIDTokenHint().serialize())); 1241 } 1242 if (getLoginHint() != null) { 1243 params.put("login_hint", Collections.singletonList(getLoginHint())); 1244 } 1245 if (getBindingMessage() != null) { 1246 params.put("binding_message", Collections.singletonList(getBindingMessage())); 1247 } 1248 if (getUserCode() != null) { 1249 params.put("user_code", Collections.singletonList(getUserCode().getValue())); 1250 } 1251 if (getRequestedExpiry() != null) { 1252 params.put("requested_expiry", Collections.singletonList(getRequestedExpiry().toString())); 1253 } 1254 if (getOIDCClaims() != null) { 1255 params.put("claims", Collections.singletonList(getOIDCClaims().toJSONString())); 1256 } 1257 if (CollectionUtils.isNotEmpty(getClaimsLocales())) { 1258 params.put("claims_locales", Collections.singletonList(LangTagUtils.concat(getClaimsLocales()))); 1259 } 1260 if (getPurpose() != null) { 1261 params.put("purpose", Collections.singletonList(purpose)); 1262 } 1263 if (CollectionUtils.isNotEmpty(getResources())) { 1264 params.put("resource", URIUtils.toStringList(getResources(), true)); 1265 } 1266 1267 return params; 1268 } 1269 1270 1271 /** 1272 * Returns the parameters for this CIBA request as a JSON Web Token 1273 * (JWT) claims set. Intended for creating a signed CIBA request. 1274 * 1275 * @return The parameters as JWT claim set. 1276 */ 1277 public JWTClaimsSet toJWTClaimsSet() { 1278 1279 if (isSigned()) { 1280 throw new IllegalStateException(); 1281 } 1282 1283 return JWTClaimsSetUtils.toJWTClaimsSet(toParameters()); 1284 } 1285 1286 1287 /** 1288 * Returns the matching HTTP request. 1289 * 1290 * @return The HTTP request. 1291 */ 1292 @Override 1293 public HTTPRequest toHTTPRequest() { 1294 1295 if (getEndpointURI() == null) 1296 throw new SerializeException("The endpoint URI is not specified"); 1297 1298 HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getEndpointURI()); 1299 httpRequest.setEntityContentType(ContentType.APPLICATION_URLENCODED); 1300 1301 getClientAuthentication().applyTo(httpRequest); 1302 1303 Map<String, List<String>> params; 1304 try { 1305 params = new LinkedHashMap<>(httpRequest.getBodyAsFormParameters()); 1306 } catch (ParseException e) { 1307 throw new SerializeException(e.getMessage(), e); 1308 } 1309 params.putAll(toParameters()); 1310 httpRequest.setBody(URLUtils.serializeParameters(params)); 1311 1312 return httpRequest; 1313 } 1314 1315 1316 /** 1317 * Parses a CIBA request from the specified HTTP request. 1318 * 1319 * @param httpRequest The HTTP request. Must not be {@code null}. 1320 * 1321 * @return The CIBA request. 1322 * 1323 * @throws ParseException If parsing failed. 1324 */ 1325 public static CIBARequest parse(final HTTPRequest httpRequest) throws ParseException { 1326 1327 // Only HTTP POST accepted 1328 URI uri = httpRequest.getURI(); 1329 httpRequest.ensureMethod(HTTPRequest.Method.POST); 1330 httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED); 1331 1332 ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest); 1333 1334 if (clientAuth == null) { 1335 throw new ParseException("Missing required client authentication"); 1336 } 1337 1338 Map<String, List<String>> params = httpRequest.getQueryParameters(); 1339 1340 String v; 1341 1342 if (params.containsKey("request")) { 1343 // Signed request 1344 v = MultivaluedMapUtils.getFirstValue(params, "request"); 1345 1346 if (StringUtils.isBlank(v)) { 1347 throw new ParseException("Empty request parameter"); 1348 } 1349 1350 SignedJWT signedRequest; 1351 try { 1352 signedRequest = SignedJWT.parse(v); 1353 } catch (java.text.ParseException e) { 1354 throw new ParseException("Invalid request JWT: " + e.getMessage(), e); 1355 } 1356 1357 try { 1358 return new CIBARequest(uri, clientAuth, signedRequest); 1359 } catch (IllegalArgumentException e) { 1360 throw new ParseException(e.getMessage(), e); 1361 } 1362 } 1363 1364 1365 // Plain request 1366 1367 // Parse required scope 1368 v = MultivaluedMapUtils.getFirstValue(params, "scope"); 1369 Scope scope = Scope.parse(v); 1370 1371 v = MultivaluedMapUtils.getFirstValue(params, "client_notification_token"); 1372 BearerAccessToken clientNotificationToken = null; 1373 if (StringUtils.isNotBlank(v)) { 1374 clientNotificationToken = new BearerAccessToken(v); 1375 } 1376 1377 v = MultivaluedMapUtils.getFirstValue(params, "acr_values"); 1378 List<ACR> acrValues = null; 1379 if (StringUtils.isNotBlank(v)) { 1380 acrValues = new LinkedList<>(); 1381 StringTokenizer st = new StringTokenizer(v, " "); 1382 while (st.hasMoreTokens()) { 1383 acrValues.add(new ACR(st.nextToken())); 1384 } 1385 } 1386 1387 String loginHintTokenString = MultivaluedMapUtils.getFirstValue(params, "login_hint_token"); 1388 1389 v = MultivaluedMapUtils.getFirstValue(params, "id_token_hint"); 1390 JWT idTokenHint = null; 1391 if (StringUtils.isNotBlank(v)) { 1392 try { 1393 idTokenHint = JWTParser.parse(v); 1394 } catch (java.text.ParseException e) { 1395 throw new ParseException("Invalid id_token_hint parameter: " + e.getMessage()); 1396 } 1397 } 1398 1399 String loginHint = MultivaluedMapUtils.getFirstValue(params, "login_hint"); 1400 1401 v = MultivaluedMapUtils.getFirstValue(params, "user_code"); 1402 1403 Secret userCode = null; 1404 if (StringUtils.isNotBlank(v)) { 1405 userCode = new Secret(v); 1406 } 1407 1408 String bindingMessage = MultivaluedMapUtils.getFirstValue(params, "binding_message"); 1409 1410 v = MultivaluedMapUtils.getFirstValue(params, "requested_expiry"); 1411 1412 Integer requestedExpiry = null; 1413 if (StringUtils.isNotBlank(v)) { 1414 try { 1415 requestedExpiry = Integer.valueOf(v); 1416 } catch (NumberFormatException e) { 1417 throw new ParseException("The requested_expiry parameter must be an integer"); 1418 } 1419 } 1420 1421 v = MultivaluedMapUtils.getFirstValue(params, "claims"); 1422 OIDCClaimsRequest claims = null; 1423 if (StringUtils.isNotBlank(v)) { 1424 try { 1425 claims = OIDCClaimsRequest.parse(v); 1426 } catch (ParseException e) { 1427 throw new ParseException("Invalid claims parameter: " + e.getMessage(), e); 1428 } 1429 } 1430 1431 1432 List<LangTag> claimsLocales; 1433 try { 1434 claimsLocales = LangTagUtils.parseLangTagList(MultivaluedMapUtils.getFirstValue(params, "claims_locales")); 1435 } catch (LangTagException e) { 1436 throw new ParseException("Invalid claims_locales parameter: " + e.getMessage(), e); 1437 } 1438 1439 String purpose = MultivaluedMapUtils.getFirstValue(params, "purpose"); 1440 1441 List<URI> resources = ResourceUtils.parseResourceURIs(params.get("resource")); 1442 1443 // Parse additional custom parameters 1444 Map<String,List<String>> customParams = null; 1445 1446 for (Map.Entry<String,List<String>> p: params.entrySet()) { 1447 1448 if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey()) && ! clientAuth.getFormParameterNames().contains(p.getKey())) { 1449 // We have a custom parameter 1450 if (customParams == null) { 1451 customParams = new HashMap<>(); 1452 } 1453 customParams.put(p.getKey(), p.getValue()); 1454 } 1455 } 1456 1457 try { 1458 return new CIBARequest( 1459 uri, clientAuth, 1460 scope, clientNotificationToken, acrValues, 1461 loginHintTokenString, idTokenHint, loginHint, 1462 bindingMessage, userCode, requestedExpiry, 1463 claims, claimsLocales, purpose, 1464 resources, 1465 customParams); 1466 } catch (IllegalArgumentException e) { 1467 throw new ParseException(e.getMessage()); 1468 } 1469 } 1470}