All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nimbusds.openid.connect.sdk.ClaimsRequest Maven / Gradle / Ivy

Go to download

OAuth 2.0 SDK with OpenID Connection extensions for developing client and server applications.

There is a newer version: 11.20.1
Show newest version
/*
 * oauth2-oidc-sdk
 *
 * Copyright 2012-2016, Connect2id Ltd and contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the
 * License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

package com.nimbusds.openid.connect.sdk;


import java.util.*;

import net.jcip.annotations.Immutable;
import net.minidev.json.JSONArray;
import net.minidev.json.JSONAware;
import net.minidev.json.JSONObject;

import com.nimbusds.langtag.LangTag;
import com.nimbusds.langtag.LangTagException;
import com.nimbusds.oauth2.sdk.OAuth2Error;
import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.oauth2.sdk.ResponseType;
import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.oauth2.sdk.util.JSONArrayUtils;
import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
import com.nimbusds.openid.connect.sdk.claims.ClaimRequirement;


/**
 * Specifies the individual OpenID claims to return from the UserInfo endpoint
 * and / or in the ID Token.
 *
 * 

Related specifications: * *

    *
  • OpenID Connect Core 1.0, section 5.5. *
  • OpenID Connect for Identity Assurance 1.0. *
*/ @Deprecated public class ClaimsRequest implements JSONAware { /** * Individual OpenID claim request. * *

Related specifications: * *

    *
  • OpenID Connect Core 1.0, section 5.5.1. *
  • OpenID Connect for Identity Assurance 1.0. *
*/ @Immutable public static class Entry { /** * The claim name. */ private final String claimName; /** * The claim requirement. */ private final ClaimRequirement requirement; /** * Optional language tag. */ private final LangTag langTag; /** * Optional claim value. */ private final String value; /** * Optional claim values. */ private final List values; /** * The claim purpose. */ private final String purpose; /** * Optional additional claim information. * *

Example additional information in the "info" member: * *

		 * {
		 *   "userinfo" : {
		 *       "email": null,
		 *       "email_verified": null,
		 *       "http://example.info/claims/groups" : { "info" : "custom information" } }
		 * }
		 * 
*/ private final Map additionalInformation; /** * Creates a new individual claim request. The claim * requirement is set to voluntary (the default) and no * expected value(s) or other parameters are specified. * * @param claimName The claim name. Must not be {@code null}. */ public Entry(final String claimName) { this(claimName, ClaimRequirement.VOLUNTARY, null, null, null, null, null); } /** * Creates a new individual claim request. The claim * requirement is set to voluntary (the default) and no * expected value(s) are specified. * * @param claimName The claim name. Must not be {@code null}. * @param langTag Optional language tag for the claim. */ @Deprecated public Entry(final String claimName, final LangTag langTag) { this(claimName, ClaimRequirement.VOLUNTARY, langTag, null, null); } /** * Creates a new individual claim request. * * @param claimName The claim name. Must not be {@code null}. * @param requirement The claim requirement. Must not be * {@code null}. */ @Deprecated public Entry(final String claimName, final ClaimRequirement requirement) { this(claimName, requirement, null, null, null); } /** * Creates a new individual claim request. * * @param claimName The claim name. Must not be {@code null}. * @param requirement The claim requirement. Must not be * {@code null}. * @param langTag Optional language tag for the claim. * @param value Optional expected value for the claim. */ @Deprecated public Entry(final String claimName, final ClaimRequirement requirement, final LangTag langTag, final String value) { this(claimName, requirement, langTag, value, null); } /** * Creates a new individual claim request. * * @param claimName The claim name. Must not be {@code null}. * @param requirement The claim requirement. Must not be * {@code null}. * @param langTag Optional language tag for the claim. * @param values Optional expected values for the claim. */ @Deprecated public Entry(final String claimName, final ClaimRequirement requirement, final LangTag langTag, final List values) { this(claimName, requirement, langTag, null, values, null, null); } /** * Creates a new individual claim request. This constructor is * to be used privately. Ensures that {@code value} and * {@code values} are not simultaneously specified. * * @param claimName The claim name. Must not be {@code null}. * @param requirement The claim requirement. Must not be * {@code null}. * @param langTag Optional language tag for the claim. * @param value Optional expected value for the claim. If * set, then the {@code values} parameter * must not be set. * @param values Optional expected values for the claim. * If set, then the {@code value} parameter * must not be set. */ @Deprecated private Entry(final String claimName, final ClaimRequirement requirement, final LangTag langTag, final String value, final List values) { this(claimName, requirement, langTag, value, values, null, null); } /** * Creates a new individual claim request. This constructor is * to be used privately. Ensures that {@code value} and * {@code values} are not simultaneously specified. * * @param claimName The claim name. Must not be * {@code null}. * @param requirement The claim requirement. Must not * be {@code null}. * @param langTag Optional language tag for the * claim. * @param value Optional expected value for the * claim. If set, then the {@code * values} parameter must not be * set. * @param values Optional expected values for * the claim. If set, then the * {@code value} parameter must * not be set. * @param purpose The purpose for the requested * claim, {@code null} if not * specified. * @param additionalInformation Optional additional information */ private Entry(final String claimName, final ClaimRequirement requirement, final LangTag langTag, final String value, final List values, final String purpose, final Map additionalInformation) { if (claimName == null) throw new IllegalArgumentException("The claim name must not be null"); this.claimName = claimName; if (requirement == null) throw new IllegalArgumentException("The claim requirement must not be null"); this.requirement = requirement; this.langTag = langTag; if (value != null && values == null) { this.value = value; this.values = null; } else if (value == null && values != null) { this.value = null; this.values = values; } else if (value == null && values == null) { this.value = null; this.values = null; } else { throw new IllegalArgumentException("Either value or values must be specified, but not both"); } this.purpose = purpose; this.additionalInformation = additionalInformation; } /** * Returns the claim name. * * @return The claim name. */ public String getClaimName() { return claimName; } /** * Returns the claim name, optionally with the language tag * appended. * *

Example with language tag: * *

		 * name#de-DE
		 * 
* * @param withLangTag If {@code true} the language tag will be * appended to the name (if any), else not. * * @return The claim name, with optionally appended language * tag. */ public String getClaimName(final boolean withLangTag) { if (withLangTag && langTag != null) return claimName + "#" + langTag; else return claimName; } /** * Returns a new claim entry with the specified requirement. * * @param requirement The claim requirement. * * @return The new entry. */ public Entry withClaimRequirement(final ClaimRequirement requirement) { return new Entry(claimName, requirement, langTag, value, values, purpose, additionalInformation); } /** * Returns the claim requirement. * * @return The claim requirement. */ public ClaimRequirement getClaimRequirement() { return requirement; } /** * Returns a new claim entry with the specified language tag * for the claim. * * @param langTag The language tag, {@code null} if not * specified. * * @return The new entry. */ public Entry withLangTag(final LangTag langTag) { return new Entry(claimName, requirement, langTag, value, values, purpose, additionalInformation); } /** * Returns the optional language tag for the claim. * * @return The language tag, {@code null} if not specified. */ public LangTag getLangTag() { return langTag; } /** * Returns a new claim entry with the specified requested value * for the claim. * * @param value The value, {@code null} if not specified. * * @return The new entry. */ public Entry withValue(final String value) { return new Entry(claimName, requirement, langTag, value, null, purpose, additionalInformation); } /** * Returns the requested value for the claim. * * @return The value, {@code null} if not specified. */ public String getValue() { return value; } /** * Returns a new claim entry with the specified requested * values for the claim. * * @param values The values, {@code null} if not specified. * * @return The new entry. */ public Entry withValues(final List values) { return new Entry(claimName, requirement, langTag, null, values, purpose, additionalInformation); } /** * Returns the optional values for the claim. * * @return The values, {@code null} if not specified. */ public List getValues() { return values; } /** * Returns a new claim entry with the specified purpose for the * requested claim. * * @param purpose The purpose, {@code null} if not specified. * * @return The new entry. */ public Entry withPurpose(final String purpose) { return new Entry(claimName, requirement, langTag, value, values, purpose, additionalInformation); } /** * Returns the optional purpose for the requested claim. * * @return The purpose, {@code null} if not specified. */ public String getPurpose() { return purpose; } /** * Returns a new claim entry with the specified additional * information for the claim. * *

Example additional information in the "info" member: * *

		 * {
		 *   "userinfo" : {
		 *       "email": null,
		 *       "email_verified": null,
		 *       "http://example.info/claims/groups" : { "info" : "custom information" } }
		 * }
		 * 
* * @param additionalInformation The additional information, * {@code null} if not specified. * * @return The new entry. */ public Entry withAdditionalInformation(final Map additionalInformation) { return new Entry(claimName, requirement, langTag, value, values, purpose, additionalInformation); } /** * Returns the additional information for the claim. * *

Example additional information in the "info" member: * *

		 * {
		 *   "userinfo" : {
		 *       "email": null,
		 *       "email_verified": null,
		 *       "http://example.info/claims/groups" : { "info" : "custom information" } }
		 * }
		 * 
* * @return The additional information, {@code null} if not * specified. */ public Map getAdditionalInformation() { return additionalInformation; } /** * Returns the JSON object representation of the specified * collection of individual claim requests. * *

Example: * *

		 * {
		 *   "given_name": {"essential": true},
		 *   "nickname": null,
		 *   "email": {"essential": true},
		 *   "email_verified": {"essential": true},
		 *   "picture": null,
		 *   "http://example.info/claims/groups": null
		 * }
		 * 
* * @param entries The entries to serialise. Must not be * {@code null}. * @return The corresponding JSON object, empty if no claims * were found. */ public static JSONObject toJSONObject(final Collection entries) { JSONObject o = new JSONObject(); for (Entry entry : entries) { // Compose the optional value JSONObject entrySpec = null; if (entry.getValue() != null) { entrySpec = new JSONObject(); entrySpec.put("value", entry.getValue()); } if (entry.getValues() != null) { // Either "value" or "values", or none // may be defined entrySpec = new JSONObject(); entrySpec.put("values", entry.getValues()); } if (entry.getClaimRequirement().equals(ClaimRequirement.ESSENTIAL)) { if (entrySpec == null) entrySpec = new JSONObject(); entrySpec.put("essential", true); } if (entry.getPurpose() != null) { if (entrySpec == null) { entrySpec = new JSONObject(); } entrySpec.put("purpose", entry.getPurpose()); } if (entry.getAdditionalInformation() != null) { if (entrySpec == null) { entrySpec = new JSONObject(); } for (Map.Entry additionalInformationEntry : entry.getAdditionalInformation().entrySet()) { entrySpec.put(additionalInformationEntry.getKey(), additionalInformationEntry.getValue()); } } o.put(entry.getClaimName(true), entrySpec); } return o; } /** * Parses a collection of individual claim requests from the * specified JSON object. Request entries that are not * understood are silently ignored. * * @param jsonObject The JSON object to parse. Must not be * {@code null}. * * @return The collection of claim requests. */ public static Collection parseEntries(final JSONObject jsonObject) { Collection entries = new LinkedList<>(); if (jsonObject.isEmpty()) return entries; for (Map.Entry member : jsonObject.entrySet()) { // Process the key String claimNameWithOptLangTag = member.getKey(); String claimName; LangTag langTag = null; if (claimNameWithOptLangTag.contains("#")) { String[] parts = claimNameWithOptLangTag.split("#", 2); claimName = parts[0]; try { langTag = LangTag.parse(parts[1]); } catch (LangTagException e) { // Ignore and continue continue; } } else { claimName = claimNameWithOptLangTag; } // Parse the optional value if (member.getValue() == null) { // Voluntary claim with no value(s) entries.add(new Entry(claimName, langTag)); continue; } try { JSONObject entrySpec = (JSONObject) member.getValue(); ClaimRequirement requirement = ClaimRequirement.VOLUNTARY; if (entrySpec.containsKey("essential")) { boolean isEssential = (Boolean) entrySpec.get("essential"); if (isEssential) requirement = ClaimRequirement.ESSENTIAL; } String purpose = null; if (entrySpec.containsKey("purpose")) { purpose = (String) entrySpec.get("purpose"); } if (entrySpec.containsKey("value")) { String expectedValue = (String) entrySpec.get("value"); Map additionalInformation = getAdditionalInformationFromClaim(entrySpec); entries.add(new Entry(claimName, requirement, langTag, expectedValue, null, purpose, additionalInformation)); } else if (entrySpec.containsKey("values")) { List expectedValues = new LinkedList<>(); for (Object v : (List) entrySpec.get("values")) { expectedValues.add((String) v); } Map additionalInformation = getAdditionalInformationFromClaim(entrySpec); entries.add(new Entry(claimName, requirement, langTag, null, expectedValues, purpose, additionalInformation)); } else { Map additionalInformation = getAdditionalInformationFromClaim(entrySpec); entries.add(new Entry(claimName, requirement, langTag, null, null, purpose, additionalInformation)); } } catch (Exception e) { // Ignore and continue } } return entries; } private static Map getAdditionalInformationFromClaim(final JSONObject entrySpec) { Set stdKeys = new HashSet<>(Arrays.asList("essential", "value", "values", "purpose")); Map additionalClaimInformation = new HashMap<>(); for (Map.Entry additionalClaimInformationEntry : entrySpec.entrySet()) { if (stdKeys.contains(additionalClaimInformationEntry.getKey())) { continue; // skip std key } additionalClaimInformation.put(additionalClaimInformationEntry.getKey(), additionalClaimInformationEntry.getValue()); } return additionalClaimInformation.isEmpty() ? null : additionalClaimInformation; } } /** * The requested ID token claims, keyed by claim name and language tag. */ private final Map, Entry> idTokenClaims = new HashMap<>(); /** * The requested verified ID token claims, keyed by claim name and * language tag. */ private final Map, Entry> verifiedIDTokenClaims = new HashMap<>(); /** * The verification element for the requested verified ID token claims. */ private JSONObject idTokenClaimsVerification; /** * The requested UserInfo claims, keyed by claim name and language tag. */ private final Map, Entry> userInfoClaims = new HashMap<>(); /** * The requested verified UserInfo claims, keyed by claim name and * language tag. */ private final Map, Entry> verifiedUserInfoClaims = new HashMap<>(); /** * The verification element for the requested verified UserInfo claims. */ private JSONObject userInfoClaimsVerification; /** * Creates a new empty claims request. */ public ClaimsRequest() { // Nothing to initialise } /** * Adds the entries from the specified other claims request. * * @param other The other claims request. If {@code null} no claims * request entries will be added to this claims request. */ public void add(final ClaimsRequest other) { if (other == null) return; idTokenClaims.putAll(other.idTokenClaims); verifiedIDTokenClaims.putAll(other.verifiedIDTokenClaims); idTokenClaimsVerification = other.idTokenClaimsVerification; userInfoClaims.putAll(other.userInfoClaims); verifiedUserInfoClaims.putAll(other.verifiedUserInfoClaims); userInfoClaimsVerification = other.userInfoClaimsVerification; } /** * Adds the specified ID token claim to the request. It is marked as * voluntary and no language tag and value(s) are associated with it. * * @param claimName The claim name. Must not be {@code null}. */ public void addIDTokenClaim(final String claimName) { addIDTokenClaim(claimName, ClaimRequirement.VOLUNTARY); } /** * Adds the specified ID token claim to the request. No language tag * and value(s) are associated with it. * * @param claimName The claim name. Must not be {@code null}. * @param requirement The claim requirement. Must not be {@code null}. */ public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement) { addIDTokenClaim(claimName, requirement, null); } /** * Adds the specified ID token claim to the request. No value(s) are * associated with it. * * @param claimName The claim name. Must not be {@code null}. * @param requirement The claim requirement. Must not be {@code null}. * @param langTag The associated language tag, {@code null} if not * specified. */ public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement, final LangTag langTag) { addIDTokenClaim(claimName, requirement, langTag, (String) null); } /** * Adds the specified ID token claim to the request. * * @param claimName The claim name. Must not be {@code null}. * @param requirement The claim requirement. Must not be {@code null}. * @param langTag The associated language tag, {@code null} if not * specified. * @param value The expected claim value, {@code null} if not * specified. */ public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement, final LangTag langTag, final String value) { addIDTokenClaim(new Entry(claimName, requirement, langTag, value)); } /** * Adds the specified ID token claim to the request. * * @param claimName The claim name. Must not be * {@code null}. * @param requirement The claim requirement. Must not be * {@code null}. * @param langTag The associated language tag, * {@code null} if not specified. * @param value The expected claim value, {@code null} * if not specified. * @param additionalInformation The additional information for this * claim, {@code null} if not specified. */ public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement, final LangTag langTag, final String value, final Map additionalInformation) { addIDTokenClaim(new Entry(claimName, requirement, langTag, value, null, null, additionalInformation)); } /** * Adds the specified ID token claim to the request. * * @param claimName The claim name. Must not be {@code null}. * @param requirement The claim requirement. Must not be {@code null}. * @param langTag The associated language tag, {@code null} if not * specified. * @param values The expected claim values, {@code null} if not * specified. */ public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement, final LangTag langTag, final List values) { addIDTokenClaim(new Entry(claimName, requirement, langTag, values)); } /** * Adds the specified ID token claim to the request. * * @param claimName The claim name. Must not be * {@code null}. * @param requirement The claim requirement. Must not be * {@code null}. * @param langTag The associated language tag, * {@code null} if not specified. * @param values The expected claim values, {@code null} * if not specified. * @param additionalInformation The additional information for this * claim, {@code null} if not specified. */ public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement, final LangTag langTag, final List values, final Map additionalInformation) { addIDTokenClaim(new Entry(claimName, requirement, langTag, null, values, null, additionalInformation)); } private static Map.Entry toKey(final Entry entry) { return new AbstractMap.SimpleImmutableEntry<>( entry.getClaimName(), entry.getLangTag()); } /** * Adds the specified ID token claim to the request. * * @param entry The individual ID token claim request. Must not be * {@code null}. */ public void addIDTokenClaim(final Entry entry) { idTokenClaims.put(toKey(entry), entry); } /** * Adds the specified verified ID token claim to the request. * * @param entry The individual verified ID token claim request. Must * not be {@code null}. */ public void addVerifiedIDTokenClaim(final Entry entry) { verifiedIDTokenClaims.put(toKey(entry), entry); } /** * Sets the {@code verification} element for the requested verified ID * token claims. * * @param jsonObject The {@code verification} JSON object, {@code null} * if not specified. */ public void setIDTokenClaimsVerificationJSONObject(final JSONObject jsonObject) { this.idTokenClaimsVerification = jsonObject; } /** * Gets the {@code verification} element for the requested verified ID * token claims. * * @return The {@code verification} JSON object, {@code null} if not * specified. */ public JSONObject getIDTokenClaimsVerificationJSONObject() { return idTokenClaimsVerification; } /** * Gets the requested ID token claims. * * @return The ID token claims, as an unmodifiable collection, empty * set if none. */ public Collection getIDTokenClaims() { return Collections.unmodifiableCollection(idTokenClaims.values()); } /** * Gets the requested verified ID token claims. * * @return The verified ID token claims, as an unmodifiable collection, * empty set if none. */ public Collection getVerifiedIDTokenClaims() { return Collections.unmodifiableCollection(verifiedIDTokenClaims.values()); } private static Set getClaimNames(final Map, Entry> claims, final boolean withLangTag) { Set names = new HashSet<>(); for (Entry en : claims.values()) names.add(en.getClaimName(withLangTag)); return Collections.unmodifiableSet(names); } /** * Gets the names of the requested ID token claim names. * * @param withLangTag If {@code true} the language tags, if any, will * be appended to the names, else not. * * @return The ID token claim names, as an unmodifiable set, empty set * if none. */ public Set getIDTokenClaimNames(final boolean withLangTag) { return getClaimNames(idTokenClaims, withLangTag); } /** * Gets the names of the requested verified ID token claim names. * * @param withLangTag If {@code true} the language tags, if any, will * be appended to the names, else not. * * @return The ID token claim names, as an unmodifiable set, empty set * if none. */ public Set getVerifiedIDTokenClaimNames(final boolean withLangTag) { return getClaimNames(verifiedIDTokenClaims, withLangTag); } private static Map.Entry toKey(final String claimName, final LangTag langTag) { return new AbstractMap.SimpleImmutableEntry<>(claimName, langTag); } /** * Removes the specified ID token claim from the request. * * @param claimName The claim name. Must not be {@code null}. * @param langTag The associated language tag, {@code null} if none. * * @return The removed ID token claim, {@code null} if not found. */ public Entry removeIDTokenClaim(final String claimName, final LangTag langTag) { return idTokenClaims.remove(toKey(claimName, langTag)); } /** * Removes the specified verified ID token claim from the request. * * @param claimName The claim name. Must not be {@code null}. * @param langTag The associated language tag, {@code null} if none. * * @return The removed ID token claim, {@code null} if not found. */ public Entry removeVerifiedIDTokenClaim(final String claimName, final LangTag langTag) { return verifiedIDTokenClaims.remove(toKey(claimName, langTag)); } private static Collection removeClaims(final Map, Entry> claims, final String claimName) { Collection removedClaims = new LinkedList<>(); Iterator, Entry>> it = claims.entrySet().iterator(); while (it.hasNext()) { Map.Entry, Entry> reqEntry = it.next(); if (reqEntry.getKey().getKey().equals(claimName)) { removedClaims.add(reqEntry.getValue()); it.remove(); } } return Collections.unmodifiableCollection(removedClaims); } /** * Removes the specified ID token claims from the request, in all * existing language tag variations. * * @param claimName The claim name. Must not be {@code null}. * * @return The removed ID token claims, as an unmodifiable collection, * empty set if none were found. */ public Collection removeIDTokenClaims(final String claimName) { return removeClaims(idTokenClaims, claimName); } /** * Removes the specified verified ID token claims from the request, in * all existing language tag variations. * * @param claimName The claim name. Must not be {@code null}. * * @return The removed ID token claims, as an unmodifiable collection, * empty set if none were found. */ public Collection removeVerifiedIDTokenClaims(final String claimName) { return removeClaims(verifiedIDTokenClaims, claimName); } /** * Adds the specified UserInfo claim to the request. It is marked as * voluntary and no language tag and value(s) are associated with it. * * @param claimName The claim name. Must not be {@code null}. */ public void addUserInfoClaim(final String claimName) { addUserInfoClaim(claimName, ClaimRequirement.VOLUNTARY); } /** * Adds the specified UserInfo claim to the request. No language tag and * value(s) are associated with it. * * @param claimName The claim name. Must not be {@code null}. * @param requirement The claim requirement. Must not be {@code null}. */ public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement) { addUserInfoClaim(claimName, requirement, null); } /** * Adds the specified UserInfo claim to the request. No value(s) are * associated with it. * * @param claimName The claim name. Must not be {@code null}. * @param requirement The claim requirement. Must not be {@code null}. * @param langTag The associated language tag, {@code null} if not * specified. */ public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement, final LangTag langTag) { addUserInfoClaim(claimName, requirement, langTag, (String) null); } /** * Adds the specified UserInfo claim to the request. * * @param claimName The claim name. Must not be {@code null}. * @param requirement The claim requirement. Must not be {@code null}. * @param langTag The associated language tag, {@code null} if not * specified. * @param value The expected claim value, {@code null} if not * specified. */ public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement, final LangTag langTag, final String value) { addUserInfoClaim(new Entry(claimName, requirement, langTag, value)); } /** * Adds the specified UserInfo claim to the request. * * @param claimName The claim name. Must not be {@code * null}. * @param requirement The claim requirement. Must not be * {@code null}. * @param langTag The associated language tag, {@code * null} if not specified. * @param value The expected claim value, {@code null} * if not specified. * @param additionalInformation The additional information for this * claim, {@code null} if not specified. */ public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement, final LangTag langTag, final String value, final Map additionalInformation) { addUserInfoClaim(new Entry(claimName, requirement, langTag, value, null, null, additionalInformation)); } /** * Adds the specified UserInfo claim to the request. * * @param claimName The claim name. Must not be {@code null}. * @param requirement The claim requirement. Must not be {@code null}. * @param langTag The associated language tag, {@code null} if not * specified. * @param values The expected claim values, {@code null} if not * specified. */ public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement, final LangTag langTag, final List values) { addUserInfoClaim(new Entry(claimName, requirement, langTag, values)); } /** * Adds the specified UserInfo claim to the request. * * @param claimName The claim name. Must not be * {@code null}. * @param requirement The claim requirement. Must not be * {@code null}. * @param langTag The associated language tag, * {@code null} if not specified. * @param values The expected claim values, {@code null} * if not specified. * @param additionalInformation The additional information for this * claim, {@code null} if not specified. */ public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement, final LangTag langTag, final List values, final Map additionalInformation) { addUserInfoClaim(new Entry(claimName, requirement, langTag, null, values, null, additionalInformation)); } /** * Adds the specified UserInfo claim to the request. * * @param entry The individual UserInfo claim request. Must not be * {@code null}. */ public void addUserInfoClaim(final Entry entry) { userInfoClaims.put(toKey(entry), entry); } /** * Adds the specified verified UserInfo claim to the request. * * @param entry The individual verified UserInfo claim request. Must * not be {@code null}. */ public void addVerifiedUserInfoClaim(final Entry entry) { verifiedUserInfoClaims.put(toKey(entry), entry); } /** * Sets the {@code verification} element for the requested verified * UserInfo claims. * * @param jsonObject The {@code verification} JSON object, {@code null} * if not specified. */ public void setUserInfoClaimsVerificationJSONObject(final JSONObject jsonObject) { this.userInfoClaimsVerification = jsonObject; } /** * Gets the {@code verification} element for the requested verified * UserInfo claims. * * @return The {@code verification} JSON object, {@code null} if not * specified. */ public JSONObject getUserInfoClaimsVerificationJSONObject() { return userInfoClaimsVerification; } /** * Gets the requested UserInfo claims. * * @return The UserInfo claims, as an unmodifiable collection, empty * set if none. */ public Collection getUserInfoClaims() { return Collections.unmodifiableCollection(userInfoClaims.values()); } /** * Gets the requested verified UserInfo claims. * * @return The UserInfo claims, as an unmodifiable collection, empty * set if none. */ public Collection getVerifiedUserInfoClaims() { return Collections.unmodifiableCollection(verifiedUserInfoClaims.values()); } /** * Gets the names of the requested UserInfo claim names. * * @param withLangTag If {@code true} the language tags, if any, will * be appended to the names, else not. * * @return The UserInfo claim names, as an unmodifiable set, empty set * if none. */ public Set getUserInfoClaimNames(final boolean withLangTag) { return getClaimNames(userInfoClaims, withLangTag); } /** * Gets the names of the requested verified UserInfo claim names. * * @param withLangTag If {@code true} the language tags, if any, will * be appended to the names, else not. * * @return The UserInfo claim names, as an unmodifiable set, empty set * if none. */ public Set getVerifiedUserInfoClaimNames(final boolean withLangTag) { return getClaimNames(verifiedUserInfoClaims, withLangTag); } /** * Removes the specified UserInfo claim from the request. * * @param claimName The claim name. Must not be {@code null}. * @param langTag The associated language tag, {@code null} if none. * * @return The removed UserInfo claim, {@code null} if not found. */ public Entry removeUserInfoClaim(final String claimName, final LangTag langTag) { return userInfoClaims.remove(toKey(claimName, langTag)); } /** * Removes the specified verified UserInfo claim from the request. * * @param claimName The claim name. Must not be {@code null}. * @param langTag The associated language tag, {@code null} if none. * * @return The removed UserInfo claim, {@code null} if not found. */ public Entry removeVerifiedUserInfoClaim(final String claimName, final LangTag langTag) { return verifiedUserInfoClaims.remove(toKey(claimName, langTag)); } /** * Removes the specified UserInfo claims from the request, in all * existing language tag variations. * * @param claimName The claim name. Must not be {@code null}. * * @return The removed UserInfo claims, as an unmodifiable collection, * empty set if none were found. */ public Collection removeUserInfoClaims(final String claimName) { return removeClaims(userInfoClaims, claimName); } /** * Removes the specified verified UserInfo claims from the request, in * all existing language tag variations. * * @param claimName The claim name. Must not be {@code null}. * * @return The removed UserInfo claims, as an unmodifiable collection, * empty set if none were found. */ public Collection removeVerifiedUserInfoClaims(final String claimName) { return removeClaims(verifiedUserInfoClaims, claimName); } /** * Returns the JSON object representation of this claims request. * *

Example: * *

	 * {
	 *   "userinfo":
	 *    {
	 *     "given_name": {"essential": true},
	 *     "nickname": null,
	 *     "email": {"essential": true},
	 *     "email_verified": {"essential": true},
	 *     "picture": null,
	 *     "http://example.info/claims/groups": null
	 *    },
	 *   "id_token":
	 *    {
	 *     "auth_time": {"essential": true},
	 *     "acr": {"values": ["urn:mace:incommon:iap:silver"] }
	 *    }
	 * }
	 * 
* * @return The corresponding JSON object, empty if no ID token and * UserInfo claims are specified. */ public JSONObject toJSONObject() { JSONObject o = new JSONObject(); if (! getIDTokenClaims().isEmpty()) { o.put("id_token", Entry.toJSONObject(getIDTokenClaims())); } if (! getVerifiedIDTokenClaims().isEmpty()) { JSONObject idTokenObject; if (o.get("id_token") != null) { idTokenObject = (JSONObject) o.get("id_token"); } else { idTokenObject = new JSONObject(); } JSONObject verifiedClaims = new JSONObject(); verifiedClaims.put("claims", Entry.toJSONObject(getVerifiedIDTokenClaims())); if (getIDTokenClaimsVerificationJSONObject() != null) { verifiedClaims.put("verification", getIDTokenClaimsVerificationJSONObject()); } idTokenObject.put("verified_claims", verifiedClaims); o.put("id_token", idTokenObject); } if (! getUserInfoClaims().isEmpty()) { o.put("userinfo", Entry.toJSONObject(getUserInfoClaims())); } if (! getVerifiedUserInfoClaims().isEmpty()) { JSONObject userInfoObject; if (o.get("userinfo") != null) { userInfoObject = (JSONObject) o.get("userinfo"); } else { userInfoObject = new JSONObject(); } JSONObject verifiedClaims = new JSONObject(); verifiedClaims.put("claims", Entry.toJSONObject(getVerifiedUserInfoClaims())); if (getUserInfoClaimsVerificationJSONObject() != null) { verifiedClaims.put("verification", getUserInfoClaimsVerificationJSONObject()); } userInfoObject.put("verified_claims", verifiedClaims); o.put("userinfo", userInfoObject); } return o; } @Override public String toJSONString() { return toJSONObject().toJSONString(); } @Override public String toString() { return toJSONString(); } /** * Resolves the claims request for the specified response type and * scope. The scope values that are {@link OIDCScopeValue standard * OpenID scope values} are resolved to their respective individual * claims requests, any other scope values are ignored. * * @param responseType The response type. Must not be {@code null}. * @param scope The scope, {@code null} if not specified (for a * plain OAuth 2.0 authorisation request with no * scope explicitly specified). * * @return The claims request. */ public static ClaimsRequest resolve(final ResponseType responseType, final Scope scope) { return resolve(responseType, scope, Collections.>emptyMap()); } /** * Resolves the claims request for the specified response type and * scope. The scope values that are {@link OIDCScopeValue standard * OpenID scope values} are resolved to their respective individual * claims requests, any other scope values are checked in the specified * custom claims map and resolved accordingly. * * @param responseType The response type. Must not be {@code null}. * @param scope The scope, {@code null} if not specified (for a * plain OAuth 2.0 authorisation request with no * scope explicitly specified). * @param customClaims Custom scope value to set of claim names map, * {@code null} if not specified. * * @return The claims request. */ public static ClaimsRequest resolve(final ResponseType responseType, final Scope scope, final Map> customClaims) { // Determine the claims target (ID token or UserInfo) final boolean switchToIDToken = responseType.contains(OIDCResponseTypeValue.ID_TOKEN) && !responseType.contains(ResponseType.Value.CODE) && !responseType.contains(ResponseType.Value.TOKEN); ClaimsRequest claimsRequest = new ClaimsRequest(); if (scope == null) { // Plain OAuth 2.0 mode return claimsRequest; } for (Scope.Value value : scope) { Set entries; if (value.equals(OIDCScopeValue.PROFILE)) { entries = OIDCScopeValue.PROFILE.toClaimsRequestEntries(); } else if (value.equals(OIDCScopeValue.EMAIL)) { entries = OIDCScopeValue.EMAIL.toClaimsRequestEntries(); } else if (value.equals(OIDCScopeValue.PHONE)) { entries = OIDCScopeValue.PHONE.toClaimsRequestEntries(); } else if (value.equals(OIDCScopeValue.ADDRESS)) { entries = OIDCScopeValue.ADDRESS.toClaimsRequestEntries(); } else if (customClaims != null && customClaims.containsKey(value)) { // Process custom scope value -> claim names expansion, e.g. // "corp_profile" -> ["employeeNumber", "dept", "ext"] Set claimNames = customClaims.get(value); if (claimNames == null || claimNames.isEmpty()) { continue; // skip } entries = new HashSet<>(); for (String claimName: claimNames) { entries.add(new ClaimsRequest.Entry(claimName, ClaimRequirement.VOLUNTARY)); } } else { continue; // skip } for (ClaimsRequest.Entry en : entries) { if (switchToIDToken) claimsRequest.addIDTokenClaim(en); else claimsRequest.addUserInfoClaim(en); } } return claimsRequest; } /** * Resolves the merged claims request from the specified OpenID * authentication request parameters. The scope values that are {@link * OIDCScopeValue standard OpenID scope values} are resolved to their * respective individual claims requests, any other scope values are * ignored. * * @param responseType The response type. Must not be {@code null}. * @param scope The scope, {@code null} if not specified (for a * plain OAuth 2.0 authorisation request with no * scope explicitly specified). * @param claimsRequest The claims request, corresponding to the * optional {@code claims} OpenID Connect * authorisation request parameter, {@code null} * if not specified. * * @return The merged claims request. */ public static ClaimsRequest resolve(final ResponseType responseType, final Scope scope, final ClaimsRequest claimsRequest) { return resolve(responseType, scope, claimsRequest, Collections.>emptyMap()); } /** * Resolves the merged claims request from the specified OpenID * authentication request parameters. The scope values that are {@link * OIDCScopeValue standard OpenID scope values} are resolved to their * respective individual claims requests, any other scope values are * checked in the specified custom claims map and resolved accordingly. * * @param responseType The response type. Must not be {@code null}. * @param scope The scope, {@code null} if not specified (for a * plain OAuth 2.0 authorisation request with no * scope explicitly specified). * @param claimsRequest The claims request, corresponding to the * optional {@code claims} OpenID Connect * authorisation request parameter, {@code null} * if not specified. * @param customClaims Custom scope value to set of claim names map, * {@code null} if not specified. * * @return The merged claims request. */ public static ClaimsRequest resolve(final ResponseType responseType, final Scope scope, final ClaimsRequest claimsRequest, final Map> customClaims) { ClaimsRequest mergedClaimsRequest = resolve(responseType, scope, customClaims); mergedClaimsRequest.add(claimsRequest); return mergedClaimsRequest; } /** * Resolves the merged claims request for the specified OpenID * authentication request. The scope values that are {@link * OIDCScopeValue standard OpenID scope values} are resolved to their * respective individual claims requests, any other scope values are * ignored. * * @param authRequest The OpenID authentication request. Must not be * {@code null}. * * @return The merged claims request. */ public static ClaimsRequest resolve(final AuthenticationRequest authRequest) { return resolve(authRequest.getResponseType(), authRequest.getScope(), authRequest.getClaims()); } private static JSONObject parseFirstVerifiedClaimsObject(final JSONObject containingObject) throws ParseException { if (containingObject.get("verified_claims") instanceof JSONObject) { // JSON object is the simple case return JSONObjectUtils.getJSONObject(containingObject, "verified_claims"); } if (containingObject.get("verified_claims") instanceof JSONArray) { // Try JSON array, take first element, ignore rest (use new OIDCClaimsRequest class to handle this case) List elements = JSONArrayUtils.toJSONObjectList(JSONObjectUtils.getJSONArray(containingObject, "verified_claims")); if (elements.size() > 0) { return elements.get(0); } } return null; } /** * Parses a claims request from the specified JSON object * representation. Unexpected members in the JSON object are silently * ignored. * * @param jsonObject The JSON object to parse. Must not be * {@code null}. * * @return The claims request. * * @throws ParseException If parsing failed. */ public static ClaimsRequest parse(final JSONObject jsonObject) throws ParseException { ClaimsRequest claimsRequest = new ClaimsRequest(); try { JSONObject idTokenObject = JSONObjectUtils.getJSONObject(jsonObject, "id_token", null); if (idTokenObject != null) { for (Entry entry : Entry.parseEntries(idTokenObject)) { if ("verified_claims".equals(entry.getClaimName())) { continue; //skip } claimsRequest.addIDTokenClaim(entry); } JSONObject verifiedClaimsObject = parseFirstVerifiedClaimsObject(idTokenObject); if (verifiedClaimsObject != null) { // id_token -> verified_claims -> claims JSONObject claimsObject = JSONObjectUtils.getJSONObject(verifiedClaimsObject, "claims", null); if (claimsObject != null) { if (claimsObject.isEmpty()) { String msg = "Invalid claims object: Empty verification claims object"; throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); } for (Entry entry : Entry.parseEntries(claimsObject)) { claimsRequest.addVerifiedIDTokenClaim(entry); } } // id_token -> verified_claims -> verification claimsRequest.setIDTokenClaimsVerificationJSONObject(JSONObjectUtils.getJSONObject(verifiedClaimsObject, "verification", null)); } } JSONObject userInfoObject = JSONObjectUtils.getJSONObject(jsonObject, "userinfo", null); if (userInfoObject != null) { for (Entry entry : Entry.parseEntries(userInfoObject)) { if ("verified_claims".equals(entry.getClaimName())) { continue; //skip } claimsRequest.addUserInfoClaim(entry); } JSONObject verifiedClaimsObject = parseFirstVerifiedClaimsObject(userInfoObject); if (verifiedClaimsObject != null) { // userinfo -> verified_claims -> claims JSONObject claimsObject = JSONObjectUtils.getJSONObject(verifiedClaimsObject, "claims", null); if (claimsObject != null) { if (claimsObject.isEmpty()) { String msg = "Invalid claims object: Empty verification claims object"; throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg)); } for (Entry entry : Entry.parseEntries(claimsObject)) { claimsRequest.addVerifiedUserInfoClaim(entry); } } // userinfo -> verified_claims -> verification claimsRequest.setUserInfoClaimsVerificationJSONObject(JSONObjectUtils.getJSONObject(verifiedClaimsObject, "verification", null)); } } } catch (Exception e) { if (e instanceof ParseException) { throw e; } } return claimsRequest; } /** * Parses a claims request from the specified JSON object string * representation. Unexpected members in the JSON object are silently * ignored. * * @param json The JSON object string to parse. Must not be * {@code null}. * * @return The claims request. * * @throws ParseException If the string couldn't be parsed to a valid * JSON object. */ public static ClaimsRequest parse(final String json) throws ParseException { return parse(JSONObjectUtils.parse(json)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy