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