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.openid.connect.sdk; 019 020 021import java.util.*; 022 023import net.jcip.annotations.Immutable; 024import net.minidev.json.JSONArray; 025import net.minidev.json.JSONAware; 026import net.minidev.json.JSONObject; 027 028import com.nimbusds.langtag.LangTag; 029import com.nimbusds.langtag.LangTagException; 030import com.nimbusds.oauth2.sdk.OAuth2Error; 031import com.nimbusds.oauth2.sdk.ParseException; 032import com.nimbusds.oauth2.sdk.ResponseType; 033import com.nimbusds.oauth2.sdk.Scope; 034import com.nimbusds.oauth2.sdk.util.JSONArrayUtils; 035import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 036import com.nimbusds.openid.connect.sdk.claims.ClaimRequirement; 037 038 039/** 040 * Specifies the individual OpenID claims to return from the UserInfo endpoint 041 * and / or in the ID Token. 042 * 043 * <p>Related specifications: 044 * 045 * <ul> 046 * <li>OpenID Connect Core 1.0, section 5.5. 047 * <li>OpenID Connect for Identity Assurance 1.0. 048 * </ul> 049 */ 050public class ClaimsRequest implements JSONAware { 051 052 053 /** 054 * Individual OpenID claim request. 055 * 056 * <p>Related specifications: 057 * 058 * <ul> 059 * <li>OpenID Connect Core 1.0, section 5.5.1. 060 * <li>OpenID Connect for Identity Assurance 1.0. 061 * </ul> 062 */ 063 @Immutable 064 public static class Entry { 065 066 067 /** 068 * The claim name. 069 */ 070 private final String claimName; 071 072 073 /** 074 * The claim requirement. 075 */ 076 private final ClaimRequirement requirement; 077 078 079 /** 080 * Optional language tag. 081 */ 082 private final LangTag langTag; 083 084 085 /** 086 * Optional claim value. 087 */ 088 private final String value; 089 090 091 /** 092 * Optional claim values. 093 */ 094 private final List<String> values; 095 096 097 /** 098 * The claim purpose. 099 */ 100 private final String purpose; 101 102 103 /** 104 * Optional additional claim information. 105 * 106 * <p>Example additional information in the "info" member: 107 * 108 * <pre> 109 * { 110 * "userinfo" : { 111 * "email": null, 112 * "email_verified": null, 113 * "http://example.info/claims/groups" : { "info" : "custom information" } } 114 * } 115 * </pre> 116 */ 117 private final Map<String, Object> additionalInformation; 118 119 120 /** 121 * Creates a new individual claim request. The claim 122 * requirement is set to voluntary (the default) and no 123 * expected value(s) or other parameters are specified. 124 * 125 * @param claimName The claim name. Must not be {@code null}. 126 */ 127 public Entry(final String claimName) { 128 129 this(claimName, ClaimRequirement.VOLUNTARY, null, null, null, null, null); 130 } 131 132 133 /** 134 * Creates a new individual claim request. The claim 135 * requirement is set to voluntary (the default) and no 136 * expected value(s) are specified. 137 * 138 * @param claimName The claim name. Must not be {@code null}. 139 * @param langTag Optional language tag for the claim. 140 */ 141 @Deprecated 142 public Entry(final String claimName, final LangTag langTag) { 143 144 this(claimName, ClaimRequirement.VOLUNTARY, langTag, null, null); 145 } 146 147 148 /** 149 * Creates a new individual claim request. 150 * 151 * @param claimName The claim name. Must not be {@code null}. 152 * @param requirement The claim requirement. Must not be 153 * {@code null}. 154 */ 155 @Deprecated 156 public Entry(final String claimName, final ClaimRequirement requirement) { 157 158 this(claimName, requirement, null, null, null); 159 } 160 161 162 /** 163 * Creates a new individual claim request. 164 * 165 * @param claimName The claim name. Must not be {@code null}. 166 * @param requirement The claim requirement. Must not be 167 * {@code null}. 168 * @param langTag Optional language tag for the claim. 169 * @param value Optional expected value for the claim. 170 */ 171 @Deprecated 172 public Entry(final String claimName, final ClaimRequirement requirement, 173 final LangTag langTag, final String value) { 174 175 this(claimName, requirement, langTag, value, null); 176 } 177 178 179 /** 180 * Creates a new individual claim request. 181 * 182 * @param claimName The claim name. Must not be {@code null}. 183 * @param requirement The claim requirement. Must not be 184 * {@code null}. 185 * @param langTag Optional language tag for the claim. 186 * @param values Optional expected values for the claim. 187 */ 188 @Deprecated 189 public Entry(final String claimName, final ClaimRequirement requirement, 190 final LangTag langTag, final List<String> values) { 191 192 this(claimName, requirement, langTag, null, values, null, null); 193 } 194 195 196 /** 197 * Creates a new individual claim request. This constructor is 198 * to be used privately. Ensures that {@code value} and 199 * {@code values} are not simultaneously specified. 200 * 201 * @param claimName The claim name. Must not be {@code null}. 202 * @param requirement The claim requirement. Must not be 203 * {@code null}. 204 * @param langTag Optional language tag for the claim. 205 * @param value Optional expected value for the claim. If 206 * set, then the {@code values} parameter 207 * must not be set. 208 * @param values Optional expected values for the claim. 209 * If set, then the {@code value} parameter 210 * must not be set. 211 */ 212 @Deprecated 213 private Entry(final String claimName, final ClaimRequirement requirement, final LangTag langTag, 214 final String value, final List<String> values) { 215 this(claimName, requirement, langTag, value, values, null, null); 216 } 217 218 219 /** 220 * Creates a new individual claim request. This constructor is 221 * to be used privately. Ensures that {@code value} and 222 * {@code values} are not simultaneously specified. 223 * 224 * @param claimName The claim name. Must not be 225 * {@code null}. 226 * @param requirement The claim requirement. Must not 227 * be {@code null}. 228 * @param langTag Optional language tag for the 229 * claim. 230 * @param value Optional expected value for the 231 * claim. If set, then the {@code 232 * values} parameter must not be 233 * set. 234 * @param values Optional expected values for 235 * the claim. If set, then the 236 * {@code value} parameter must 237 * not be set. 238 * @param purpose The purpose for the requested 239 * claim, {@code null} if not 240 * specified. 241 * @param additionalInformation Optional additional information 242 */ 243 private Entry(final String claimName, 244 final ClaimRequirement requirement, 245 final LangTag langTag, 246 final String value, 247 final List<String> values, 248 final String purpose, 249 final Map<String, Object> additionalInformation) { 250 251 if (claimName == null) 252 throw new IllegalArgumentException("The claim name must not be null"); 253 254 this.claimName = claimName; 255 256 257 if (requirement == null) 258 throw new IllegalArgumentException("The claim requirement must not be null"); 259 260 this.requirement = requirement; 261 262 263 this.langTag = langTag; 264 265 266 if (value != null && values == null) { 267 268 this.value = value; 269 this.values = null; 270 271 } else if (value == null && values != null) { 272 273 this.value = null; 274 this.values = values; 275 276 } else if (value == null && values == null) { 277 278 this.value = null; 279 this.values = null; 280 281 } else { 282 283 throw new IllegalArgumentException("Either value or values must be specified, but not both"); 284 } 285 286 this.purpose = purpose; 287 288 this.additionalInformation = additionalInformation; 289 } 290 291 292 /** 293 * Returns the claim name. 294 * 295 * @return The claim name. 296 */ 297 public String getClaimName() { 298 299 return claimName; 300 } 301 302 303 /** 304 * Returns the claim name, optionally with the language tag 305 * appended. 306 * 307 * <p>Example with language tag: 308 * 309 * <pre> 310 * name#de-DE 311 * </pre> 312 * 313 * @param withLangTag If {@code true} the language tag will be 314 * appended to the name (if any), else not. 315 * 316 * @return The claim name, with optionally appended language 317 * tag. 318 */ 319 public String getClaimName(final boolean withLangTag) { 320 321 if (withLangTag && langTag != null) 322 return claimName + "#" + langTag.toString(); 323 else 324 return claimName; 325 } 326 327 328 /** 329 * Returns a new claim entry with the specified requirement. 330 * 331 * @param requirement The claim requirement. 332 * 333 * @return The new entry. 334 */ 335 public Entry withClaimRequirement(final ClaimRequirement requirement) { 336 337 return new Entry(claimName, requirement, langTag, value, values, purpose, additionalInformation); 338 } 339 340 341 /** 342 * Returns the claim requirement. 343 * 344 * @return The claim requirement. 345 */ 346 public ClaimRequirement getClaimRequirement() { 347 348 return requirement; 349 } 350 351 352 /** 353 * Returns a new claim entry with the specified language tag 354 * for the claim. 355 * 356 * @param langTag The language tag, {@code null} if not 357 * specified. 358 * 359 * @return The new entry. 360 */ 361 public Entry withLangTag(final LangTag langTag) { 362 363 return new Entry(claimName, requirement, langTag, value, values, purpose, additionalInformation); 364 } 365 366 367 /** 368 * Returns the optional language tag for the claim. 369 * 370 * @return The language tag, {@code null} if not specified. 371 */ 372 public LangTag getLangTag() { 373 374 return langTag; 375 } 376 377 378 /** 379 * Returns a new claim entry with the specified requested value 380 * for the claim. 381 * 382 * @param value The value, {@code null} if not specified. 383 * 384 * @return The new entry. 385 */ 386 public Entry withValue(final String value) { 387 388 return new Entry(claimName, requirement, langTag, value, null, purpose, additionalInformation); 389 } 390 391 392 /** 393 * Returns the requested value for the claim. 394 * 395 * @return The value, {@code null} if not specified. 396 */ 397 public String getValue() { 398 399 return value; 400 } 401 402 403 /** 404 * Returns a new claim entry with the specified requested 405 * values for the claim. 406 * 407 * @param values The values, {@code null} if not specified. 408 * 409 * @return The new entry. 410 */ 411 public Entry withValues(final List<String> values) { 412 413 return new Entry(claimName, requirement, langTag, null, values, purpose, additionalInformation); 414 } 415 416 417 /** 418 * Returns the optional values for the claim. 419 * 420 * @return The values, {@code null} if not specified. 421 */ 422 public List<String> getValues() { 423 424 return values; 425 } 426 427 428 /** 429 * Returns a new claim entry with the specified purpose for the 430 * requested claim. 431 * 432 * @param purpose The purpose, {@code null} if not specified. 433 * 434 * @return The new entry. 435 */ 436 public Entry withPurpose(final String purpose) { 437 438 return new Entry(claimName, requirement, langTag, value, values, purpose, additionalInformation); 439 } 440 441 442 /** 443 * Returns the optional purpose for the requested claim. 444 * 445 * @return The purpose, {@code null} if not specified. 446 */ 447 public String getPurpose() { 448 449 return purpose; 450 } 451 452 453 /** 454 * Returns a new claim entry with the specified additional 455 * information for the claim. 456 * 457 * <p>Example additional information in the "info" member: 458 * 459 * <pre> 460 * { 461 * "userinfo" : { 462 * "email": null, 463 * "email_verified": null, 464 * "http://example.info/claims/groups" : { "info" : "custom information" } } 465 * } 466 * </pre> 467 * 468 * @param additionalInformation The additional information, 469 * {@code null} if not specified. 470 * 471 * @return The new entry. 472 */ 473 public Entry withAdditionalInformation(final Map<String, Object> additionalInformation) { 474 475 return new Entry(claimName, requirement, langTag, value, values, purpose, additionalInformation); 476 } 477 478 479 /** 480 * Returns the additional information for the claim. 481 * 482 * <p>Example additional information in the "info" member: 483 * 484 * <pre> 485 * { 486 * "userinfo" : { 487 * "email": null, 488 * "email_verified": null, 489 * "http://example.info/claims/groups" : { "info" : "custom information" } } 490 * } 491 * </pre> 492 * 493 * @return The additional information, {@code null} if not 494 * specified. 495 */ 496 public Map<String, Object> getAdditionalInformation() { 497 return additionalInformation; 498 } 499 500 501 /** 502 * Returns the JSON object representation of the specified 503 * collection of individual claim requests. 504 * 505 * <p>Example: 506 * 507 * <pre> 508 * { 509 * "given_name": {"essential": true}, 510 * "nickname": null, 511 * "email": {"essential": true}, 512 * "email_verified": {"essential": true}, 513 * "picture": null, 514 * "http://example.info/claims/groups": null 515 * } 516 * </pre> 517 * 518 * @param entries The entries to serialise. Must not be 519 * {@code null}. 520 * @return The corresponding JSON object, empty if no claims 521 * were found. 522 */ 523 public static JSONObject toJSONObject(final Collection<Entry> entries) { 524 525 JSONObject o = new JSONObject(); 526 527 for (Entry entry : entries) { 528 529 // Compose the optional value 530 JSONObject entrySpec = null; 531 532 if (entry.getValue() != null) { 533 534 entrySpec = new JSONObject(); 535 entrySpec.put("value", entry.getValue()); 536 } 537 538 if (entry.getValues() != null) { 539 540 // Either "value" or "values", or none 541 // may be defined 542 entrySpec = new JSONObject(); 543 entrySpec.put("values", entry.getValues()); 544 } 545 546 if (entry.getClaimRequirement().equals(ClaimRequirement.ESSENTIAL)) { 547 548 if (entrySpec == null) 549 entrySpec = new JSONObject(); 550 551 entrySpec.put("essential", true); 552 } 553 554 if (entry.getPurpose() != null) { 555 if (entrySpec == null) { 556 entrySpec = new JSONObject(); 557 } 558 entrySpec.put("purpose", entry.getPurpose()); 559 } 560 561 if (entry.getAdditionalInformation() != null) { 562 if (entrySpec == null) { 563 entrySpec = new JSONObject(); 564 } 565 for (Map.Entry<String, Object> additionalInformationEntry : entry.getAdditionalInformation().entrySet()) { 566 entrySpec.put(additionalInformationEntry.getKey(), additionalInformationEntry.getValue()); 567 } 568 } 569 570 o.put(entry.getClaimName(true), entrySpec); 571 } 572 573 return o; 574 } 575 576 577 /** 578 * Parses a collection of individual claim requests from the 579 * specified JSON object. Request entries that are not 580 * understood are silently ignored. 581 * 582 * @param jsonObject The JSON object to parse. Must not be 583 * {@code null}. 584 * 585 * @return The collection of claim requests. 586 */ 587 public static Collection<Entry> parseEntries(final JSONObject jsonObject) { 588 589 Collection<Entry> entries = new LinkedList<>(); 590 591 if (jsonObject.isEmpty()) 592 return entries; 593 594 for (Map.Entry<String, Object> member : jsonObject.entrySet()) { 595 596 // Process the key 597 String claimNameWithOptLangTag = member.getKey(); 598 599 String claimName; 600 LangTag langTag = null; 601 602 if (claimNameWithOptLangTag.contains("#")) { 603 604 String[] parts = claimNameWithOptLangTag.split("#", 2); 605 606 claimName = parts[0]; 607 608 try { 609 langTag = LangTag.parse(parts[1]); 610 611 } catch (LangTagException e) { 612 613 // Ignore and continue 614 continue; 615 } 616 617 } else { 618 claimName = claimNameWithOptLangTag; 619 } 620 621 // Parse the optional value 622 if (member.getValue() == null) { 623 624 // Voluntary claim with no value(s) 625 entries.add(new Entry(claimName, langTag)); 626 continue; 627 } 628 629 try { 630 JSONObject entrySpec = (JSONObject) member.getValue(); 631 632 ClaimRequirement requirement = ClaimRequirement.VOLUNTARY; 633 634 if (entrySpec.containsKey("essential")) { 635 636 boolean isEssential = (Boolean) entrySpec.get("essential"); 637 638 if (isEssential) 639 requirement = ClaimRequirement.ESSENTIAL; 640 } 641 642 String purpose = null; 643 if (entrySpec.containsKey("purpose")) { 644 purpose = (String) entrySpec.get("purpose"); 645 } 646 647 if (entrySpec.containsKey("value")) { 648 649 String expectedValue = (String) entrySpec.get("value"); 650 Map<String, Object> additionalInformation = getAdditionalInformationFromClaim(entrySpec); 651 entries.add(new Entry(claimName, requirement, langTag, expectedValue, null, purpose, additionalInformation)); 652 653 } else if (entrySpec.containsKey("values")) { 654 655 List<String> expectedValues = new LinkedList<>(); 656 657 for (Object v : (List) entrySpec.get("values")) { 658 659 expectedValues.add((String) v); 660 } 661 Map<String, Object> additionalInformation = getAdditionalInformationFromClaim(entrySpec); 662 663 entries.add(new Entry(claimName, requirement, langTag, null, expectedValues, purpose, additionalInformation)); 664 665 } else { 666 Map<String, Object> additionalInformation = getAdditionalInformationFromClaim(entrySpec); 667 entries.add(new Entry(claimName, requirement, langTag, null, null, purpose, additionalInformation)); 668 } 669 670 } catch (Exception e) { 671 // Ignore and continue 672 } 673 } 674 675 return entries; 676 } 677 678 679 private static Map<String, Object> getAdditionalInformationFromClaim(final JSONObject entrySpec) { 680 681 Set<String> stdKeys = new HashSet<>(Arrays.asList("essential", "value", "values", "purpose")); 682 683 Map<String, Object> additionalClaimInformation = new HashMap<>(); 684 685 for (Map.Entry<String, Object> additionalClaimInformationEntry : entrySpec.entrySet()) { 686 if (stdKeys.contains(additionalClaimInformationEntry.getKey())) { 687 continue; // skip std key 688 } 689 additionalClaimInformation.put(additionalClaimInformationEntry.getKey(), additionalClaimInformationEntry.getValue()); 690 } 691 692 return additionalClaimInformation.isEmpty() ? null : additionalClaimInformation; 693 } 694 } 695 696 697 /** 698 * The requested ID token claims, keyed by claim name and language tag. 699 */ 700 private final Map<Map.Entry<String, LangTag>, Entry> idTokenClaims = new HashMap<>(); 701 702 703 /** 704 * The requested verified ID token claims, keyed by claim name and 705 * language tag. 706 */ 707 private final Map<Map.Entry<String, LangTag>, Entry> verifiedIDTokenClaims = new HashMap<>(); 708 709 710 /** 711 * The verification element for the requested verified ID token claims. 712 */ 713 private JSONObject idTokenClaimsVerification; 714 715 716 /** 717 * The requested UserInfo claims, keyed by claim name and language tag. 718 */ 719 private final Map<Map.Entry<String, LangTag>, Entry> userInfoClaims = new HashMap<>(); 720 721 722 /** 723 * The requested verified UserInfo claims, keyed by claim name and 724 * language tag. 725 */ 726 private final Map<Map.Entry<String, LangTag>, Entry> verifiedUserInfoClaims = new HashMap<>(); 727 728 729 /** 730 * The verification element for the requested verified UserInfo claims. 731 */ 732 private JSONObject userInfoClaimsVerification; 733 734 735 /** 736 * Creates a new empty claims request. 737 */ 738 public ClaimsRequest() { 739 740 // Nothing to initialise 741 } 742 743 744 /** 745 * Adds the entries from the specified other claims request. 746 * 747 * @param other The other claims request. If {@code null} no claims 748 * request entries will be added to this claims request. 749 */ 750 public void add(final ClaimsRequest other) { 751 752 if (other == null) 753 return; 754 755 idTokenClaims.putAll(other.idTokenClaims); 756 verifiedIDTokenClaims.putAll(other.verifiedIDTokenClaims); 757 idTokenClaimsVerification = other.idTokenClaimsVerification; 758 759 userInfoClaims.putAll(other.userInfoClaims); 760 verifiedUserInfoClaims.putAll(other.verifiedUserInfoClaims); 761 userInfoClaimsVerification = other.userInfoClaimsVerification; 762 } 763 764 765 /** 766 * Adds the specified ID token claim to the request. It is marked as 767 * voluntary and no language tag and value(s) are associated with it. 768 * 769 * @param claimName The claim name. Must not be {@code null}. 770 */ 771 public void addIDTokenClaim(final String claimName) { 772 773 addIDTokenClaim(claimName, ClaimRequirement.VOLUNTARY); 774 } 775 776 777 /** 778 * Adds the specified ID token claim to the request. No language tag 779 * and value(s) are associated with it. 780 * 781 * @param claimName The claim name. Must not be {@code null}. 782 * @param requirement The claim requirement. Must not be {@code null}. 783 */ 784 public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement) { 785 786 addIDTokenClaim(claimName, requirement, null); 787 } 788 789 790 /** 791 * Adds the specified ID token claim to the request. No value(s) are 792 * associated with it. 793 * 794 * @param claimName The claim name. Must not be {@code null}. 795 * @param requirement The claim requirement. Must not be {@code null}. 796 * @param langTag The associated language tag, {@code null} if not 797 * specified. 798 */ 799 public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement, 800 final LangTag langTag) { 801 802 addIDTokenClaim(claimName, requirement, langTag, (String) null); 803 } 804 805 806 /** 807 * Adds the specified ID token claim to the request. 808 * 809 * @param claimName The claim name. Must not be {@code null}. 810 * @param requirement The claim requirement. Must not be {@code null}. 811 * @param langTag The associated language tag, {@code null} if not 812 * specified. 813 * @param value The expected claim value, {@code null} if not 814 * specified. 815 */ 816 public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement, 817 final LangTag langTag, final String value) { 818 819 addIDTokenClaim(new Entry(claimName, requirement, langTag, value)); 820 } 821 822 823 /** 824 * Adds the specified ID token claim to the request. 825 * 826 * @param claimName The claim name. Must not be 827 * {@code null}. 828 * @param requirement The claim requirement. Must not be 829 * {@code null}. 830 * @param langTag The associated language tag, 831 * {@code null} if not specified. 832 * @param value The expected claim value, {@code null} 833 * if not specified. 834 * @param additionalInformation The additional information for this 835 * claim, {@code null} if not specified. 836 */ 837 public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement, 838 final LangTag langTag, final String value, final Map<String, Object> additionalInformation) { 839 840 addIDTokenClaim(new Entry(claimName, requirement, langTag, value, null, null, additionalInformation)); 841 } 842 843 844 /** 845 * Adds the specified ID token claim to the request. 846 * 847 * @param claimName The claim name. Must not be {@code null}. 848 * @param requirement The claim requirement. Must not be {@code null}. 849 * @param langTag The associated language tag, {@code null} if not 850 * specified. 851 * @param values The expected claim values, {@code null} if not 852 * specified. 853 */ 854 public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement, 855 final LangTag langTag, final List<String> values) { 856 857 addIDTokenClaim(new Entry(claimName, requirement, langTag, values)); 858 } 859 860 861 /** 862 * Adds the specified ID token claim to the request. 863 * 864 * @param claimName The claim name. Must not be 865 * {@code null}. 866 * @param requirement The claim requirement. Must not be 867 * {@code null}. 868 * @param langTag The associated language tag, 869 * {@code null} if not specified. 870 * @param values The expected claim values, {@code null} 871 * if not specified. 872 * @param additionalInformation The additional information for this 873 * claim, {@code null} if not specified. 874 */ 875 public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement, 876 final LangTag langTag, final List<String> values, final Map<String, Object> additionalInformation) { 877 878 addIDTokenClaim(new Entry(claimName, requirement, langTag, null, values, null, additionalInformation)); 879 } 880 881 882 private static Map.Entry<String, LangTag> toKey(final Entry entry) { 883 884 return new AbstractMap.SimpleImmutableEntry<>( 885 entry.getClaimName(), 886 entry.getLangTag()); 887 } 888 889 890 /** 891 * Adds the specified ID token claim to the request. 892 * 893 * @param entry The individual ID token claim request. Must not be 894 * {@code null}. 895 */ 896 public void addIDTokenClaim(final Entry entry) { 897 898 idTokenClaims.put(toKey(entry), entry); 899 } 900 901 902 /** 903 * Adds the specified verified ID token claim to the request. 904 * 905 * @param entry The individual verified ID token claim request. Must 906 * not be {@code null}. 907 */ 908 public void addVerifiedIDTokenClaim(final Entry entry) { 909 910 verifiedIDTokenClaims.put(toKey(entry), entry); 911 } 912 913 914 /** 915 * Sets the {@code verification} element for the requested verified ID 916 * token claims. 917 * 918 * @param jsonObject The {@code verification} JSON object, {@code null} 919 * if not specified. 920 */ 921 public void setIDTokenClaimsVerificationJSONObject(final JSONObject jsonObject) { 922 923 this.idTokenClaimsVerification = jsonObject; 924 } 925 926 927 /** 928 * Gets the {@code verification} element for the requested verified ID 929 * token claims. 930 * 931 * @return The {@code verification} JSON object, {@code null} if not 932 * specified. 933 */ 934 public JSONObject getIDTokenClaimsVerificationJSONObject() { 935 936 return idTokenClaimsVerification; 937 } 938 939 940 /** 941 * Gets the requested ID token claims. 942 * 943 * @return The ID token claims, as an unmodifiable collection, empty 944 * set if none. 945 */ 946 public Collection<Entry> getIDTokenClaims() { 947 948 return Collections.unmodifiableCollection(idTokenClaims.values()); 949 } 950 951 952 /** 953 * Gets the requested verified ID token claims. 954 * 955 * @return The verified ID token claims, as an unmodifiable collection, 956 * empty set if none. 957 */ 958 public Collection<Entry> getVerifiedIDTokenClaims() { 959 960 return Collections.unmodifiableCollection(verifiedIDTokenClaims.values()); 961 } 962 963 964 private static Set<String> getClaimNames(final Map<Map.Entry<String, LangTag>, Entry> claims, final boolean withLangTag) { 965 966 Set<String> names = new HashSet<>(); 967 968 for (Entry en : claims.values()) 969 names.add(en.getClaimName(withLangTag)); 970 971 return Collections.unmodifiableSet(names); 972 } 973 974 975 /** 976 * Gets the names of the requested ID token claim names. 977 * 978 * @param withLangTag If {@code true} the language tags, if any, will 979 * be appended to the names, else not. 980 * 981 * @return The ID token claim names, as an unmodifiable set, empty set 982 * if none. 983 */ 984 public Set<String> getIDTokenClaimNames(final boolean withLangTag) { 985 986 return getClaimNames(idTokenClaims, withLangTag); 987 } 988 989 990 /** 991 * Gets the names of the requested verified ID token claim names. 992 * 993 * @param withLangTag If {@code true} the language tags, if any, will 994 * be appended to the names, else not. 995 * 996 * @return The ID token claim names, as an unmodifiable set, empty set 997 * if none. 998 */ 999 public Set<String> getVerifiedIDTokenClaimNames(final boolean withLangTag) { 1000 1001 return getClaimNames(verifiedIDTokenClaims, withLangTag); 1002 } 1003 1004 1005 private static Map.Entry<String, LangTag> toKey(final String claimName, final LangTag langTag) { 1006 1007 return new AbstractMap.SimpleImmutableEntry<>(claimName, langTag); 1008 } 1009 1010 1011 /** 1012 * Removes the specified ID token claim from the request. 1013 * 1014 * @param claimName The claim name. Must not be {@code null}. 1015 * @param langTag The associated language tag, {@code null} if none. 1016 * 1017 * @return The removed ID token claim, {@code null} if not found. 1018 */ 1019 public Entry removeIDTokenClaim(final String claimName, final LangTag langTag) { 1020 1021 return idTokenClaims.remove(toKey(claimName, langTag)); 1022 } 1023 1024 1025 /** 1026 * Removes the specified verified ID token claim from the request. 1027 * 1028 * @param claimName The claim name. Must not be {@code null}. 1029 * @param langTag The associated language tag, {@code null} if none. 1030 * 1031 * @return The removed ID token claim, {@code null} if not found. 1032 */ 1033 public Entry removeVerifiedIDTokenClaim(final String claimName, final LangTag langTag) { 1034 1035 return verifiedIDTokenClaims.remove(toKey(claimName, langTag)); 1036 } 1037 1038 1039 private static Collection<Entry> removeClaims(final Map<Map.Entry<String, LangTag>, Entry> claims, final String claimName) { 1040 1041 Collection<Entry> removedClaims = new LinkedList<>(); 1042 1043 Iterator<Map.Entry<Map.Entry<String, LangTag>, Entry>> it = claims.entrySet().iterator(); 1044 1045 while (it.hasNext()) { 1046 1047 Map.Entry<Map.Entry<String, LangTag>, Entry> reqEntry = it.next(); 1048 1049 if (reqEntry.getKey().getKey().equals(claimName)) { 1050 1051 removedClaims.add(reqEntry.getValue()); 1052 1053 it.remove(); 1054 } 1055 } 1056 1057 return Collections.unmodifiableCollection(removedClaims); 1058 } 1059 1060 1061 /** 1062 * Removes the specified ID token claims from the request, in all 1063 * existing language tag variations. 1064 * 1065 * @param claimName The claim name. Must not be {@code null}. 1066 * 1067 * @return The removed ID token claims, as an unmodifiable collection, 1068 * empty set if none were found. 1069 */ 1070 public Collection<Entry> removeIDTokenClaims(final String claimName) { 1071 1072 return removeClaims(idTokenClaims, claimName); 1073 } 1074 1075 1076 /** 1077 * Removes the specified verified ID token claims from the request, in 1078 * all existing language tag variations. 1079 * 1080 * @param claimName The claim name. Must not be {@code null}. 1081 * 1082 * @return The removed ID token claims, as an unmodifiable collection, 1083 * empty set if none were found. 1084 */ 1085 public Collection<Entry> removeVerifiedIDTokenClaims(final String claimName) { 1086 1087 return removeClaims(verifiedIDTokenClaims, claimName); 1088 } 1089 1090 1091 /** 1092 * Adds the specified UserInfo claim to the request. It is marked as 1093 * voluntary and no language tag and value(s) are associated with it. 1094 * 1095 * @param claimName The claim name. Must not be {@code null}. 1096 */ 1097 public void addUserInfoClaim(final String claimName) { 1098 1099 addUserInfoClaim(claimName, ClaimRequirement.VOLUNTARY); 1100 } 1101 1102 1103 /** 1104 * Adds the specified UserInfo claim to the request. No language tag and 1105 * value(s) are associated with it. 1106 * 1107 * @param claimName The claim name. Must not be {@code null}. 1108 * @param requirement The claim requirement. Must not be {@code null}. 1109 */ 1110 public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement) { 1111 1112 addUserInfoClaim(claimName, requirement, null); 1113 } 1114 1115 1116 /** 1117 * Adds the specified UserInfo claim to the request. No value(s) are 1118 * associated with it. 1119 * 1120 * @param claimName The claim name. Must not be {@code null}. 1121 * @param requirement The claim requirement. Must not be {@code null}. 1122 * @param langTag The associated language tag, {@code null} if not 1123 * specified. 1124 */ 1125 public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement, 1126 final LangTag langTag) { 1127 1128 1129 addUserInfoClaim(claimName, requirement, langTag, (String) null); 1130 } 1131 1132 1133 /** 1134 * Adds the specified UserInfo claim to the request. 1135 * 1136 * @param claimName The claim name. Must not be {@code null}. 1137 * @param requirement The claim requirement. Must not be {@code null}. 1138 * @param langTag The associated language tag, {@code null} if not 1139 * specified. 1140 * @param value The expected claim value, {@code null} if not 1141 * specified. 1142 */ 1143 public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement, 1144 final LangTag langTag, final String value) { 1145 1146 addUserInfoClaim(new Entry(claimName, requirement, langTag, value)); 1147 } 1148 1149 1150 /** 1151 * Adds the specified UserInfo claim to the request. 1152 * 1153 * @param claimName The claim name. Must not be {@code 1154 * null}. 1155 * @param requirement The claim requirement. Must not be 1156 * {@code null}. 1157 * @param langTag The associated language tag, {@code 1158 * null} if not specified. 1159 * @param value The expected claim value, {@code null} 1160 * if not specified. 1161 * @param additionalInformation The additional information for this 1162 * claim, {@code null} if not specified. 1163 */ 1164 public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement, 1165 final LangTag langTag, final String value, final Map<String, Object> additionalInformation) { 1166 1167 addUserInfoClaim(new Entry(claimName, requirement, langTag, value, null, null, additionalInformation)); 1168 } 1169 1170 1171 /** 1172 * Adds the specified UserInfo claim to the request. 1173 * 1174 * @param claimName The claim name. Must not be {@code null}. 1175 * @param requirement The claim requirement. Must not be {@code null}. 1176 * @param langTag The associated language tag, {@code null} if not 1177 * specified. 1178 * @param values The expected claim values, {@code null} if not 1179 * specified. 1180 */ 1181 public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement, 1182 final LangTag langTag, final List<String> values) { 1183 1184 addUserInfoClaim(new Entry(claimName, requirement, langTag, values)); 1185 } 1186 1187 1188 /** 1189 * Adds the specified UserInfo claim to the request. 1190 * 1191 * @param claimName The claim name. Must not be 1192 * {@code null}. 1193 * @param requirement The claim requirement. Must not be 1194 * {@code null}. 1195 * @param langTag The associated language tag, 1196 * {@code null} if not specified. 1197 * @param values The expected claim values, {@code null} 1198 * if not specified. 1199 * @param additionalInformation The additional information for this 1200 * claim, {@code null} if not specified. 1201 */ 1202 public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement, 1203 final LangTag langTag, final List<String> values, final Map<String, Object> additionalInformation) { 1204 1205 addUserInfoClaim(new Entry(claimName, requirement, langTag, null, values, null, additionalInformation)); 1206 } 1207 1208 1209 /** 1210 * Adds the specified UserInfo claim to the request. 1211 * 1212 * @param entry The individual UserInfo claim request. Must not be 1213 * {@code null}. 1214 */ 1215 public void addUserInfoClaim(final Entry entry) { 1216 1217 userInfoClaims.put(toKey(entry), entry); 1218 } 1219 1220 1221 /** 1222 * Adds the specified verified UserInfo claim to the request. 1223 * 1224 * @param entry The individual verified UserInfo claim request. Must 1225 * not be {@code null}. 1226 */ 1227 public void addVerifiedUserInfoClaim(final Entry entry) { 1228 1229 verifiedUserInfoClaims.put(toKey(entry), entry); 1230 } 1231 1232 1233 /** 1234 * Sets the {@code verification} element for the requested verified 1235 * UserInfo claims. 1236 * 1237 * @param jsonObject The {@code verification} JSON object, {@code null} 1238 * if not specified. 1239 */ 1240 public void setUserInfoClaimsVerificationJSONObject(final JSONObject jsonObject) { 1241 1242 this.userInfoClaimsVerification = jsonObject; 1243 } 1244 1245 1246 /** 1247 * Gets the {@code verification} element for the requested verified 1248 * UserInfo claims. 1249 * 1250 * @return The {@code verification} JSON object, {@code null} if not 1251 * specified. 1252 */ 1253 public JSONObject getUserInfoClaimsVerificationJSONObject() { 1254 1255 return userInfoClaimsVerification; 1256 } 1257 1258 1259 /** 1260 * Gets the requested UserInfo claims. 1261 * 1262 * @return The UserInfo claims, as an unmodifiable collection, empty 1263 * set if none. 1264 */ 1265 public Collection<Entry> getUserInfoClaims() { 1266 1267 return Collections.unmodifiableCollection(userInfoClaims.values()); 1268 } 1269 1270 1271 /** 1272 * Gets the requested verified UserInfo claims. 1273 * 1274 * @return The UserInfo claims, as an unmodifiable collection, empty 1275 * set if none. 1276 */ 1277 public Collection<Entry> getVerifiedUserInfoClaims() { 1278 1279 return Collections.unmodifiableCollection(verifiedUserInfoClaims.values()); 1280 } 1281 1282 1283 /** 1284 * Gets the names of the requested UserInfo claim names. 1285 * 1286 * @param withLangTag If {@code true} the language tags, if any, will 1287 * be appended to the names, else not. 1288 * 1289 * @return The UserInfo claim names, as an unmodifiable set, empty set 1290 * if none. 1291 */ 1292 public Set<String> getUserInfoClaimNames(final boolean withLangTag) { 1293 1294 return getClaimNames(userInfoClaims, withLangTag); 1295 } 1296 1297 1298 /** 1299 * Gets the names of the requested verified UserInfo claim names. 1300 * 1301 * @param withLangTag If {@code true} the language tags, if any, will 1302 * be appended to the names, else not. 1303 * 1304 * @return The UserInfo claim names, as an unmodifiable set, empty set 1305 * if none. 1306 */ 1307 public Set<String> getVerifiedUserInfoClaimNames(final boolean withLangTag) { 1308 1309 return getClaimNames(verifiedUserInfoClaims, withLangTag); 1310 } 1311 1312 1313 /** 1314 * Removes the specified UserInfo claim from the request. 1315 * 1316 * @param claimName The claim name. Must not be {@code null}. 1317 * @param langTag The associated language tag, {@code null} if none. 1318 * 1319 * @return The removed UserInfo claim, {@code null} if not found. 1320 */ 1321 public Entry removeUserInfoClaim(final String claimName, final LangTag langTag) { 1322 1323 return userInfoClaims.remove(toKey(claimName, langTag)); 1324 } 1325 1326 1327 /** 1328 * Removes the specified verified UserInfo claim from the request. 1329 * 1330 * @param claimName The claim name. Must not be {@code null}. 1331 * @param langTag The associated language tag, {@code null} if none. 1332 * 1333 * @return The removed UserInfo claim, {@code null} if not found. 1334 */ 1335 public Entry removeVerifiedUserInfoClaim(final String claimName, final LangTag langTag) { 1336 1337 return verifiedUserInfoClaims.remove(toKey(claimName, langTag)); 1338 } 1339 1340 1341 /** 1342 * Removes the specified UserInfo claims from the request, in all 1343 * existing language tag variations. 1344 * 1345 * @param claimName The claim name. Must not be {@code null}. 1346 * 1347 * @return The removed UserInfo claims, as an unmodifiable collection, 1348 * empty set if none were found. 1349 */ 1350 public Collection<Entry> removeUserInfoClaims(final String claimName) { 1351 1352 return removeClaims(userInfoClaims, claimName); 1353 } 1354 1355 1356 /** 1357 * Removes the specified verified UserInfo claims from the request, in 1358 * all existing language tag variations. 1359 * 1360 * @param claimName The claim name. Must not be {@code null}. 1361 * 1362 * @return The removed UserInfo claims, as an unmodifiable collection, 1363 * empty set if none were found. 1364 */ 1365 public Collection<Entry> removeVerifiedUserInfoClaims(final String claimName) { 1366 1367 return removeClaims(verifiedUserInfoClaims, claimName); 1368 } 1369 1370 1371 /** 1372 * Returns the JSON object representation of this claims request. 1373 * 1374 * <p>Example: 1375 * 1376 * <pre> 1377 * { 1378 * "userinfo": 1379 * { 1380 * "given_name": {"essential": true}, 1381 * "nickname": null, 1382 * "email": {"essential": true}, 1383 * "email_verified": {"essential": true}, 1384 * "picture": null, 1385 * "http://example.info/claims/groups": null 1386 * }, 1387 * "id_token": 1388 * { 1389 * "auth_time": {"essential": true}, 1390 * "acr": {"values": ["urn:mace:incommon:iap:silver"] } 1391 * } 1392 * } 1393 * </pre> 1394 * 1395 * @return The corresponding JSON object, empty if no ID token and 1396 * UserInfo claims are specified. 1397 */ 1398 public JSONObject toJSONObject() { 1399 1400 JSONObject o = new JSONObject(); 1401 1402 if (! getIDTokenClaims().isEmpty()) { 1403 1404 o.put("id_token", Entry.toJSONObject(getIDTokenClaims())); 1405 } 1406 1407 if (! getVerifiedIDTokenClaims().isEmpty()) { 1408 1409 JSONObject idTokenObject; 1410 if (o.get("id_token") != null) { 1411 idTokenObject = (JSONObject) o.get("id_token"); 1412 } else { 1413 idTokenObject = new JSONObject(); 1414 } 1415 1416 JSONObject verifiedClaims = new JSONObject(); 1417 1418 verifiedClaims.put("claims", Entry.toJSONObject(getVerifiedIDTokenClaims())); 1419 1420 if (getIDTokenClaimsVerificationJSONObject() != null) { 1421 verifiedClaims.put("verification", getIDTokenClaimsVerificationJSONObject()); 1422 } 1423 1424 idTokenObject.put("verified_claims", verifiedClaims); 1425 o.put("id_token", idTokenObject); 1426 } 1427 1428 if (! getUserInfoClaims().isEmpty()) { 1429 1430 o.put("userinfo", Entry.toJSONObject(getUserInfoClaims())); 1431 } 1432 1433 if (! getVerifiedUserInfoClaims().isEmpty()) { 1434 1435 JSONObject userInfoObject; 1436 if (o.get("userinfo") != null) { 1437 userInfoObject = (JSONObject) o.get("userinfo"); 1438 } else { 1439 userInfoObject = new JSONObject(); 1440 } 1441 1442 JSONObject verifiedClaims = new JSONObject(); 1443 1444 verifiedClaims.put("claims", Entry.toJSONObject(getVerifiedUserInfoClaims())); 1445 1446 if (getUserInfoClaimsVerificationJSONObject() != null) { 1447 verifiedClaims.put("verification", getUserInfoClaimsVerificationJSONObject()); 1448 } 1449 1450 userInfoObject.put("verified_claims", verifiedClaims); 1451 o.put("userinfo", userInfoObject); 1452 } 1453 1454 return o; 1455 } 1456 1457 1458 @Override 1459 public String toJSONString() { 1460 return toJSONObject().toJSONString(); 1461 } 1462 1463 1464 @Override 1465 public String toString() { 1466 1467 return toJSONString(); 1468 } 1469 1470 1471 /** 1472 * Resolves the claims request for the specified response type and 1473 * scope. The scope values that are {@link OIDCScopeValue standard 1474 * OpenID scope values} are resolved to their respective individual 1475 * claims requests, any other scope values are ignored. 1476 * 1477 * @param responseType The response type. Must not be {@code null}. 1478 * @param scope The scope, {@code null} if not specified (for a 1479 * plain OAuth 2.0 authorisation request with no 1480 * scope explicitly specified). 1481 * 1482 * @return The claims request. 1483 */ 1484 public static ClaimsRequest resolve(final ResponseType responseType, final Scope scope) { 1485 1486 return resolve(responseType, scope, Collections.<Scope.Value, Set<String>>emptyMap()); 1487 } 1488 1489 1490 /** 1491 * Resolves the claims request for the specified response type and 1492 * scope. The scope values that are {@link OIDCScopeValue standard 1493 * OpenID scope values} are resolved to their respective individual 1494 * claims requests, any other scope values are checked in the specified 1495 * custom claims map and resolved accordingly. 1496 * 1497 * @param responseType The response type. Must not be {@code null}. 1498 * @param scope The scope, {@code null} if not specified (for a 1499 * plain OAuth 2.0 authorisation request with no 1500 * scope explicitly specified). 1501 * @param customClaims Custom scope value to set of claim names map, 1502 * {@code null} if not specified. 1503 * 1504 * @return The claims request. 1505 */ 1506 public static ClaimsRequest resolve(final ResponseType responseType, 1507 final Scope scope, 1508 final Map<Scope.Value, Set<String>> customClaims) { 1509 1510 // Determine the claims target (ID token or UserInfo) 1511 final boolean switchToIDToken = 1512 responseType.contains(OIDCResponseTypeValue.ID_TOKEN) && 1513 !responseType.contains(ResponseType.Value.CODE) && 1514 !responseType.contains(ResponseType.Value.TOKEN); 1515 1516 ClaimsRequest claimsRequest = new ClaimsRequest(); 1517 1518 if (scope == null) { 1519 // Plain OAuth 2.0 mode 1520 return claimsRequest; 1521 } 1522 1523 for (Scope.Value value : scope) { 1524 1525 Set<ClaimsRequest.Entry> entries; 1526 1527 if (value.equals(OIDCScopeValue.PROFILE)) { 1528 1529 entries = OIDCScopeValue.PROFILE.toClaimsRequestEntries(); 1530 1531 } else if (value.equals(OIDCScopeValue.EMAIL)) { 1532 1533 entries = OIDCScopeValue.EMAIL.toClaimsRequestEntries(); 1534 1535 } else if (value.equals(OIDCScopeValue.PHONE)) { 1536 1537 entries = OIDCScopeValue.PHONE.toClaimsRequestEntries(); 1538 1539 } else if (value.equals(OIDCScopeValue.ADDRESS)) { 1540 1541 entries = OIDCScopeValue.ADDRESS.toClaimsRequestEntries(); 1542 1543 } else if (customClaims != null && customClaims.containsKey(value)) { 1544 1545 // Process custom scope value -> claim names expansion, e.g. 1546 // "corp_profile" -> ["employeeNumber", "dept", "ext"] 1547 Set<String> claimNames = customClaims.get(value); 1548 1549 if (claimNames == null || claimNames.isEmpty()) { 1550 continue; // skip 1551 } 1552 1553 entries = new HashSet<>(); 1554 1555 for (String claimName: claimNames) { 1556 entries.add(new ClaimsRequest.Entry(claimName, ClaimRequirement.VOLUNTARY)); 1557 } 1558 1559 } else { 1560 1561 continue; // skip 1562 } 1563 1564 for (ClaimsRequest.Entry en : entries) { 1565 1566 if (switchToIDToken) 1567 claimsRequest.addIDTokenClaim(en); 1568 else 1569 claimsRequest.addUserInfoClaim(en); 1570 } 1571 } 1572 1573 return claimsRequest; 1574 } 1575 1576 1577 /** 1578 * Resolves the merged claims request from the specified OpenID 1579 * authentication request parameters. The scope values that are {@link 1580 * OIDCScopeValue standard OpenID scope values} are resolved to their 1581 * respective individual claims requests, any other scope values are 1582 * ignored. 1583 * 1584 * @param responseType The response type. Must not be {@code null}. 1585 * @param scope The scope, {@code null} if not specified (for a 1586 * plain OAuth 2.0 authorisation request with no 1587 * scope explicitly specified). 1588 * @param claimsRequest The claims request, corresponding to the 1589 * optional {@code claims} OpenID Connect 1590 * authorisation request parameter, {@code null} 1591 * if not specified. 1592 * 1593 * @return The merged claims request. 1594 */ 1595 public static ClaimsRequest resolve(final ResponseType responseType, 1596 final Scope scope, 1597 final ClaimsRequest claimsRequest) { 1598 1599 return resolve(responseType, scope, claimsRequest, Collections.<Scope.Value, Set<String>>emptyMap()); 1600 } 1601 1602 1603 /** 1604 * Resolves the merged claims request from the specified OpenID 1605 * authentication request parameters. The scope values that are {@link 1606 * OIDCScopeValue standard OpenID scope values} are resolved to their 1607 * respective individual claims requests, any other scope values are 1608 * checked in the specified custom claims map and resolved accordingly. 1609 * 1610 * @param responseType The response type. Must not be {@code null}. 1611 * @param scope The scope, {@code null} if not specified (for a 1612 * plain OAuth 2.0 authorisation request with no 1613 * scope explicitly specified). 1614 * @param claimsRequest The claims request, corresponding to the 1615 * optional {@code claims} OpenID Connect 1616 * authorisation request parameter, {@code null} 1617 * if not specified. 1618 * @param customClaims Custom scope value to set of claim names map, 1619 * {@code null} if not specified. 1620 * 1621 * @return The merged claims request. 1622 */ 1623 public static ClaimsRequest resolve(final ResponseType responseType, 1624 final Scope scope, 1625 final ClaimsRequest claimsRequest, 1626 final Map<Scope.Value, Set<String>> customClaims) { 1627 1628 ClaimsRequest mergedClaimsRequest = resolve(responseType, scope, customClaims); 1629 1630 mergedClaimsRequest.add(claimsRequest); 1631 1632 return mergedClaimsRequest; 1633 } 1634 1635 1636 /** 1637 * Resolves the merged claims request for the specified OpenID 1638 * authentication request. The scope values that are {@link 1639 * OIDCScopeValue standard OpenID scope values} are resolved to their 1640 * respective individual claims requests, any other scope values are 1641 * ignored. 1642 * 1643 * @param authRequest The OpenID authentication request. Must not be 1644 * {@code null}. 1645 * 1646 * @return The merged claims request. 1647 */ 1648 public static ClaimsRequest resolve(final AuthenticationRequest authRequest) { 1649 1650 return resolve(authRequest.getResponseType(), authRequest.getScope(), authRequest.getClaims()); 1651 } 1652 1653 1654 private static JSONObject parseFirstVerifiedClaimsObject(final JSONObject containingObject) 1655 throws ParseException { 1656 1657 if (containingObject.get("verified_claims") instanceof JSONObject) { 1658 // JSON object is the simple case 1659 return JSONObjectUtils.getJSONObject(containingObject, "verified_claims"); 1660 } 1661 1662 if (containingObject.get("verified_claims") instanceof JSONArray) { 1663 // Try JSON array, take first element, ignore rest (use new OIDCClaimsRequest class to handle this case) 1664 List<JSONObject> elements = JSONArrayUtils.toJSONObjectList(JSONObjectUtils.getJSONArray(containingObject, "verified_claims")); 1665 if (elements.size() > 0) { 1666 return elements.get(0); 1667 } 1668 } 1669 1670 return null; 1671 } 1672 1673 1674 /** 1675 * Parses a claims request from the specified JSON object 1676 * representation. Unexpected members in the JSON object are silently 1677 * ignored. 1678 * 1679 * @param jsonObject The JSON object to parse. Must not be 1680 * {@code null}. 1681 * 1682 * @return The claims request. 1683 * 1684 * @throws ParseException If parsing failed. 1685 */ 1686 public static ClaimsRequest parse(final JSONObject jsonObject) 1687 throws ParseException { 1688 1689 ClaimsRequest claimsRequest = new ClaimsRequest(); 1690 1691 try { 1692 JSONObject idTokenObject = JSONObjectUtils.getJSONObject(jsonObject, "id_token", null); 1693 1694 if (idTokenObject != null) { 1695 1696 for (Entry entry : Entry.parseEntries(idTokenObject)) { 1697 if ("verified_claims".equals(entry.getClaimName())) { 1698 continue; //skip 1699 } 1700 claimsRequest.addIDTokenClaim(entry); 1701 } 1702 1703 JSONObject verifiedClaimsObject = parseFirstVerifiedClaimsObject(idTokenObject); 1704 1705 if (verifiedClaimsObject != null) { 1706 // id_token -> verified_claims -> claims 1707 JSONObject claimsObject = JSONObjectUtils.getJSONObject(verifiedClaimsObject, "claims", null); 1708 if (claimsObject != null) { 1709 1710 if (claimsObject.isEmpty()) { 1711 String msg = "Invalid claims object: Empty verification claims object"; 1712 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 1713 } 1714 1715 for (Entry entry : Entry.parseEntries(claimsObject)) { 1716 claimsRequest.addVerifiedIDTokenClaim(entry); 1717 } 1718 } 1719 // id_token -> verified_claims -> verification 1720 claimsRequest.setIDTokenClaimsVerificationJSONObject(JSONObjectUtils.getJSONObject(verifiedClaimsObject, "verification", null)); 1721 } 1722 } 1723 1724 JSONObject userInfoObject = JSONObjectUtils.getJSONObject(jsonObject, "userinfo", null); 1725 1726 if (userInfoObject != null) { 1727 1728 for (Entry entry : Entry.parseEntries(userInfoObject)) { 1729 if ("verified_claims".equals(entry.getClaimName())) { 1730 continue; //skip 1731 } 1732 claimsRequest.addUserInfoClaim(entry); 1733 } 1734 1735 JSONObject verifiedClaimsObject = parseFirstVerifiedClaimsObject(userInfoObject); 1736 1737 if (verifiedClaimsObject != null) { 1738 // userinfo -> verified_claims -> claims 1739 JSONObject claimsObject = JSONObjectUtils.getJSONObject(verifiedClaimsObject, "claims", null); 1740 1741 if (claimsObject != null) { 1742 1743 if (claimsObject.isEmpty()) { 1744 String msg = "Invalid claims object: Empty verification claims object"; 1745 throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); 1746 } 1747 1748 for (Entry entry : Entry.parseEntries(claimsObject)) { 1749 claimsRequest.addVerifiedUserInfoClaim(entry); 1750 } 1751 } 1752 // userinfo -> verified_claims -> verification 1753 claimsRequest.setUserInfoClaimsVerificationJSONObject(JSONObjectUtils.getJSONObject(verifiedClaimsObject, "verification", null)); 1754 } 1755 } 1756 1757 } catch (Exception e) { 1758 1759 if (e instanceof ParseException) { 1760 throw e; 1761 } 1762 } 1763 1764 return claimsRequest; 1765 } 1766 1767 1768 /** 1769 * Parses a claims request from the specified JSON object string 1770 * representation. Unexpected members in the JSON object are silently 1771 * ignored. 1772 * 1773 * @param json The JSON object string to parse. Must not be 1774 * {@code null}. 1775 * 1776 * @return The claims request. 1777 * 1778 * @throws ParseException If the string couldn't be parsed to a valid 1779 * JSON object. 1780 */ 1781 public static ClaimsRequest parse(final String json) 1782 throws ParseException { 1783 1784 return parse(JSONObjectUtils.parse(json)); 1785 } 1786}