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