com.nimbusds.openid.connect.sdk.claims.ClaimsSetRequest Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of oauth2-oidc-sdk Show documentation
Show all versions of oauth2-oidc-sdk Show documentation
OAuth 2.0 SDK with OpenID Connection extensions for developing client
and server applications.
/*
* oauth2-oidc-sdk
*
* Copyright 2012-2020, 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.claims;
import java.util.*;
import net.jcip.annotations.Immutable;
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.ParseException;
import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
/**
* OpenID Connect claims set request, intended to represent the
* {@code userinfo} and {@code id_token} elements in a
* {@link com.nimbusds.openid.connect.sdk.OIDCClaimsRequest claims} request
* parameter.
*
* Example:
*
*
* {
* "given_name": {"essential": true},
* "nickname": null,
* "email": {"essential": true},
* "email_verified": {"essential": true},
* "picture": null,
* "http://example.info/claims/groups": null
* }
*
*
* Related specifications:
*
*
* - OpenID Connect Core 1.0, section 5.5.
*
- OpenID Connect for Identity Assurance 1.0.
*
*/
@Immutable
public class ClaimsSetRequest 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, as string, number or JSON object.
*/
private final Object value;
/**
* Optional claim values, as an array of JSON entities.
*/
private final List> values;
/**
* Optional 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 {@link ClaimRequirement#VOLUNTARY
* 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. 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 Object 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 getClaimName(false);
}
/**
* 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;
}
/**
* Sets the claim requirement.
*
* @param requirement The claim requirement. Must not be
* {@code null},
*
* @return The updated entry.
*/
public ClaimsSetRequest.Entry withClaimRequirement(final ClaimRequirement requirement) {
return new ClaimsSetRequest.Entry(claimName, requirement, langTag, value, values, purpose, additionalInformation);
}
/**
* Returns the claim requirement.
*
* @return The claim requirement.
*/
public ClaimRequirement getClaimRequirement() {
return requirement;
}
/**
* Sets the language tag for the claim.
*
* @param langTag The language tag, {@code null} if not
* specified.
*
* @return The updated entry.
*/
public ClaimsSetRequest.Entry withLangTag(final LangTag langTag) {
return new ClaimsSetRequest.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;
}
/**
* Sets the requested value (as string) for the claim.
*
* @param value The value, {@code null} if not specified.
*
* @return The updated entry.
*/
public ClaimsSetRequest.Entry withValue(final String value) {
return new ClaimsSetRequest.Entry(claimName, requirement, langTag, value, null, purpose, additionalInformation);
}
/**
* Sets the requested value (as number) for the claim.
*
* @param value The value, {@code null} if not specified.
*
* @return The updated entry.
*/
public ClaimsSetRequest.Entry withValue(final Number value) {
return new ClaimsSetRequest.Entry(claimName, requirement, langTag, value, null, purpose, additionalInformation);
}
/**
* Sets the requested value (as JSON object) for the claim.
*
* @param value The value, {@code null} if not specified.
*
* @return The updated entry.
*/
public ClaimsSetRequest.Entry withValue(final JSONObject value) {
return new ClaimsSetRequest.Entry(claimName, requirement, langTag, value, null, purpose, additionalInformation);
}
/**
* Sets the requested value (untyped) for the claim.
*
* @param value The value, {@code null} if not specified.
*
* @return The updated entry.
*/
public ClaimsSetRequest.Entry withValue(final Object value) {
return new ClaimsSetRequest.Entry(claimName, requirement, langTag, value, null, purpose, additionalInformation);
}
/**
* Returns the requested value (as string) for the claim.
*
* @return The value as string, {@code null} if not specified
* or the value isn't a string.
*/
public String getValueAsString() {
if (value instanceof String) {
return (String)value;
} else {
return null;
}
}
/**
* Returns the requested value (as string) for the claim. Use
* {@link #getValueAsString()} instead.
*
* @return The value as string, {@code null} if not specified
* or the value isn't a string.
*/
@Deprecated
public String getValue() {
return getValueAsString();
}
/**
* Returns the requested value (as number) for the claim.
*
* @return The value as number, {@code null} if not specified
* or the value isn't a number.
*/
public Number getValueAsNumber() {
if (value instanceof Number) {
return (Number)value;
} else {
return null;
}
}
/**
* Returns the requested value (as JSON object) for the claim.
*
* @return The value as JSON object, {@code null} if not
* specified or the value isn't a JSON object.
*/
public JSONObject getValueAsJSONObject() {
if (value instanceof JSONObject) {
return (JSONObject)value;
} else {
return null;
}
}
/**
* Returns the requested value (untyped) for the claim.
*
* @return The value (untyped), {@code null} if not specified.
*/
public Object getRawValue() {
return value;
}
/**
* Sets the requested values (untyped) for the claim.
*
* @param values The values, {@code null} if not specified.
*
* @return The updated entry.
*/
public ClaimsSetRequest.Entry withValues(final List> values) {
return new ClaimsSetRequest.Entry(claimName, requirement, langTag, null, values, purpose, additionalInformation);
}
/**
* Returns the requested values (as strings) for the claim.
*
* @return The values as list of strings, {@code null} if not
* specified or the values aren't strings.
*/
public List getValuesAsListOfStrings() {
if (values == null) {
return null;
}
if (values.isEmpty()) {
return Collections.emptyList();
}
List list = new ArrayList<>(values.size());
for (Object v: values) {
if (v instanceof String) {
list.add((String)v);
} else {
return null;
}
}
return list;
}
/**
* Returns the requested values (as strings) for the claim. Use
* {@link #getValuesAsListOfStrings()} instead.
*
* @return The values as list of strings, {@code null} if not
* specified or the values aren't strings.
*/
@Deprecated
public List getValues() {
return getValuesAsListOfStrings();
}
/**
* Returns the requested values (as JSON objects) for the
* claim.
*
* @return The values as list of JSON objects, {@code null} if
* not specified or the values aren't JSON objects.
*/
public List getValuesAsListOfJSONObjects() {
if (values == null) {
return null;
}
if (values.isEmpty()) {
return Collections.emptyList();
}
List list = new ArrayList<>(values.size());
for (Object v: values) {
if (v instanceof JSONObject) {
list.add((JSONObject) v);
} else {
return null;
}
}
return list;
}
/**
* Returns the requested values (untyped) for the claim.
*
* @return The values as list of untyped objects, {@code null}
* if not specified.
*/
public List> getValuesAsRawList() {
return values;
}
/**
* Sets the purpose for which the claim is requested.
*
* @param purpose The purpose, {@code null} if not specified.
*
* @return The updated entry.
*/
public ClaimsSetRequest.Entry withPurpose(final String purpose) {
return new ClaimsSetRequest.Entry(claimName, requirement, langTag, value, values, purpose, additionalInformation);
}
/**
* Returns the optional purpose for which the claim is
* requested.
*
* @return The purpose, {@code null} if not specified.
*/
public String getPurpose() {
return purpose;
}
/**
* Sets additional information for the requested 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 updated entry.
*/
public ClaimsSetRequest.Entry withAdditionalInformation(final Map additionalInformation) {
return new ClaimsSetRequest.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 entry for this individual claim
* request.
*
* @return The JSON object entry.
*/
public Map.Entry toJSONObjectEntry() {
// Compose the optional value
JSONObject entrySpec = null;
if (getRawValue() != null) {
entrySpec = new JSONObject();
entrySpec.put("value", getRawValue());
}
if (getValuesAsRawList() != null) {
// Either "value" or "values", or none
// may be defined
entrySpec = new JSONObject();
entrySpec.put("values", getValuesAsRawList());
}
if (getClaimRequirement().equals(ClaimRequirement.ESSENTIAL)) {
if (entrySpec == null)
entrySpec = new JSONObject();
entrySpec.put("essential", true);
}
if (getPurpose() != null) {
if (entrySpec == null) {
entrySpec = new JSONObject();
}
entrySpec.put("purpose", getPurpose());
}
if (getAdditionalInformation() != null) {
if (entrySpec == null) {
entrySpec = new JSONObject();
}
for (Map.Entry additionalInformationEntry : getAdditionalInformation().entrySet()) {
entrySpec.put(additionalInformationEntry.getKey(), additionalInformationEntry.getValue());
}
}
return new AbstractMap.SimpleImmutableEntry<>(getClaimName(true), entrySpec);
}
/**
* Parses an individual claim request from the specified JSON
* object entry.
*
* @param jsonObjectEntry The JSON object entry to parse. Must
* not be {@code null}.
*
* @return The individual claim request.
*
* @throws ParseException If parsing failed.
*/
public static ClaimsSetRequest.Entry parse(final Map.Entry jsonObjectEntry)
throws ParseException {
// Process the key
String claimNameWithOptLangTag = jsonObjectEntry.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) {
throw new ParseException(e.getMessage(), e);
}
} else {
claimName = claimNameWithOptLangTag;
}
// Parse the optional spec
JSONObject spec = jsonObjectEntry.getValue();
if (spec == null) {
// Voluntary claim with no value(s)
return new ClaimsSetRequest.Entry(claimName).withLangTag(langTag);
}
ClaimRequirement requirement = ClaimRequirement.VOLUNTARY;
if (spec.containsKey("essential")) {
boolean isEssential = JSONObjectUtils.getBoolean(spec, "essential");
if (isEssential)
requirement = ClaimRequirement.ESSENTIAL;
}
String purpose = JSONObjectUtils.getString(spec, "purpose", null);
if (spec.get("value") != null) {
Object expectedValue = spec.get("value");
Map additionalInformation = getAdditionalInformationFromClaim(spec);
return new ClaimsSetRequest.Entry(claimName, requirement, langTag, expectedValue, null, purpose, additionalInformation);
} else if (spec.get("values") != null) {
List