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 * @return This builder. 476 */ 477 public Builder requestedExpiry(final Integer requestedExpiry) { 478 this.requestedExpiry = requestedExpiry; 479 return this; 480 } 481 482 483 /** 484 * Sets a custom parameter. 485 * 486 * @param name The parameter name. Must not be {@code null}. 487 * @param values The parameter values, {@code null} if not 488 * specified. 489 * 490 * @return This builder. 491 */ 492 public Builder customParameter(final String name, final String ... values) { 493 494 if (values == null || values.length == 0) { 495 customParams.remove(name); 496 } else { 497 customParams.put(name, Arrays.asList(values)); 498 } 499 500 return this; 501 } 502 503 504 /** 505 * Sets the URI of the endpoint (HTTP or HTTPS) for which the 506 * request is intended. 507 * 508 * @param uri The endpoint URI, {@code null} if not specified. 509 * 510 * @return This builder. 511 */ 512 public Builder endpointURI(final URI uri) { 513 514 this.uri = uri; 515 return this; 516 } 517 518 519 /** 520 * Builds a new CIBA request. 521 * 522 * @return The CIBA request. 523 */ 524 public CIBARequest build() { 525 526 try { 527 if (signedRequest != null) { 528 return new CIBARequest( 529 uri, 530 clientAuth, 531 signedRequest 532 ); 533 } 534 535 // Plain request 536 return new CIBARequest( 537 uri, 538 clientAuth, 539 scope, 540 clientNotificationToken, 541 acrValues, 542 loginHintTokenString, 543 idTokenHint, 544 loginHint, 545 bindingMessage, 546 userCode, 547 requestedExpiry, 548 customParams 549 ); 550 } catch (IllegalArgumentException e) { 551 throw new IllegalArgumentException(e.getMessage(), e); 552 } 553 } 554 } 555 556 557 /** 558 * Creates a new CIBA request. 559 * 560 * @param uri The endpoint URI, {@code null} if not 561 * specified. 562 * @param clientAuth The client authentication. Must not 563 * be {@code null}. 564 * @param scope The requested scope. Must not be 565 * empty or {@code null}. 566 * @param clientNotificationToken The client notification token, 567 * {@code null} if not specified. 568 * @param acrValues The requested ACR values, 569 * {@code null} if not specified. 570 * @param loginHintTokenString The login hint token string, 571 * {@code null} if not specified. 572 * @param idTokenHint The ID Token hint, {@code null} if 573 * not specified. 574 * @param loginHint The login hint, {@code null} if not 575 * specified. 576 * @param bindingMessage The binding message, {@code null} if 577 * not specified. 578 * @param userCode The user code, {@code null} if not 579 * specified. 580 * @param requestedExpiry The required expiry (as positive 581 * integer), {@code null} if not 582 * specified. 583 * @param customParams Custom parameters, empty or 584 * {@code null} if not specified. 585 */ 586 public CIBARequest(final URI uri, 587 final ClientAuthentication clientAuth, 588 final Scope scope, 589 final BearerAccessToken clientNotificationToken, 590 final List<ACR> acrValues, 591 final String loginHintTokenString, 592 final JWT idTokenHint, 593 final String loginHint, 594 final String bindingMessage, 595 final Secret userCode, 596 final Integer requestedExpiry, 597 final Map<String, List<String>> customParams) { 598 599 super(uri, clientAuth); 600 601 if (CollectionUtils.isEmpty(scope)) { 602 throw new IllegalArgumentException("The scope must not be null or empty"); 603 } 604 this.scope = scope; 605 606 if (clientNotificationToken != null && clientNotificationToken.getValue().length() > CLIENT_NOTIFICATION_TOKEN_MAX_LENGTH) { 607 throw new IllegalArgumentException("The client notification token must not exceed " + CLIENT_NOTIFICATION_TOKEN_MAX_LENGTH + " chars"); 608 } 609 this.clientNotificationToken = clientNotificationToken; 610 611 this.acrValues = acrValues; 612 613 // https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0-03.html#rfc.section.7.1 614 // As in the CIBA flow the OP does not have an interaction with 615 // the end-user through the consumption device, it is REQUIRED 616 // that the Client provides one (and only one) of the hints 617 // specified above in the authentication request, that is 618 // "login_hint_token", "id_token_hint" or "login_hint". 619 int numHints = 0; 620 621 if (loginHintTokenString != null) numHints++; 622 this.loginHintTokenString = loginHintTokenString; 623 624 if (idTokenHint != null) numHints++; 625 this.idTokenHint = idTokenHint; 626 627 if (loginHint != null) numHints++; 628 this.loginHint = loginHint; 629 630 if (numHints != 1) { 631 throw new IllegalArgumentException("One user identity hist must be provided (login_hint_token, id_token_hint or login_hint)"); 632 } 633 634 this.bindingMessage = bindingMessage; 635 636 this.userCode = userCode; 637 638 if (requestedExpiry != null && requestedExpiry < 1) { 639 throw new IllegalArgumentException("The requested expiry must be a positive integer"); 640 } 641 this.requestedЕxpiry = requestedExpiry; 642 643 this.customParams = customParams != null ? customParams : Collections.<String, List<String>>emptyMap(); 644 645 signedRequest = null; 646 } 647 648 649 /** 650 * Creates a new CIBA signed request. 651 * 652 * @param uri The endpoint URI, {@code null} if not 653 * specified. 654 * @param clientAuth The client authentication. Must not be 655 * {@code null}. 656 * @param signedRequest The signed request JWT. Must not be 657 * {@code null}. 658 */ 659 public CIBARequest(final URI uri, 660 final ClientAuthentication clientAuth, 661 final SignedJWT signedRequest) { 662 663 super(uri, clientAuth); 664 665 if (signedRequest == null) { 666 throw new IllegalArgumentException("The signed request JWT must not be null"); 667 } 668 if (JWSObject.State.UNSIGNED.equals(signedRequest.getState())) { 669 throw new IllegalArgumentException("The request JWT must be in a signed state"); 670 } 671 this.signedRequest = signedRequest; 672 673 scope = null; 674 clientNotificationToken = null; 675 acrValues = null; 676 loginHintTokenString = null; 677 idTokenHint = null; 678 loginHint = null; 679 bindingMessage = null; 680 userCode = null; 681 requestedЕxpiry = null; 682 customParams = Collections.emptyMap(); 683 } 684 685 686 /** 687 * Returns the registered (standard) CIBA request parameter names. 688 * 689 * @return The registered CIBA request parameter names, as a 690 * unmodifiable set. 691 */ 692 public static Set<String> getRegisteredParameterNames() { 693 694 return REGISTERED_PARAMETER_NAMES; 695 } 696 697 698 /** 699 * Gets the scope. Corresponds to the optional {@code scope} parameter. 700 * 701 * @return The scope, {@code null} for a {@link #isSigned signed 702 * request}. 703 */ 704 public Scope getScope() { 705 706 return scope; 707 } 708 709 710 /** 711 * Gets the client notification token, required for the CIBA ping and 712 * push token delivery modes. Corresponds to the 713 * {@code client_notification_token} parameter. 714 * 715 * @return The client notification token, {@code null} if not 716 * specified. 717 */ 718 public BearerAccessToken getClientNotificationToken() { 719 720 return clientNotificationToken; 721 } 722 723 724 /** 725 * Gets the requested Authentication Context Class Reference values. 726 * Corresponds to the optional {@code acr_values} parameter. 727 * 728 * @return The requested ACR values, {@code null} if not specified. 729 */ 730 public List<ACR> getACRValues() { 731 732 return acrValues; 733 } 734 735 736 /** 737 * Gets the login hint token string, containing information 738 * identifying the end-user for whom authentication is being requested. 739 * Corresponds to the {@code login_hint_token} parameter. 740 * 741 * @return The login hint token string, {@code null} if not 742 * specified. 743 */ 744 public String getLoginHintTokenString() { 745 746 return loginHintTokenString; 747 } 748 749 750 /** 751 * Gets the ID Token hint, passed as a hint to identify the end-user 752 * for whom authentication is being requested. Corresponds to the 753 * {@code id_token_hint} parameter. 754 * 755 * @return The ID Token hint, {@code null} if not specified. 756 */ 757 public JWT getIDTokenHint() { 758 759 return idTokenHint; 760 } 761 762 763 /** 764 * Gets the login hint (email address, phone number, etc), about the 765 * end-user for whom authentication is being requested. Corresponds to 766 * the {@code login_hint} parameter. 767 * 768 * @return The login hint, {@code null} if not specified. 769 */ 770 public String getLoginHint() { 771 772 return loginHint; 773 } 774 775 776 /** 777 * Gets the human readable binding message for the display at the 778 * consumption and authentication devices. Corresponds to the 779 * {@code binding_message} parameter. 780 * 781 * @return The binding message, {@code null} if not specified. 782 */ 783 public String getBindingMessage() { 784 785 return bindingMessage; 786 } 787 788 789 /** 790 * Gets the user secret code (password, PIN, etc) to authorise the CIBA 791 * request with the authentication device. Corresponds to the 792 * {@code user_code} parameter. 793 * 794 * @return The user code, {@code null} if not specified. 795 */ 796 public Secret getUserCode() { 797 798 return userCode; 799 } 800 801 802 /** 803 * Gets the requested expiration for the {@code auth_req_id}. 804 * Corresponds to the {@code requested_expiry} parameter. 805 * 806 * @return The required expiry (as positive integer), {@code null} if 807 * not specified. 808 */ 809 public Integer getRequestedExpiry() { 810 811 return requestedЕxpiry; 812 } 813 814 815 /** 816 * Returns the additional custom parameters. 817 * 818 * @return The additional custom parameters as a unmodifiable map, 819 * empty map if none. 820 */ 821 public Map<String, List<String>> getCustomParameters() { 822 823 return customParams; 824 } 825 826 827 /** 828 * Returns the specified custom parameter. 829 * 830 * @param name The parameter name. Must not be {@code null}. 831 * 832 * @return The parameter value(s), {@code null} if not specified. 833 */ 834 public List<String> getCustomParameter(final String name) { 835 836 return customParams.get(name); 837 } 838 839 840 /** 841 * Returns {@code true} if this request is signed. 842 * 843 * @return {@code true} for a signed request, {@code false} for a plain 844 * request. 845 */ 846 public boolean isSigned() { 847 848 return signedRequest != null; 849 } 850 851 852 /** 853 * Returns the JWT for a signed request. 854 * 855 * @return The request JWT. 856 */ 857 public SignedJWT getRequestJWT() { 858 859 return signedRequest; 860 } 861 862 863 /** 864 * Returns the for parameters for this CIBA request. Parameters which 865 * are part of the client authentication are not included. 866 * 867 * @return The parameters. 868 */ 869 public Map<String, List<String>> toParameters() { 870 871 // Put custom params first, so they may be overwritten by std params 872 Map<String, List<String>> params = new LinkedHashMap<>(getCustomParameters()); 873 874 if (isSigned()) { 875 params.put("request", Collections.singletonList(signedRequest.serialize())); 876 return params; 877 } 878 879 params.put("scope", Collections.singletonList(getScope().toString())); 880 881 if (getClientNotificationToken() != null) { 882 params.put("client_notification_token", Collections.singletonList(getClientNotificationToken().getValue())); 883 } 884 if (getACRValues() != null) { 885 params.put("acr_values", Identifier.toStringList(getACRValues())); 886 } 887 if (getLoginHintTokenString() != null) { 888 params.put("login_hint_token", Collections.singletonList(getLoginHintTokenString())); 889 } 890 if (getIDTokenHint() != null) { 891 params.put("id_token_hint", Collections.singletonList(getIDTokenHint().serialize())); 892 } 893 if (getLoginHint() != null) { 894 params.put("login_hint", Collections.singletonList(getLoginHint())); 895 } 896 if (getBindingMessage() != null) { 897 params.put("binding_message", Collections.singletonList(getBindingMessage())); 898 } 899 if (getUserCode() != null) { 900 params.put("user_code", Collections.singletonList(getUserCode().getValue())); 901 } 902 if (getRequestedExpiry() != null) { 903 params.put("requested_expiry", Collections.singletonList(getRequestedExpiry().toString())); 904 } 905 906 return params; 907 } 908 909 910 /** 911 * Returns the parameters for this CIBA request as a JSON Web Token 912 * (JWT) claims set. Intended for creating a signed CIBA request. 913 * 914 * @return The parameters as JWT claim set. 915 */ 916 public JWTClaimsSet toJWTClaimsSet() { 917 918 if (isSigned()) { 919 throw new IllegalStateException(); 920 } 921 922 return JWTClaimsSetUtils.toJWTClaimsSet(toParameters()); 923 } 924 925 926 /** 927 * Returns the matching HTTP request. 928 * 929 * @return The HTTP request. 930 */ 931 @Override 932 public HTTPRequest toHTTPRequest() { 933 934 if (getEndpointURI() == null) 935 throw new SerializeException("The endpoint URI is not specified"); 936 937 HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getEndpointURI()); 938 httpRequest.setEntityContentType(ContentType.APPLICATION_URLENCODED); 939 940 getClientAuthentication().applyTo(httpRequest); 941 942 Map<String, List<String>> params = httpRequest.getQueryParameters(); 943 params.putAll(toParameters()); 944 httpRequest.setQuery(URLUtils.serializeParameters(params)); 945 946 return httpRequest; 947 } 948 949 950 /** 951 * Parses a CIBA request from the specified HTTP request. 952 * 953 * @param httpRequest The HTTP request. Must not be {@code null}. 954 * 955 * @return The CIBA request. 956 * 957 * @throws ParseException If parsing failed. 958 */ 959 public static CIBARequest parse(final HTTPRequest httpRequest) throws ParseException { 960 961 // Only HTTP POST accepted 962 URI uri = httpRequest.getURI(); 963 httpRequest.ensureMethod(HTTPRequest.Method.POST); 964 httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED); 965 966 ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest); 967 968 if (clientAuth == null) { 969 throw new ParseException("Missing required client authentication"); 970 } 971 972 Map<String, List<String>> params = httpRequest.getQueryParameters(); 973 974 String v; 975 976 if (params.containsKey("request")) { 977 // Signed request 978 v = MultivaluedMapUtils.getFirstValue(params, "request"); 979 980 if (StringUtils.isBlank(v)) { 981 throw new ParseException("Empty request parameter"); 982 } 983 984 SignedJWT signedRequest; 985 try { 986 signedRequest = SignedJWT.parse(v); 987 } catch (java.text.ParseException e) { 988 throw new ParseException("Invalid request JWT: " + e.getMessage(), e); 989 } 990 991 try { 992 return new CIBARequest(uri, clientAuth, signedRequest); 993 } catch (IllegalArgumentException e) { 994 throw new ParseException(e.getMessage(), e); 995 } 996 } 997 998 999 // Plain request 1000 1001 // Parse required scope 1002 v = MultivaluedMapUtils.getFirstValue(params, "scope"); 1003 Scope scope = Scope.parse(v); 1004 1005 v = MultivaluedMapUtils.getFirstValue(params, "client_notification_token"); 1006 BearerAccessToken clientNotificationToken = null; 1007 if (StringUtils.isNotBlank(v)) { 1008 clientNotificationToken = new BearerAccessToken(v); 1009 } 1010 1011 v = MultivaluedMapUtils.getFirstValue(params, "acr_values"); 1012 List<ACR> acrValues = null; 1013 if (StringUtils.isNotBlank(v)) { 1014 acrValues = new LinkedList<>(); 1015 StringTokenizer st = new StringTokenizer(v, " "); 1016 while (st.hasMoreTokens()) { 1017 acrValues.add(new ACR(st.nextToken())); 1018 } 1019 } 1020 1021 String loginHintTokenString = MultivaluedMapUtils.getFirstValue(params, "login_hint_token"); 1022 1023 v = MultivaluedMapUtils.getFirstValue(params, "id_token_hint"); 1024 JWT idTokenHint = null; 1025 if (StringUtils.isNotBlank(v)) { 1026 try { 1027 idTokenHint = JWTParser.parse(v); 1028 } catch (java.text.ParseException e) { 1029 throw new ParseException("Invalid id_token_hint parameter: " + e.getMessage()); 1030 } 1031 } 1032 1033 String loginHint = MultivaluedMapUtils.getFirstValue(params, "login_hint"); 1034 1035 v = MultivaluedMapUtils.getFirstValue(params, "user_code"); 1036 1037 Secret userCode = null; 1038 if (StringUtils.isNotBlank(v)) { 1039 userCode = new Secret(v); 1040 } 1041 1042 String bindingMessage = MultivaluedMapUtils.getFirstValue(params, "binding_message"); 1043 1044 v = MultivaluedMapUtils.getFirstValue(params, "requested_expiry"); 1045 1046 Integer requestedExpiry = null; 1047 1048 if (StringUtils.isNotBlank(v)) { 1049 try { 1050 requestedExpiry = Integer.valueOf(v); 1051 } catch (NumberFormatException e) { 1052 throw new ParseException("The requested_expiry parameter must be an integer"); 1053 } 1054 } 1055 1056 // Parse additional custom parameters 1057 Map<String,List<String>> customParams = null; 1058 1059 for (Map.Entry<String,List<String>> p: params.entrySet()) { 1060 1061 if (! REGISTERED_PARAMETER_NAMES.contains(p.getKey())) { 1062 // We have a custom parameter 1063 if (customParams == null) { 1064 customParams = new HashMap<>(); 1065 } 1066 customParams.put(p.getKey(), p.getValue()); 1067 } 1068 } 1069 1070 try { 1071 return new CIBARequest( 1072 uri, clientAuth, 1073 scope, clientNotificationToken, acrValues, loginHintTokenString, idTokenHint, loginHint, bindingMessage, userCode, requestedExpiry, 1074 customParams); 1075 } catch (IllegalArgumentException e) { 1076 throw new ParseException(e.getMessage()); 1077 } 1078 } 1079}