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