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