001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2020, 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.claims; 019 020 021import java.util.*; 022 023import net.jcip.annotations.Immutable; 024import net.minidev.json.JSONAware; 025import net.minidev.json.JSONObject; 026 027import com.nimbusds.langtag.LangTag; 028import com.nimbusds.langtag.LangTagException; 029import com.nimbusds.oauth2.sdk.ParseException; 030import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 031 032 033/** 034 * OpenID Connect claims set request, intended to represent the 035 * {@code userinfo} and {@code id_token} elements in a 036 * {@link com.nimbusds.openid.connect.sdk.OIDCClaimsRequest claims} request 037 * parameter. 038 * 039 * <p>Example: 040 * 041 * <pre> 042 * { 043 * "given_name": {"essential": true}, 044 * "nickname": null, 045 * "email": {"essential": true}, 046 * "email_verified": {"essential": true}, 047 * "picture": null, 048 * "http://example.info/claims/groups": null 049 * } 050 * </pre> 051 * 052 * <p>Related specifications: 053 * 054 * <ul> 055 * <li>OpenID Connect Core 1.0, section 5.5. 056 * <li>OpenID Connect for Identity Assurance 1.0. 057 * </ul> 058 */ 059@Immutable 060public class ClaimsSetRequest implements JSONAware { 061 062 063 /** 064 * Individual OpenID claim request. 065 * 066 * <p>Related specifications: 067 * 068 * <ul> 069 * <li>OpenID Connect Core 1.0, section 5.5.1. 070 * <li>OpenID Connect for Identity Assurance 1.0. 071 * </ul> 072 */ 073 @Immutable 074 public static class Entry { 075 076 077 /** 078 * The claim name. 079 */ 080 private final String claimName; 081 082 083 /** 084 * The claim requirement. 085 */ 086 private final ClaimRequirement requirement; 087 088 089 /** 090 * Optional language tag. 091 */ 092 private final LangTag langTag; 093 094 095 /** 096 * Optional claim value, as string, number or JSON object. 097 */ 098 private final Object value; 099 100 101 /** 102 * Optional claim values, as an array of JSON entities. 103 */ 104 private final List<?> values; 105 106 107 /** 108 * Optional claim purpose. 109 */ 110 private final String purpose; 111 112 113 /** 114 * Optional additional claim information. 115 * 116 * <p>Example additional information in the "info" member: 117 * 118 * <pre> 119 * { 120 * "userinfo" : { 121 * "email": null, 122 * "email_verified": null, 123 * "http://example.info/claims/groups" : { "info" : "custom information" } 124 * } 125 * } 126 * </pre> 127 */ 128 private final Map<String, Object> additionalInformation; 129 130 131 /** 132 * Creates a new individual claim request. The claim 133 * requirement is set to {@link ClaimRequirement#VOLUNTARY 134 * voluntary} (the default) and no expected value(s) or other 135 * parameters are specified. 136 * 137 * @param claimName The claim name. Must not be {@code null}. 138 */ 139 public Entry(final String claimName) { 140 this(claimName, ClaimRequirement.VOLUNTARY, null, null, null, null, null); 141 } 142 143 144 /** 145 * Creates a new individual claim request. This constructor is 146 * to be used privately. Ensures that {@code value} and 147 * {@code values} are not simultaneously specified. 148 * 149 * @param claimName The claim name. Must not be 150 * {@code null}. 151 * @param requirement The claim requirement. Must not 152 * be {@code null}. 153 * @param langTag Optional language tag for the 154 * claim. 155 * @param value Optional expected value for the 156 * claim. If set, then the {@code 157 * values} parameter must not be 158 * set. 159 * @param values Optional expected values for 160 * the claim. If set, then the 161 * {@code value} parameter must 162 * not be set. 163 * @param purpose The purpose for the requested 164 * claim, {@code null} if not 165 * specified. 166 * @param additionalInformation Optional additional information 167 */ 168 private Entry(final String claimName, 169 final ClaimRequirement requirement, 170 final LangTag langTag, 171 final Object value, 172 final List<?> values, 173 final String purpose, 174 final Map<String, Object> additionalInformation) { 175 176 if (claimName == null) 177 throw new IllegalArgumentException("The claim name must not be null"); 178 179 this.claimName = claimName; 180 181 182 if (requirement == null) 183 throw new IllegalArgumentException("The claim requirement must not be null"); 184 185 this.requirement = requirement; 186 187 188 this.langTag = langTag; 189 190 191 if (value != null && values == null) { 192 193 this.value = value; 194 this.values = null; 195 196 } else if (value == null && values != null) { 197 198 this.value = null; 199 this.values = values; 200 201 } else if (value == null && values == null) { 202 203 this.value = null; 204 this.values = null; 205 206 } else { 207 208 throw new IllegalArgumentException("Either value or values must be specified, but not both"); 209 } 210 211 this.purpose = purpose; 212 213 this.additionalInformation = additionalInformation; 214 } 215 216 217 /** 218 * Returns the claim name. 219 * 220 * @return The claim name. 221 */ 222 public String getClaimName() { 223 return getClaimName(false); 224 } 225 226 227 /** 228 * Returns the claim name, optionally with the language tag 229 * appended. 230 * 231 * <p>Example with language tag: 232 * 233 * <pre> 234 * name#de-DE 235 * </pre> 236 * 237 * @param withLangTag If {@code true} the language tag will be 238 * appended to the name (if any), else not. 239 * 240 * @return The claim name, with optionally appended language 241 * tag. 242 */ 243 public String getClaimName(final boolean withLangTag) { 244 245 if (withLangTag && langTag != null) 246 return claimName + "#" + langTag; 247 else 248 return claimName; 249 } 250 251 252 /** 253 * Sets the claim requirement. 254 * 255 * @param requirement The claim requirement. Must not be 256 * {@code null}, 257 * 258 * @return The updated entry. 259 */ 260 public ClaimsSetRequest.Entry withClaimRequirement(final ClaimRequirement requirement) { 261 return new ClaimsSetRequest.Entry(claimName, requirement, langTag, value, values, purpose, additionalInformation); 262 } 263 264 265 /** 266 * Returns the claim requirement. 267 * 268 * @return The claim requirement. 269 */ 270 public ClaimRequirement getClaimRequirement() { 271 return requirement; 272 } 273 274 275 /** 276 * Sets the language tag for the claim. 277 * 278 * @param langTag The language tag, {@code null} if not 279 * specified. 280 * 281 * @return The updated entry. 282 */ 283 public ClaimsSetRequest.Entry withLangTag(final LangTag langTag) { 284 return new ClaimsSetRequest.Entry(claimName, requirement, langTag, value, values, purpose, additionalInformation); 285 } 286 287 288 /** 289 * Returns the optional language tag for the claim. 290 * 291 * @return The language tag, {@code null} if not specified. 292 */ 293 public LangTag getLangTag() { 294 return langTag; 295 } 296 297 298 /** 299 * Sets the requested value (as string) for the claim. 300 * 301 * @param value The value, {@code null} if not specified. 302 * 303 * @return The updated entry. 304 */ 305 public ClaimsSetRequest.Entry withValue(final String value) { 306 return new ClaimsSetRequest.Entry(claimName, requirement, langTag, value, null, purpose, additionalInformation); 307 } 308 309 310 /** 311 * Sets the requested value (as number) for the claim. 312 * 313 * @param value The value, {@code null} if not specified. 314 * 315 * @return The updated entry. 316 */ 317 public ClaimsSetRequest.Entry withValue(final Number value) { 318 return new ClaimsSetRequest.Entry(claimName, requirement, langTag, value, null, purpose, additionalInformation); 319 } 320 321 322 /** 323 * Sets the requested value (as JSON object) for the claim. 324 * 325 * @param value The value, {@code null} if not specified. 326 * 327 * @return The updated entry. 328 */ 329 public ClaimsSetRequest.Entry withValue(final JSONObject value) { 330 return new ClaimsSetRequest.Entry(claimName, requirement, langTag, value, null, purpose, additionalInformation); 331 } 332 333 334 /** 335 * Sets the requested value (untyped) for the claim. 336 * 337 * @param value The value, {@code null} if not specified. 338 * 339 * @return The updated entry. 340 */ 341 public ClaimsSetRequest.Entry withValue(final Object value) { 342 return new ClaimsSetRequest.Entry(claimName, requirement, langTag, value, null, purpose, additionalInformation); 343 } 344 345 346 /** 347 * Returns the requested value (as string) for the claim. 348 * 349 * @return The value as string, {@code null} if not specified 350 * or the value isn't a string. 351 */ 352 public String getValueAsString() { 353 if (value instanceof String) { 354 return (String)value; 355 } else { 356 return null; 357 } 358 } 359 360 361 /** 362 * Returns the requested value (as string) for the claim. Use 363 * {@link #getValueAsString()} instead. 364 * 365 * @return The value as string, {@code null} if not specified 366 * or the value isn't a string. 367 */ 368 @Deprecated 369 public String getValue() { 370 return getValueAsString(); 371 } 372 373 374 /** 375 * Returns the requested value (as number) for the claim. 376 * 377 * @return The value as number, {@code null} if not specified 378 * or the value isn't a number. 379 */ 380 public Number getValueAsNumber() { 381 if (value instanceof Number) { 382 return (Number)value; 383 } else { 384 return null; 385 } 386 } 387 388 389 /** 390 * Returns the requested value (as JSON object) for the claim. 391 * 392 * @return The value as JSON object, {@code null} if not 393 * specified or the value isn't a JSON object. 394 */ 395 public JSONObject getValueAsJSONObject() { 396 if (value instanceof JSONObject) { 397 return (JSONObject)value; 398 } else { 399 return null; 400 } 401 } 402 403 404 /** 405 * Returns the requested value (untyped) for the claim. 406 * 407 * @return The value (untyped), {@code null} if not specified. 408 */ 409 public Object getRawValue() { 410 return value; 411 } 412 413 414 /** 415 * Sets the requested values (untyped) for the claim. 416 * 417 * @param values The values, {@code null} if not specified. 418 * 419 * @return The updated entry. 420 */ 421 public ClaimsSetRequest.Entry withValues(final List<?> values) { 422 return new ClaimsSetRequest.Entry(claimName, requirement, langTag, null, values, purpose, additionalInformation); 423 } 424 425 426 /** 427 * Returns the requested values (as strings) for the claim. 428 * 429 * @return The values as list of strings, {@code null} if not 430 * specified or the values aren't strings. 431 */ 432 public List<String> getValuesAsListOfStrings() { 433 if (values == null) { 434 return null; 435 } 436 if (values.isEmpty()) { 437 return Collections.emptyList(); 438 } 439 List<String> list = new ArrayList<>(values.size()); 440 for (Object v: values) { 441 if (v instanceof String) { 442 list.add((String)v); 443 } else { 444 return null; 445 } 446 } 447 return list; 448 } 449 450 451 /** 452 * Returns the requested values (as strings) for the claim. Use 453 * {@link #getValuesAsListOfStrings()} instead. 454 * 455 * @return The values as list of strings, {@code null} if not 456 * specified or the values aren't strings. 457 */ 458 @Deprecated 459 public List<String> getValues() { 460 return getValuesAsListOfStrings(); 461 } 462 463 464 /** 465 * Returns the requested values (as JSON objects) for the 466 * claim. 467 * 468 * @return The values as list of JSON objects, {@code null} if 469 * not specified or the values aren't JSON objects. 470 */ 471 public List<JSONObject> getValuesAsListOfJSONObjects() { 472 if (values == null) { 473 return null; 474 } 475 if (values.isEmpty()) { 476 return Collections.emptyList(); 477 } 478 List<JSONObject> list = new ArrayList<>(values.size()); 479 for (Object v: values) { 480 if (v instanceof JSONObject) { 481 list.add((JSONObject) v); 482 } else { 483 return null; 484 } 485 } 486 return list; 487 } 488 489 490 /** 491 * Returns the requested values (untyped) for the claim. 492 * 493 * @return The values as list of untyped objects, {@code null} 494 * if not specified. 495 */ 496 public List<?> getValuesAsRawList() { 497 return values; 498 } 499 500 501 /** 502 * Sets the purpose for which the claim is requested. 503 * 504 * @param purpose The purpose, {@code null} if not specified. 505 * 506 * @return The updated entry. 507 */ 508 public ClaimsSetRequest.Entry withPurpose(final String purpose) { 509 return new ClaimsSetRequest.Entry(claimName, requirement, langTag, value, values, purpose, additionalInformation); 510 } 511 512 513 /** 514 * Returns the optional purpose for which the claim is 515 * requested. 516 * 517 * @return The purpose, {@code null} if not specified. 518 */ 519 public String getPurpose() { 520 return purpose; 521 } 522 523 524 /** 525 * Sets additional information for the requested claim. 526 * 527 * <p>Example additional information in the "info" member: 528 * 529 * <pre> 530 * { 531 * "userinfo" : { 532 * "email": null, 533 * "email_verified": null, 534 * "http://example.info/claims/groups" : { "info" : "custom information" } 535 * } 536 * } 537 * </pre> 538 * 539 * @param additionalInformation The additional information, 540 * {@code null} if not specified. 541 * 542 * @return The updated entry. 543 */ 544 public ClaimsSetRequest.Entry withAdditionalInformation(final Map<String, Object> additionalInformation) { 545 return new ClaimsSetRequest.Entry(claimName, requirement, langTag, value, values, purpose, additionalInformation); 546 } 547 548 549 /** 550 * Returns the additional information for the claim. 551 * 552 * <p>Example additional information in the "info" member: 553 * 554 * <pre> 555 * { 556 * "userinfo" : { 557 * "email": null, 558 * "email_verified": null, 559 * "http://example.info/claims/groups" : { "info" : "custom information" } 560 * } 561 * } 562 * </pre> 563 * 564 * @return The additional information, {@code null} if not 565 * specified. 566 */ 567 public Map<String, Object> getAdditionalInformation() { 568 return additionalInformation; 569 } 570 571 572 /** 573 * Returns the JSON object entry for this individual claim 574 * request. 575 * 576 * @return The JSON object entry. 577 */ 578 public Map.Entry<String,JSONObject> toJSONObjectEntry() { 579 580 // Compose the optional value 581 JSONObject entrySpec = null; 582 583 if (getRawValue() != null) { 584 585 entrySpec = new JSONObject(); 586 entrySpec.put("value", getRawValue()); 587 } 588 589 if (getValuesAsRawList() != null) { 590 591 // Either "value" or "values", or none 592 // may be defined 593 entrySpec = new JSONObject(); 594 entrySpec.put("values", getValuesAsRawList()); 595 } 596 597 if (getClaimRequirement().equals(ClaimRequirement.ESSENTIAL)) { 598 599 if (entrySpec == null) 600 entrySpec = new JSONObject(); 601 602 entrySpec.put("essential", true); 603 } 604 605 if (getPurpose() != null) { 606 if (entrySpec == null) { 607 entrySpec = new JSONObject(); 608 } 609 entrySpec.put("purpose", getPurpose()); 610 } 611 612 if (getAdditionalInformation() != null) { 613 if (entrySpec == null) { 614 entrySpec = new JSONObject(); 615 } 616 for (Map.Entry<String, Object> additionalInformationEntry : getAdditionalInformation().entrySet()) { 617 entrySpec.put(additionalInformationEntry.getKey(), additionalInformationEntry.getValue()); 618 } 619 } 620 621 return new AbstractMap.SimpleImmutableEntry<>(getClaimName(true), entrySpec); 622 } 623 624 625 /** 626 * Parses an individual claim request from the specified JSON 627 * object entry. 628 * 629 * @param jsonObjectEntry The JSON object entry to parse. Must 630 * not be {@code null}. 631 * 632 * @return The individual claim request. 633 * 634 * @throws ParseException If parsing failed. 635 */ 636 public static ClaimsSetRequest.Entry parse(final Map.Entry<String,JSONObject> jsonObjectEntry) 637 throws ParseException { 638 639 // Process the key 640 String claimNameWithOptLangTag = jsonObjectEntry.getKey(); 641 642 String claimName; 643 LangTag langTag = null; 644 645 if (claimNameWithOptLangTag.contains("#")) { 646 647 String[] parts = claimNameWithOptLangTag.split("#", 2); 648 649 claimName = parts[0]; 650 651 try { 652 langTag = LangTag.parse(parts[1]); 653 } catch (LangTagException e) { 654 throw new ParseException(e.getMessage(), e); 655 } 656 657 } else { 658 claimName = claimNameWithOptLangTag; 659 } 660 661 // Parse the optional spec 662 663 JSONObject spec = jsonObjectEntry.getValue(); 664 665 if (spec == null) { 666 // Voluntary claim with no value(s) 667 return new ClaimsSetRequest.Entry(claimName).withLangTag(langTag); 668 } 669 670 ClaimRequirement requirement = ClaimRequirement.VOLUNTARY; 671 672 if (spec.containsKey("essential")) { 673 674 boolean isEssential = JSONObjectUtils.getBoolean(spec, "essential"); 675 676 if (isEssential) 677 requirement = ClaimRequirement.ESSENTIAL; 678 } 679 680 String purpose = JSONObjectUtils.getString(spec, "purpose", null); 681 682 if (spec.get("value") != null) { 683 684 Object expectedValue = spec.get("value"); 685 Map<String, Object> additionalInformation = getAdditionalInformationFromClaim(spec); 686 return new ClaimsSetRequest.Entry(claimName, requirement, langTag, expectedValue, null, purpose, additionalInformation); 687 688 } else if (spec.get("values") != null) { 689 690 List<Object> expectedValues = JSONObjectUtils.getList(spec, "values"); 691 Map<String, Object> additionalInformation = getAdditionalInformationFromClaim(spec); 692 return new ClaimsSetRequest.Entry(claimName, requirement, langTag, null, expectedValues, purpose, additionalInformation); 693 694 } else { 695 Map<String, Object> additionalInformation = getAdditionalInformationFromClaim(spec); 696 return new ClaimsSetRequest.Entry(claimName, requirement, langTag, null, null, purpose, additionalInformation); 697 } 698 } 699 700 701 private static Map<String, Object> getAdditionalInformationFromClaim(final JSONObject spec) { 702 703 Set<String> stdKeys = new HashSet<>(Arrays.asList("essential", "value", "values", "purpose")); 704 705 Map<String, Object> additionalClaimInformation = new HashMap<>(); 706 707 for (Map.Entry<String, Object> additionalClaimInformationEntry : spec.entrySet()) { 708 if (stdKeys.contains(additionalClaimInformationEntry.getKey())) { 709 continue; // skip std key 710 } 711 additionalClaimInformation.put(additionalClaimInformationEntry.getKey(), additionalClaimInformationEntry.getValue()); 712 } 713 714 return additionalClaimInformation.isEmpty() ? null : additionalClaimInformation; 715 } 716 } 717 718 719 /** 720 * The request entries. 721 */ 722 private final Collection<ClaimsSetRequest.Entry> entries; 723 724 725 /** 726 * Creates a new empty OpenID Connect claims set request. 727 */ 728 public ClaimsSetRequest() { 729 this(Collections.<Entry>emptyList()); 730 } 731 732 733 /** 734 * Creates a new OpenID Connect claims set request. 735 * 736 * @param entries The request entries, empty collection if none. Must 737 * not be {@code null}. 738 */ 739 public ClaimsSetRequest(final Collection<ClaimsSetRequest.Entry> entries) { 740 if (entries == null) { 741 throw new IllegalArgumentException("The entries must not be null"); 742 } 743 this.entries = Collections.unmodifiableCollection(entries); 744 } 745 746 747 /** 748 * Adds the specified claim to the request, using default settings. 749 * Shorthand for {@link #add(Entry)}. 750 * 751 * @param claimName The claim name. Must not be {@code null}. 752 * 753 * @return The updated claims set request. 754 */ 755 public ClaimsSetRequest add(final String claimName) { 756 return add(new ClaimsSetRequest.Entry(claimName)); 757 } 758 759 760 /** 761 * Adds the specified claim to the request. 762 * 763 * @param entry The individual claim request. Must not be {@code null}. 764 * 765 * @return The updated claims set request. 766 */ 767 public ClaimsSetRequest add(final ClaimsSetRequest.Entry entry) { 768 List<Entry> updatedEntries = new LinkedList<>(getEntries()); 769 updatedEntries.add(entry); 770 return new ClaimsSetRequest(updatedEntries); 771 } 772 773 774 /** 775 * Gets the request entries. 776 * 777 * @return The request entries, empty collection if none. 778 */ 779 public Collection<ClaimsSetRequest.Entry> getEntries() { 780 return entries; 781 } 782 783 784 /** 785 * Gets the names of the requested claims. 786 * 787 * @param withLangTag If {@code true} the language tags, if any, will 788 * be appended to the names, else not. 789 * 790 * @return The claim names, as an unmodifiable set, empty set if none. 791 */ 792 public Set<String> getClaimNames(final boolean withLangTag) { 793 Set<String> names = new HashSet<>(); 794 for (ClaimsSetRequest.Entry en : entries) { 795 names.add(en.getClaimName(withLangTag)); 796 } 797 return Collections.unmodifiableSet(names); 798 } 799 800 801 /** 802 * Gets the specified claim entry from this request. 803 * 804 * @param claimName The claim name. Must not be {@code null}. 805 * 806 * @return The claim entry, {@code null} if not found. 807 */ 808 public Entry get(final String claimName) { 809 810 return get(claimName, null); 811 } 812 813 814 /** 815 * Gets the specified claim entry from this request. 816 * 817 * @param claimName The claim name. Must not be {@code null}. 818 * @param langTag The associated language tag, {@code null} if none. 819 * 820 * @return The claim entry, {@code null} if not found. 821 */ 822 public Entry get(final String claimName, final LangTag langTag) { 823 824 for (ClaimsSetRequest.Entry en: getEntries()) { 825 if (claimName.equals(en.getClaimName()) && langTag == null && en.getLangTag() == null) { 826 // No lang tag 827 return en; 828 } else if (claimName.equals(en.getClaimName()) && langTag != null && langTag.equals(en.getLangTag())) { 829 // Matching lang tag 830 return en; 831 } 832 } 833 return null; 834 } 835 836 837 /** 838 * Deletes the specified claim from this request. 839 * 840 * @param claimName The claim name. Must not be {@code null}. 841 * @param langTag The associated language tag, {@code null} if none. 842 * 843 * @return The updated claims set request. 844 */ 845 public ClaimsSetRequest delete(final String claimName, final LangTag langTag) { 846 847 Collection<ClaimsSetRequest.Entry> updatedEntries = new LinkedList<>(); 848 849 for (ClaimsSetRequest.Entry en: getEntries()) { 850 if (claimName.equals(en.getClaimName()) && langTag == null && en.getLangTag() == null) { 851 // don't copy 852 } else if (claimName.equals(en.getClaimName()) && langTag != null && langTag.equals(en.getLangTag())) { 853 // don't copy 854 } else { 855 updatedEntries.add(en); 856 } 857 } 858 859 return new ClaimsSetRequest(updatedEntries); 860 } 861 862 863 /** 864 * Deletes the specified claim from this request, in all existing 865 * language tag variations if any. 866 * 867 * @param claimName The claim name. Must not be {@code null}. 868 * 869 * @return The updated claims set request. 870 */ 871 public ClaimsSetRequest delete(final String claimName) { 872 Collection<ClaimsSetRequest.Entry> updatedEntries = new LinkedList<>(); 873 874 for (ClaimsSetRequest.Entry en: getEntries()) { 875 if (claimName.equals(en.getClaimName())) { 876 // don't copy 877 } else { 878 updatedEntries.add(en); 879 } 880 } 881 882 return new ClaimsSetRequest(updatedEntries); 883 } 884 885 886 /** 887 * Returns the JSON object representation of this claims set request. 888 * 889 * <p>Example: 890 * 891 * <pre> 892 * { 893 * "given_name": {"essential": true}, 894 * "nickname": null, 895 * "email": {"essential": true}, 896 * "email_verified": {"essential": true}, 897 * "picture": null, 898 * "http://example.info/claims/groups": null 899 * } 900 * </pre> 901 * 902 * @return The JSON object, empty if no claims are specified. 903 */ 904 public JSONObject toJSONObject() { 905 JSONObject o = new JSONObject(); 906 for (ClaimsSetRequest.Entry entry : entries) { 907 Map.Entry<String, JSONObject> jsonObjectEntry = entry.toJSONObjectEntry(); 908 o.put(jsonObjectEntry.getKey(), jsonObjectEntry.getValue()); 909 } 910 return o; 911 } 912 913 914 @Override 915 public String toJSONString() { 916 return toJSONObject().toJSONString(); 917 } 918 919 920 @Override 921 public String toString() { 922 return toJSONString(); 923 } 924 925 926 /** 927 * Parses an OpenID Connect claims set request from the specified JSON 928 * object representation. 929 * 930 * @param jsonObject The JSON object to parse. Must not be 931 * {@code null}. 932 * 933 * @return The claims set request. 934 * 935 * @throws ParseException If parsing failed. 936 */ 937 public static ClaimsSetRequest parse(final JSONObject jsonObject) 938 throws ParseException { 939 940 ClaimsSetRequest claimsRequest = new ClaimsSetRequest(); 941 942 for (String key: jsonObject.keySet()) { 943 944 if ("verified_claims".equals(key)) { 945 // Implies nested VerifiedClaimsSetRequest, skip 946 continue; 947 } 948 949 JSONObject value = JSONObjectUtils.getJSONObject(jsonObject, key, null); 950 951 claimsRequest = claimsRequest.add(ClaimsSetRequest.Entry.parse(new AbstractMap.SimpleImmutableEntry<>(key, value))); 952 } 953 954 return claimsRequest; 955 } 956 957 958 /** 959 * Parses an OpenID Connect claims set request from the specified JSON 960 * object string representation. 961 * 962 * @param json The JSON object string to parse. Must not be 963 * {@code null}. 964 * 965 * @return The claims set request. 966 * 967 * @throws ParseException If parsing failed. 968 */ 969 public static ClaimsSetRequest parse(final String json) 970 throws ParseException { 971 972 return parse(JSONObjectUtils.parse(json)); 973 } 974}