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