com.nimbusds.openid.connect.provider.spi.reg.statement.Configuration Maven / Gradle / Ivy
Show all versions of software-statement-verifier Show documentation
package com.nimbusds.openid.connect.provider.spi.reg.statement;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import com.thetransactioncompany.util.PropertyFilter;
import com.thetransactioncompany.util.PropertyParseException;
import com.thetransactioncompany.util.PropertyRetriever;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.nimbusds.common.config.ConfigurationException;
import com.nimbusds.common.config.LoggableConfiguration;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.oauth2.sdk.id.Issuer;
import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
import com.nimbusds.oauth2.sdk.util.StringUtils;
/**
* Software statement verifier configuration. It is typically derived from a
* Java key / value properties file. The configuration is stored as public
* fields which become immutable (final) after their initialisation.
*
* Example configuration properties:
*
*
* op.ssv.enable=true
* op.ssv.issuer=https://publisher.example.com
* op.ssv.issuerJWKSetURL=https://publisher.example.com/jwks.json
* op.ssv.jwsAlgorithms=RS256,PS256
* op.ssv.connectTimeout=250
* op.ssv.readTimeout=250
* op.ssv.registrationAccessToken=ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
* op.ssv.additionalRequiredClaims=
* op.ssv.logClaims=iss
* op.ssv.clientX509Certificate.require=false
* op.ssv.clientX509Certificate.rootDN=
* op.ssv.requestType=JSON
* op.ssv.requestJWT.jwkSetSource=
* op.ssv.requestJWT.jwsAlgorithms=
* op.ssv.requestJWT.requiredClaims=
* op.ssv.transforms.remove=iss,iat,jti
* op.ssv.transforms.rename.software_jwks_endpoint=jwks_uri
* op.ssv.transforms.rename.software_client_name=client_name
* op.ssv.transforms.moveIntoData=org_id,org_contacts
*
*/
public final class Configuration implements LoggableConfiguration {
/**
* The configuration file path.
*/
public static final String FILE_PATH = "/WEB-INF/softwareStatementVerifier.properties";
/**
* The default properties prefix.
*/
public static final String DEFAULT_PREFIX = "op.ssv.";
/**
* Enables / disables the software statements verifier. Disabled by
* default.
*
* Property key: [prefix]enable
*/
public final boolean enable;
/**
* The software statements issuer.
*
*
Property key: [prefix]issuer
*/
public final Issuer issuer;
/**
* The URL where the software statements issuer publishes its public
* JWK set.
*
*
Property key: [prefix]issuerJWKSetURL
*/
public final URL issuerJWKSetURL;
/**
* The accepted JWS algorithms for the software statements.
*
*
Property key: [prefix]jwsAlgorithms
*/
public final Set jwsAlgorithms;
/**
* The timeout in milliseconds for establishing HTTP connections. If
* zero the underlying HTTP client library will determine the timeout.
*
* Property key: [prefix]httpConnectTimeout
*/
public final int httpConnectTimeout;
/**
* The timeout in milliseconds for obtaining HTTP responses after
* connection. If zero the underlying HTTP client library will
* determine the timeout.
*
*
Property key: [prefix]readTimeout
*/
public final int httpReadTimeout;
/**
* An access token of type bearer (non-expiring) for accessing the
* client registration endpoint.
*
*
Property key: [prefix]registrationAccessToken
*/
public final BearerAccessToken registrationAccessToken;
/**
* The names of any additional JWT claims that must be present in the
* software statement, empty set if none.
*
*
Property key: [prefix]additionalRequiredClaims
*/
public final Set additionalRequiredClaims;
/**
* Names of software statement claims to log at INFO level.
*
* Property key: [prefix]logClaims
*/
public final Set logClaims;
/**
* If {@code true} the HTTP POST request must include a client X.509
* certificate validated by the TLS terminator / reverse proxy. The
* default value is {@code false}.
*
* Property key: [prefix]clientX509Certificate.require
*/
public final boolean clientX509Certificate_require;
/**
* The required root DN of the client X.509 certificates, if client
* certificates are required.
*
*
Property key: [prefix]clientX509Certificate.rootDN
*/
public final String clientX509Certificate_rootDN;
/**
* The accepted HTTP POST request type.
*
*
Property key: [prefix]requestType
*/
public final RequestType requestType;
/**
* The JWK set source for validating signed request JWTs.
*
*
Property key: [prefix]requestJWT.jwkSetSource
*/
public final JWKSetSource requestJWT_jwkSetSource;
/**
* The accepted JWS algorithms for the signed request JWTs.
*
*
Property key: [prefix]requestJWT.jwsAlgorithms
*/
public final Set requestJWT_jwsAlgorithms;
/**
* The names of the JWT claims that must be present in the signed
* request JWT, empty set if none.
*
* Property key: [prefix]requestJWT.requiredClaims
*/
public final Set requestJWT_requiredClaims;
/**
* List of names of top-level JSON object members to be removed from
* the merged client metadata.
*
* Property key: [prefix]transforms.remove
*/
public final List transforms_remove;
/**
* Map of names of top-level JSON object members to be renamed in the
* merged client metadata.
*
* Property key: [prefix]transforms.rename.[old-member-name]=[new-member-name]
*/
public final Map transforms_rename;
/**
* List of names of top-level JSON object members in the merged client
* metadata to be moved into the "data" JSON object member.
*
* Property key: [prefix]transforms.moveIntoData
*/
public final List transforms_moveIntoData;
/**
* Scope rules, keyed by rule name.
*
* Property key: [prefix]scopeRules.*
*/
public final Map scopeRules;
/**
* Creates a new software statement verifier configuration from the
* specified properties. System property override is enabled.
*
* @param props The properties. Must not be {@code null}.
*
* @throws ConfigurationException On a missing or invalid property.
*/
public Configuration(final Properties props)
throws ConfigurationException {
var pr = new PropertyRetriever(props, true);
try {
enable = pr.getOptBoolean(DEFAULT_PREFIX + "enable", false);
if (! enable) {
issuer = null;
issuerJWKSetURL = null;
jwsAlgorithms = Collections.emptySet();
httpConnectTimeout = 0;
httpReadTimeout = 0;
registrationAccessToken = null;
additionalRequiredClaims = Collections.emptySet();
logClaims = Collections.emptySet();
clientX509Certificate_require = false;
clientX509Certificate_rootDN = null;
requestType = RequestType.JSON;
requestJWT_jwkSetSource = null;
requestJWT_jwsAlgorithms = Collections.emptySet();
requestJWT_requiredClaims = Collections.emptySet();
transforms_remove = Collections.emptyList();
transforms_rename = Collections.emptyMap();
transforms_moveIntoData = Collections.emptyList();
scopeRules = Collections.emptyMap();
return;
}
issuer = new Issuer(pr.getString(DEFAULT_PREFIX + "issuer"));
issuerJWKSetURL = pr.getURL(DEFAULT_PREFIX + "issuerJWKSetURL");
var algorithms = new HashSet();
for (var algName: pr.getStringList(DEFAULT_PREFIX + "jwsAlgorithms")) {
algorithms.add(JWSAlgorithm.parse(algName));
}
jwsAlgorithms = Collections.unmodifiableSet(algorithms);
httpConnectTimeout = pr.getInt(DEFAULT_PREFIX + "connectTimeout");
httpReadTimeout = pr.getInt(DEFAULT_PREFIX + "readTimeout");
registrationAccessToken = new BearerAccessToken(pr.getString(DEFAULT_PREFIX + "registrationAccessToken"));
additionalRequiredClaims = new HashSet<>(pr.getOptStringList(DEFAULT_PREFIX + "additionalRequiredClaims", Collections.emptyList()));
logClaims = new LinkedHashSet<>(pr.getOptStringList(DEFAULT_PREFIX + "logClaims", List.of("iss")));
clientX509Certificate_require = pr.getOptBoolean(DEFAULT_PREFIX + "clientX509Certificate.require", false);
if (clientX509Certificate_require) {
clientX509Certificate_rootDN = pr.getOptString(DEFAULT_PREFIX + "clientX509Certificate.rootDN", null);
} else {
clientX509Certificate_rootDN = null;
}
requestType = pr.getOptEnum(DEFAULT_PREFIX + "requestType", RequestType.class, RequestType.JSON);
if (requestType.equals(RequestType.JWT)) {
try {
requestJWT_jwkSetSource = new JWKSetSource(pr.getURI(DEFAULT_PREFIX + "requestJWT.jwkSetSource"));
} catch (URISyntaxException e) {
throw new PropertyParseException(e.getMessage(), DEFAULT_PREFIX + "requestJWT.jwkSetSource");
}
algorithms = new HashSet<>();
for (var algName: pr.getStringList(DEFAULT_PREFIX + "requestJWT.jwsAlgorithms")) {
algorithms.add(JWSAlgorithm.parse(algName));
}
requestJWT_jwsAlgorithms = Collections.unmodifiableSet(algorithms);
requestJWT_requiredClaims = new HashSet<>(pr.getOptStringList(DEFAULT_PREFIX + "requestJWT.requiredClaims", Collections.emptyList()));
} else {
requestJWT_jwkSetSource = null;
requestJWT_jwsAlgorithms = Collections.emptySet();
requestJWT_requiredClaims = Collections.emptySet();
}
transforms_remove = pr.getOptStringList(DEFAULT_PREFIX + "transforms.remove", Collections.emptyList());
var mergedProps = new Properties();
mergedProps.putAll(props);
mergedProps.putAll(System.getProperties());
var renameProps = PropertyFilter.filterWithPrefix(DEFAULT_PREFIX + "transforms.rename.", mergedProps);
var renameMap = new HashMap();
for (var propName: renameProps.stringPropertyNames()) {
var oldMemberName = propName.substring((DEFAULT_PREFIX + "transforms.rename.").length());
var newMemberName = renameProps.getProperty(propName);
if (StringUtils.isNotBlank(oldMemberName) && StringUtils.isNotBlank(newMemberName)) {
renameMap.put(oldMemberName, newMemberName);
}
}
transforms_rename = Collections.unmodifiableMap(renameMap);
transforms_moveIntoData = pr.getOptStringList(DEFAULT_PREFIX + "transforms.moveIntoData", Collections.emptyList());
Map scopeRuleMap = new HashMap<>();
var scopeRulesProps = PropertyFilter.filterWithPrefix(DEFAULT_PREFIX + "scopeRules.", mergedProps);
for (var propName: scopeRulesProps.stringPropertyNames()) {
if (propName.startsWith(DEFAULT_PREFIX + "scopeRules.") && propName.endsWith(".scope")) {
var ruleName = propName.substring(
(DEFAULT_PREFIX + "scopeRules.").length(),
propName.lastIndexOf(".scope")
);
var scope = Scope.parse(pr.getString(propName));
var jsonPath = pr.getString(DEFAULT_PREFIX + "scopeRules." + ruleName + ".jsonPath");
scopeRuleMap.put(ruleName, new ScopeRule(scope, jsonPath));
}
}
scopeRules = Collections.unmodifiableMap(scopeRuleMap);
} catch (PropertyParseException e) {
throw new ConfigurationException(e.getMessage() + ": Property: " + e.getPropertyKey());
}
}
/**
* Logs the configuration details at INFO level. Properties that may
* adversely affect security are logged at WARN level.
*/
@Override
public void log() {
Logger log = LogManager.getLogger("MAIN");
log.info("[SSV0000] Software statement verifier configuration:");
log.info("[SSV0001] Software statement verifier enabled: {}", enable);
if (! enable) {
return;
}
log.info("[SSV0002] Software statement issuer: {}", issuer);
if ("https".equalsIgnoreCase(issuerJWKSetURL.getProtocol())) {
log.info("[SSV0003] Software statement issuer JWK set URL: {}", issuerJWKSetURL);
} else {
log.warn("[SSV0003] Software statement issuer JWK set URL (unsecured, consider using HTTPS): {}", issuerJWKSetURL);
}
log.info("[SSV0011] Software statement JWS algorithms: {}", jwsAlgorithms);
log.info("[SSV0004] HTTP connect timeout: {} ms", httpConnectTimeout);
log.info("[SSV0005] HTTP read timeout: {} ms", httpReadTimeout);
log.info("[SSV0006] Registration access token configured: {}", registrationAccessToken != null);
log.info("[SSV0007] Additional required software statement JWT claims: {}", additionalRequiredClaims != null ? additionalRequiredClaims : "none");
log.info("[SSV0018] Software statement claims to log at INFO level under SSV0100: {}", logClaims);
log.info("[SSV0008] Client X.509 certificate required: {}", clientX509Certificate_require);
if (clientX509Certificate_require) {
log.info("[SSV0014] Client X.509 certificate root DN: {}", clientX509Certificate_rootDN != null ? clientX509Certificate_rootDN : "not specified");
}
log.info("[SSV0009] Accepted registration request type: {}", requestType);
if (requestType.equals(RequestType.JWT)) {
log.info("[SSV0010] JWK set source for validating registration request JWTs: {}", requestJWT_jwkSetSource);
log.info("[SSV0012] Registration request JWS algorithms: {}", requestJWT_jwsAlgorithms);
log.info("[SSV0013] Required registration request JWT claims: {}", requestJWT_requiredClaims != null ? requestJWT_requiredClaims : "none");
}
log.info("[SSV0015] Merged client metadata transforms: Rename map: {}", transforms_rename);
log.info("[SSV0016] Merged client metadata transforms: Move into \"data\" list: {}", transforms_moveIntoData);
log.info("[SSV0017] Merged client metadata transforms: Remove list: {}", transforms_remove);
log.info("[SSV0019] Scope rules: {}", scopeRules);
}
}