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