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