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 com.nimbusds.langtag.LangTag; 024import com.nimbusds.langtag.LangTagException; 025import com.nimbusds.oauth2.sdk.ParseException; 026import com.nimbusds.oauth2.sdk.ResponseType; 027import com.nimbusds.oauth2.sdk.Scope; 028import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 029import com.nimbusds.openid.connect.sdk.claims.ClaimRequirement; 030import net.jcip.annotations.Immutable; 031import net.minidev.json.JSONObject; 032import org.apache.commons.lang3.tuple.ImmutablePair; 033 034 035/** 036 * Specifies the individual claims to return from the UserInfo endpoint and / 037 * or in the ID Token. 038 * 039 * <p>Related specifications: 040 * 041 * <ul> 042 * <li>OpenID Connect Core 1.0, section 5.5. 043 * </ul> 044 */ 045public class ClaimsRequest { 046 047 048 /** 049 * Individual claim request. 050 * 051 * <p>Related specifications: 052 * 053 * <ul> 054 * <li>OpenID Connect Core 1.0, section 5.5.1. 055 * </ul> 056 */ 057 @Immutable 058 public static class Entry { 059 060 061 /** 062 * The claim name. 063 */ 064 private final String claimName; 065 066 067 /** 068 * The claim requirement. 069 */ 070 private final ClaimRequirement requirement; 071 072 073 /** 074 * Optional language tag. 075 */ 076 private final LangTag langTag; 077 078 079 /** 080 * Optional claim value. 081 */ 082 private final String value; 083 084 085 /** 086 * Optional claim values. 087 */ 088 private final List<String> values; 089 090 091 /** 092 * Creates a new individual claim request. The claim 093 * requirement is set to voluntary (the default) and no 094 * expected value(s) are specified. 095 * 096 * @param claimName The claim name. Must not be {@code null}. 097 * @param langTag Optional language tag for the claim. 098 */ 099 public Entry(final String claimName, final LangTag langTag) { 100 101 this(claimName, ClaimRequirement.VOLUNTARY, langTag, null, null); 102 } 103 104 105 /** 106 * Creates a new individual claim request. 107 * 108 * @param claimName The claim name. Must not be {@code null}. 109 * @param requirement The claim requirement. Must not be 110 * {@code null}. 111 */ 112 public Entry(final String claimName, final ClaimRequirement requirement) { 113 114 this(claimName, requirement, null, null, null); 115 } 116 117 118 /** 119 * Creates a new individual claim request. 120 * 121 * @param claimName The claim name. Must not be {@code null}. 122 * @param requirement The claim requirement. Must not be 123 * {@code null}. 124 * @param langTag Optional language tag for the claim. 125 * @param value Optional expected value for the claim. 126 */ 127 public Entry(final String claimName, final ClaimRequirement requirement, 128 final LangTag langTag, final String value) { 129 130 this(claimName, requirement, langTag, value, null); 131 } 132 133 134 /** 135 * Creates a new individual claim request. 136 * 137 * @param claimName The claim name. Must not be {@code null}. 138 * @param requirement The claim requirement. Must not be 139 * {@code null}. 140 * @param langTag Optional language tag for the claim. 141 * @param values Optional expected values for the claim. 142 */ 143 public Entry(final String claimName, final ClaimRequirement requirement, 144 final LangTag langTag, final List<String> values) { 145 146 this(claimName, requirement, langTag, null, values); 147 } 148 149 150 /** 151 * Creates a new individual claim request. This constructor is 152 * to be used privately. Ensures that {@code value} and 153 * {@code values} are not simultaneously specified. 154 * 155 * @param claimName The claim name. Must not be {@code null}. 156 * @param requirement The claim requirement. Must not be 157 * {@code null}. 158 * @param langTag Optional language tag for the claim. 159 * @param value Optional expected value for the claim. If 160 * set, then the {@code values} parameter 161 * must not be set. 162 * @param values Optional expected values for the claim. 163 * If set, then the {@code value} parameter 164 * must not be set. 165 */ 166 private Entry(final String claimName, final ClaimRequirement requirement, final LangTag langTag, 167 final String value, final List<String> values) { 168 169 if (claimName == null) 170 throw new IllegalArgumentException("The claim name must not be null"); 171 172 this.claimName = claimName; 173 174 175 if (requirement == null) 176 throw new IllegalArgumentException("The claim requirement must not be null"); 177 178 this.requirement = requirement; 179 180 181 this.langTag = langTag; 182 183 184 if (value != null && values == null) { 185 186 this.value = value; 187 this.values = null; 188 189 } else if (value == null && values != null) { 190 191 this.value = null; 192 this.values = values; 193 194 } else if (value == null && values == null) { 195 196 this.value = null; 197 this.values = null; 198 199 } else { 200 201 throw new IllegalArgumentException("Either value or values must be specified, but not both"); 202 } 203 } 204 205 206 /** 207 * Gets the claim name. 208 * 209 * @return The claim name. 210 */ 211 public String getClaimName() { 212 213 return claimName; 214 } 215 216 217 /** 218 * Gets the claim name, optionally with the language tag 219 * appended. 220 * 221 * <p>Example with language tag: 222 * 223 * <pre> 224 * name#de-DE 225 * </pre> 226 * 227 * @param withLangTag If {@code true} the language tag will be 228 * appended to the name (if any), else not. 229 * 230 * @return The claim name, with optionally appended language 231 * tag. 232 */ 233 public String getClaimName(final boolean withLangTag) { 234 235 if (withLangTag && langTag != null) 236 return claimName + "#" + langTag.toString(); 237 else 238 return claimName; 239 } 240 241 242 /** 243 * Gets the claim requirement. 244 * 245 * @return The claim requirement. 246 */ 247 public ClaimRequirement getClaimRequirement() { 248 249 return requirement; 250 } 251 252 253 /** 254 * Gets the optional language tag for the claim. 255 * 256 * @return The language tag, {@code null} if not specified. 257 */ 258 public LangTag getLangTag() { 259 260 return langTag; 261 } 262 263 264 /** 265 * Gets the optional value for the claim. 266 * 267 * @return The value, {@code null} if not specified. 268 */ 269 public String getValue() { 270 271 return value; 272 } 273 274 275 /** 276 * Gets the optional values for the claim. 277 * 278 * @return The values, {@code null} if not specified. 279 */ 280 public List<String> getValues() { 281 282 return values; 283 } 284 285 286 /** 287 * Returns the JSON object representation of the specified 288 * collection of individual claim requests. 289 * 290 * <p>Example: 291 * 292 * <pre> 293 * { 294 * "given_name": {"essential": true}, 295 * "nickname": null, 296 * "email": {"essential": true}, 297 * "email_verified": {"essential": true}, 298 * "picture": null, 299 * "http://example.info/claims/groups": null 300 * } 301 * </pre> 302 * 303 * @param entries The entries to serialise. Must not be 304 * {@code null}. 305 * 306 * @return The corresponding JSON object, empty if no claims 307 * were found. 308 */ 309 public static JSONObject toJSONObject(final Collection<Entry> entries) { 310 311 JSONObject o = new JSONObject(); 312 313 for (Entry entry: entries) { 314 315 // Compose the optional value 316 JSONObject entrySpec = null; 317 318 if (entry.getValue() != null) { 319 320 entrySpec = new JSONObject(); 321 entrySpec.put("value", entry.getValue()); 322 } 323 324 if (entry.getValues() != null) { 325 326 // Either "value" or "values", or none 327 // may be defined 328 entrySpec = new JSONObject(); 329 entrySpec.put("values", entry.getValues()); 330 } 331 332 if (entry.getClaimRequirement().equals(ClaimRequirement.ESSENTIAL)) { 333 334 if (entrySpec == null) 335 entrySpec = new JSONObject(); 336 337 entrySpec.put("essential", true); 338 } 339 340 o.put(entry.getClaimName(true), entrySpec); 341 } 342 343 return o; 344 } 345 346 347 /** 348 * Parses a collection of individual claim requests from the 349 * specified JSON object. Request entries that are not 350 * understood are silently ignored. 351 */ 352 public static Collection<Entry> parseEntries(final JSONObject jsonObject) { 353 354 Collection<Entry> entries = new LinkedList<>(); 355 356 if (jsonObject.isEmpty()) 357 return entries; 358 359 for (Map.Entry<String,Object> member: jsonObject.entrySet()) { 360 361 // Process the key 362 String claimNameWithOptLangTag = member.getKey(); 363 364 String claimName; 365 LangTag langTag = null; 366 367 if (claimNameWithOptLangTag.contains("#")) { 368 369 String[] parts = claimNameWithOptLangTag.split("#", 2); 370 371 claimName = parts[0]; 372 373 try { 374 langTag = LangTag.parse(parts[1]); 375 376 } catch (LangTagException e) { 377 378 // Ignore and continue 379 continue; 380 } 381 382 } else { 383 claimName = claimNameWithOptLangTag; 384 } 385 386 // Parse the optional value 387 if (member.getValue() == null) { 388 389 // Voluntary claim with no value(s) 390 entries.add(new Entry(claimName, langTag)); 391 continue; 392 } 393 394 try { 395 JSONObject entrySpec = (JSONObject)member.getValue(); 396 397 ClaimRequirement requirement = ClaimRequirement.VOLUNTARY; 398 399 if (entrySpec.containsKey("essential")) { 400 401 boolean isEssential = (Boolean)entrySpec.get("essential"); 402 403 if (isEssential) 404 requirement = ClaimRequirement.ESSENTIAL; 405 } 406 407 if (entrySpec.containsKey("value")) { 408 409 String expectedValue = (String)entrySpec.get("value"); 410 411 entries.add(new Entry(claimName, requirement, langTag, expectedValue)); 412 413 } else if (entrySpec.containsKey("values")) { 414 415 List<String> expectedValues = new LinkedList<>(); 416 417 for (Object v: (List)entrySpec.get("values")) { 418 419 expectedValues.add((String)v); 420 } 421 422 entries.add(new Entry(claimName, requirement, langTag, expectedValues)); 423 424 } else { 425 entries.add(new Entry(claimName, requirement, langTag, (String)null)); 426 } 427 428 } catch (Exception e) { 429 // Ignore and continue 430 } 431 } 432 433 return entries; 434 } 435 } 436 437 438 /** 439 * The requested ID token claims, keyed by claim name and language tag. 440 */ 441 private final Map<ImmutablePair<String,LangTag>,Entry> idTokenClaims = 442 new HashMap<>(); 443 444 445 /** 446 * The requested UserInfo claims, keyed by claim name and language tag. 447 */ 448 private final Map<ImmutablePair<String,LangTag>,Entry> userInfoClaims = 449 new HashMap<>(); 450 451 452 /** 453 * Creates a new empty claims request. 454 */ 455 public ClaimsRequest() { 456 457 // Nothing to initialise 458 } 459 460 461 /** 462 * Adds the entries from the specified other claims request. 463 * 464 * @param other The other claims request. If {@code null} no claims 465 * request entries will be added to this claims request. 466 */ 467 public void add(final ClaimsRequest other) { 468 469 if (other == null) 470 return; 471 472 idTokenClaims.putAll(other.idTokenClaims); 473 userInfoClaims.putAll(other.userInfoClaims); 474 } 475 476 477 /** 478 * Adds the specified ID token claim to the request. It is marked as 479 * voluntary and no language tag and value(s) are associated with it. 480 * 481 * @param claimName The claim name. Must not be {@code null}. 482 */ 483 public void addIDTokenClaim(final String claimName) { 484 485 addIDTokenClaim(claimName, ClaimRequirement.VOLUNTARY); 486 } 487 488 489 /** 490 * Adds the specified ID token claim to the request. No language tag 491 * and value(s) are associated with it. 492 * 493 * @param claimName The claim name. Must not be {@code null}. 494 * @param requirement The claim requirement. Must not be {@code null}. 495 */ 496 public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement) { 497 498 addIDTokenClaim(claimName, requirement, null); 499 } 500 501 502 /** 503 * Adds the specified ID token claim to the request. No value(s) are 504 * associated with it. 505 * 506 * @param claimName The claim name. Must not be {@code null}. 507 * @param requirement The claim requirement. Must not be {@code null}. 508 * @param langTag The associated language tag, {@code null} if not 509 * specified. 510 */ 511 public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement, 512 final LangTag langTag) { 513 514 515 addIDTokenClaim(claimName, requirement, langTag, (String)null); 516 } 517 518 519 /** 520 * Adds the specified ID token claim to the request. 521 * 522 * @param claimName The claim name. Must not be {@code null}. 523 * @param requirement The claim requirement. Must not be {@code null}. 524 * @param langTag The associated language tag, {@code null} if not 525 * specified. 526 * @param value The expected claim value, {@code null} if not 527 * specified. 528 */ 529 public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement, 530 final LangTag langTag, final String value) { 531 532 addIDTokenClaim(new Entry(claimName, requirement, langTag, value)); 533 } 534 535 536 /** 537 * Adds the specified ID token claim to the request. 538 * 539 * @param claimName The claim name. Must not be {@code null}. 540 * @param requirement The claim requirement. Must not be {@code null}. 541 * @param langTag The associated language tag, {@code null} if not 542 * specified. 543 * @param values The expected claim values, {@code null} if not 544 * specified. 545 */ 546 public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement, 547 final LangTag langTag, final List<String> values) { 548 549 addIDTokenClaim(new Entry(claimName, requirement, langTag, values)); 550 } 551 552 553 /** 554 * Adds the specified ID token claim to the request. 555 * 556 * @param entry The individual ID token claim request. Must not be 557 * {@code null}. 558 */ 559 public void addIDTokenClaim(final Entry entry) { 560 561 ImmutablePair<String,LangTag> key = 562 new ImmutablePair<>(entry.getClaimName(), entry.getLangTag()); 563 564 idTokenClaims.put(key, entry); 565 } 566 567 568 /** 569 * Gets the requested ID token claims. 570 * 571 * @return The ID token claims, as an unmodifiable collection, empty 572 * set if none. 573 */ 574 public Collection<Entry> getIDTokenClaims() { 575 576 return Collections.unmodifiableCollection(idTokenClaims.values()); 577 } 578 579 580 /** 581 * Gets the names of the requested ID token claim names. 582 * 583 * @param withLangTag If {@code true} the language tags, if any, will 584 * be appended to the names, else not. 585 * 586 * 587 * @return The ID token claim names, as an unmodifiable set, empty set 588 * if none. 589 */ 590 public Set<String> getIDTokenClaimNames(final boolean withLangTag) { 591 592 Set<String> names = new HashSet<>(); 593 594 for (Entry en: idTokenClaims.values()) 595 names.add(en.getClaimName(withLangTag)); 596 597 return Collections.unmodifiableSet(names); 598 } 599 600 601 /** 602 * Removes the specified ID token claim from the request. 603 * 604 * @param claimName The claim name. Must not be {@code null}. 605 * @param langTag The associated language tag, {@code null} if none. 606 * 607 * @return The removed ID token claim, {@code null} if not found. 608 */ 609 public Entry removeIDTokenClaim(final String claimName, final LangTag langTag) { 610 611 ImmutablePair<String,LangTag> key = 612 new ImmutablePair<>(claimName, langTag); 613 614 return idTokenClaims.remove(key); 615 } 616 617 618 /** 619 * Removes the specified ID token claims from the request, in all 620 * existing language tag variations. 621 * 622 * @param claimName The claim name. Must not be {@code null}. 623 * 624 * @return The removed ID token claims, as an unmodifiable collection, 625 * empty set if none were found. 626 */ 627 public Collection<Entry> removeIDTokenClaims(final String claimName) { 628 629 Collection<Entry> removedClaims = new LinkedList<>(); 630 631 Iterator<Map.Entry<ImmutablePair<String,LangTag>,Entry>> it = idTokenClaims.entrySet().iterator(); 632 633 while (it.hasNext()) { 634 635 Map.Entry<ImmutablePair<String,LangTag>,Entry> reqEntry = it.next(); 636 637 if (reqEntry.getKey().getLeft().equals(claimName)) { 638 639 removedClaims.add(reqEntry.getValue()); 640 641 it.remove(); 642 } 643 } 644 645 return Collections.unmodifiableCollection(removedClaims); 646 } 647 648 649 /** 650 * Adds the specified UserInfo claim to the request. It is marked as 651 * voluntary and no language tag and value(s) are associated with it. 652 * 653 * @param claimName The claim name. Must not be {@code null}. 654 */ 655 public void addUserInfoClaim(final String claimName) { 656 657 addUserInfoClaim(claimName, ClaimRequirement.VOLUNTARY); 658 } 659 660 661 /** 662 * Adds the specified UserInfo claim to the request. No language tag 663 * and value(s) are associated with it. 664 * 665 * @param claimName The claim name. Must not be {@code null}. 666 * @param requirement The claim requirement. Must not be {@code null}. 667 */ 668 public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement) { 669 670 addUserInfoClaim(claimName, requirement, null); 671 } 672 673 674 /** 675 * Adds the specified UserInfo claim to the request. No value(s) are 676 * associated with it. 677 * 678 * @param claimName The claim name. Must not be {@code null}. 679 * @param requirement The claim requirement. Must not be {@code null}. 680 * @param langTag The associated language tag, {@code null} if not 681 * specified. 682 */ 683 public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement, 684 final LangTag langTag) { 685 686 687 addUserInfoClaim(claimName, requirement, langTag, (String)null); 688 } 689 690 691 /** 692 * Adds the specified UserInfo claim to the request. 693 * 694 * @param claimName The claim name. Must not be {@code null}. 695 * @param requirement The claim requirement. Must not be {@code null}. 696 * @param langTag The associated language tag, {@code null} if not 697 * specified. 698 * @param value The expected claim value, {@code null} if not 699 * specified. 700 */ 701 public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement, 702 final LangTag langTag, final String value) { 703 704 addUserInfoClaim(new Entry(claimName, requirement, langTag, value)); 705 } 706 707 708 /** 709 * Adds the specified UserInfo claim to the request. 710 * 711 * @param claimName The claim name. Must not be {@code null}. 712 * @param requirement The claim requirement. Must not be {@code null}. 713 * @param langTag The associated language tag, {@code null} if not 714 * specified. 715 * @param values The expected claim values, {@code null} if not 716 * specified. 717 */ 718 public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement, 719 final LangTag langTag, final List<String> values) { 720 721 addUserInfoClaim(new Entry(claimName, requirement, langTag, values)); 722 } 723 724 725 /** 726 * Adds the specified UserInfo claim to the request. 727 * 728 * @param entry The individual UserInfo claim request. Must not be 729 * {@code null}. 730 */ 731 public void addUserInfoClaim(final Entry entry) { 732 733 ImmutablePair<String,LangTag> key = 734 new ImmutablePair<>(entry.getClaimName(), entry.getLangTag()); 735 736 userInfoClaims.put(key, entry); 737 } 738 739 740 /** 741 * Gets the requested UserInfo claims. 742 * 743 * @return The UserInfo claims, as an unmodifiable collection, empty 744 * set if none. 745 */ 746 public Collection<Entry> getUserInfoClaims() { 747 748 return Collections.unmodifiableCollection(userInfoClaims.values()); 749 } 750 751 752 /** 753 * Gets the names of the requested UserInfo claim names. 754 * 755 * @param withLangTag If {@code true} the language tags, if any, will 756 * be appended to the names, else not. 757 * 758 * 759 * @return The UserInfo claim names, as an unmodifiable set, empty set 760 * if none. 761 */ 762 public Set<String> getUserInfoClaimNames(final boolean withLangTag) { 763 764 Set<String> names = new HashSet<>(); 765 766 for (Entry en: userInfoClaims.values()) 767 names.add(en.getClaimName(withLangTag)); 768 769 return Collections.unmodifiableSet(names); 770 } 771 772 773 /** 774 * Removes the specified UserInfo claim from the request. 775 * 776 * @param claimName The claim name. Must not be {@code null}. 777 * @param langTag The associated language tag, {@code null} if none. 778 * 779 * @return The removed UserInfo claim, {@code null} if not found. 780 */ 781 public Entry removeUserInfoClaim(final String claimName, final LangTag langTag) { 782 783 ImmutablePair<String,LangTag> key = 784 new ImmutablePair<>(claimName, langTag); 785 786 return userInfoClaims.remove(key); 787 } 788 789 790 /** 791 * Removes the specified UserInfo claims from the request, in all 792 * existing language tag variations. 793 * 794 * @param claimName The claim name. Must not be {@code null}. 795 * 796 * @return The removed UserInfo claims, as an unmodifiable collection, 797 * empty set if none were found. 798 */ 799 public Collection<Entry> removeUserInfoClaims(final String claimName) { 800 801 Collection<Entry> removedClaims = new LinkedList<>(); 802 803 Iterator<Map.Entry<ImmutablePair<String,LangTag>,Entry>> it = userInfoClaims.entrySet().iterator(); 804 805 while (it.hasNext()) { 806 807 Map.Entry<ImmutablePair<String,LangTag>,Entry> reqEntry = it.next(); 808 809 if (reqEntry.getKey().getLeft().equals(claimName)) { 810 811 removedClaims.add(reqEntry.getValue()); 812 813 it.remove(); 814 } 815 } 816 817 return Collections.unmodifiableCollection(removedClaims); 818 } 819 820 821 /** 822 * Returns the JSON object representation of this claims request. 823 * 824 * <p>Example: 825 * 826 * <pre> 827 * { 828 * "userinfo": 829 * { 830 * "given_name": {"essential": true}, 831 * "nickname": null, 832 * "email": {"essential": true}, 833 * "email_verified": {"essential": true}, 834 * "picture": null, 835 * "http://example.info/claims/groups": null 836 * }, 837 * "id_token": 838 * { 839 * "auth_time": {"essential": true}, 840 * "acr": {"values": ["urn:mace:incommon:iap:silver"] } 841 * } 842 * } 843 * </pre> 844 * 845 * @return The corresponding JSON object, empty if no ID token and 846 * UserInfo claims are specified. 847 */ 848 public JSONObject toJSONObject() { 849 850 JSONObject o = new JSONObject(); 851 852 Collection<Entry> idTokenEntries = getIDTokenClaims(); 853 854 if (! idTokenEntries.isEmpty()) { 855 856 o.put("id_token", Entry.toJSONObject(idTokenEntries)); 857 } 858 859 Collection<Entry> userInfoEntries = getUserInfoClaims(); 860 861 if (! userInfoEntries.isEmpty()) { 862 863 o.put("userinfo", Entry.toJSONObject(userInfoEntries)); 864 } 865 866 return o; 867 } 868 869 870 @Override 871 public String toString() { 872 873 return toJSONObject().toString(); 874 } 875 876 877 /** 878 * Resolves the claims request for the specified response type and 879 * scope. The scope values that are {@link OIDCScopeValue standard 880 * OpenID scope values} are resolved to their respective individual 881 * claims requests, any other scope values are ignored. 882 * 883 * @param responseType The response type. Must not be {@code null}. 884 * @param scope The scope. Must not be {@code null}. 885 * 886 * @return The claims request. 887 */ 888 public static ClaimsRequest resolve(final ResponseType responseType, final Scope scope) { 889 890 return resolve(responseType, scope, Collections.<Scope.Value,Set<String>>emptyMap()); 891 } 892 893 894 /** 895 * Resolves the claims request for the specified response type and 896 * scope. The scope values that are {@link OIDCScopeValue standard 897 * OpenID scope values} are resolved to their respective individual 898 * claims requests, any other scope values are checked in the specified 899 * custom claims map and resolved accordingly. 900 * 901 * @param responseType The response type. Must not be {@code null}. 902 * @param scope The scope. Must not be {@code null}. 903 * @param customClaims Custom scope to claim name map, {@code null} if 904 * not specified. 905 * 906 * @return The claims request. 907 */ 908 public static ClaimsRequest resolve(final ResponseType responseType, 909 final Scope scope, 910 final Map<Scope.Value,Set<String>> customClaims) { 911 912 // Determine the claims target (ID token or UserInfo) 913 final boolean switchToIDToken = 914 responseType.contains(OIDCResponseTypeValue.ID_TOKEN) && 915 ! responseType.contains(ResponseType.Value.CODE) && 916 ! responseType.contains(ResponseType.Value.TOKEN); 917 918 ClaimsRequest claimsRequest = new ClaimsRequest(); 919 920 for (Scope.Value value: scope) { 921 922 Set<ClaimsRequest.Entry> entries; 923 924 if (value.equals(OIDCScopeValue.PROFILE)) { 925 926 entries = OIDCScopeValue.PROFILE.toClaimsRequestEntries(); 927 928 } else if (value.equals(OIDCScopeValue.EMAIL)) { 929 930 entries = OIDCScopeValue.EMAIL.toClaimsRequestEntries(); 931 932 } else if (value.equals(OIDCScopeValue.PHONE)) { 933 934 entries = OIDCScopeValue.PHONE.toClaimsRequestEntries(); 935 936 } else if (value.equals(OIDCScopeValue.ADDRESS)) { 937 938 entries = OIDCScopeValue.ADDRESS.toClaimsRequestEntries(); 939 940 } else if (customClaims != null && customClaims.containsKey(value)) { 941 942 Set<String> claimNames = customClaims.get(value); 943 944 if (claimNames == null || claimNames.isEmpty()) { 945 continue; // skip 946 } 947 948 entries = new HashSet<>(); 949 950 for (String claimName: claimNames) { 951 entries.add(new ClaimsRequest.Entry(claimName, ClaimRequirement.VOLUNTARY)); 952 } 953 954 } else { 955 956 continue; // skip 957 } 958 959 for (ClaimsRequest.Entry en: entries) { 960 961 if (switchToIDToken) 962 claimsRequest.addIDTokenClaim(en); 963 else 964 claimsRequest.addUserInfoClaim(en); 965 } 966 } 967 968 return claimsRequest; 969 } 970 971 972 /** 973 * Resolves the merged claims request from the specified OpenID 974 * authentication request parameters. The scope values that are 975 * {@link OIDCScopeValue standard OpenID scope values} are resolved to 976 * their respective individual claims requests, any other scope values 977 * are ignored. 978 * 979 * @param responseType The response type. Must not be {@code null}. 980 * @param scope The scope. Must not be {@code null}. 981 * @param claimsRequest The claims request, corresponding to the 982 * optional {@code claims} OpenID Connect 983 * authorisation request parameter, {@code null} 984 * if not specified. 985 * 986 * @return The merged claims request. 987 */ 988 public static ClaimsRequest resolve(final ResponseType responseType, 989 final Scope scope, 990 final ClaimsRequest claimsRequest) { 991 992 return resolve(responseType, scope, claimsRequest, Collections.<Scope.Value,Set<String>>emptyMap()); 993 } 994 995 996 /** 997 * Resolves the merged claims request from the specified OpenID 998 * authentication request parameters. The scope values that are 999 * {@link OIDCScopeValue standard OpenID scope values} are resolved to 1000 * their respective individual claims requests, any other scope values 1001 * are checked in the specified custom claims map and resolved 1002 * accordingly. 1003 * 1004 * @param responseType The response type. Must not be {@code null}. 1005 * @param scope The scope. Must not be {@code null}. 1006 * @param claimsRequest The claims request, corresponding to the 1007 * optional {@code claims} OpenID Connect 1008 * authorisation request parameter, {@code null} 1009 * if not specified. 1010 * @param customClaims Custom scope to claim name map, {@code null} if 1011 * not specified. 1012 * 1013 * @return The merged claims request. 1014 */ 1015 public static ClaimsRequest resolve(final ResponseType responseType, 1016 final Scope scope, 1017 final ClaimsRequest claimsRequest, 1018 final Map<Scope.Value,Set<String>> customClaims) { 1019 1020 ClaimsRequest mergedClaimsRequest = resolve(responseType, scope, customClaims); 1021 1022 mergedClaimsRequest.add(claimsRequest); 1023 1024 return mergedClaimsRequest; 1025 } 1026 1027 1028 /** 1029 * Resolves the merged claims request for the specified OpenID 1030 * authentication request. The scope values that are 1031 * {@link OIDCScopeValue standard OpenID scope values} are resolved to 1032 * their respective individual claims requests, any other scope values 1033 * are ignored. 1034 * 1035 * @param authRequest The OpenID authentication request. Must not be 1036 * {@code null}. 1037 * 1038 * @return The merged claims request. 1039 */ 1040 public static ClaimsRequest resolve(final AuthenticationRequest authRequest) { 1041 1042 return resolve(authRequest.getResponseType(), authRequest.getScope(), authRequest.getClaims()); 1043 } 1044 1045 1046 /** 1047 * Parses a claims request from the specified JSON object 1048 * representation. Unexpected members in the JSON object are silently 1049 * ignored. 1050 * 1051 * @param jsonObject The JSON object to parse. Must not be 1052 * {@code null}. 1053 * 1054 * @return The claims request. 1055 */ 1056 public static ClaimsRequest parse(final JSONObject jsonObject) { 1057 1058 ClaimsRequest claimsRequest = new ClaimsRequest(); 1059 1060 try { 1061 if (jsonObject.containsKey("id_token")) { 1062 1063 JSONObject idTokenObject = (JSONObject)jsonObject.get("id_token"); 1064 1065 Collection<Entry> idTokenClaims = Entry.parseEntries(idTokenObject); 1066 1067 for (Entry entry: idTokenClaims) 1068 claimsRequest.addIDTokenClaim(entry); 1069 } 1070 1071 1072 if (jsonObject.containsKey("userinfo")) { 1073 1074 JSONObject userInfoObject = (JSONObject)jsonObject.get("userinfo"); 1075 1076 Collection<Entry> userInfoClaims = Entry.parseEntries(userInfoObject); 1077 1078 for (Entry entry: userInfoClaims) 1079 claimsRequest.addUserInfoClaim(entry); 1080 } 1081 1082 } catch (Exception e) { 1083 1084 // Ignore 1085 } 1086 1087 return claimsRequest; 1088 } 1089 1090 1091 /** 1092 * Parses a claims request from the specified JSON object string 1093 * representation. Unexpected members in the JSON object are silently 1094 * ignored. 1095 * 1096 * @param json The JSON object string to parse. Must not be 1097 * {@code null}. 1098 * 1099 * @return The claims request. 1100 * 1101 * @throws ParseException If the string couldn't be parsed to a valid 1102 * JSON object. 1103 */ 1104 public static ClaimsRequest parse(final String json) 1105 throws ParseException { 1106 1107 return parse(JSONObjectUtils.parse(json)); 1108 } 1109}