Skip to content

Commit

Permalink
feat(jans-auth-server): automatically provision scopes if they are pr…
Browse files Browse the repository at this point in the history
…esent in the SSA for trusted issuer #5164 (#5553)

#5164
  • Loading branch information
yuriyz committed Jul 14, 2023
1 parent 2f3ea45 commit abaa10f
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 11 deletions.
35 changes: 34 additions & 1 deletion docs/admin/auth-server/endpoints/client-registration.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,23 @@ parameters:
point to JWKS URI.
- `dcrSignatureValidationSoftwareStatementJwksClaim` - specifies claim name inside software statement. Value of claim
should point to inlined JWKS.
- `trustedSsaIssuers` - map of trusted SSA issuers with configuration (e.g. automatically granted scopes). When empty - no issuers validation is performed. When not empty - AS forces validation and each SSA must match at least one entry from list.
- `automaticallyGrantedScopes` - automatically granted scopes for trusted issuer
```json
{
"https://trusted.as.com": {
"automaticallyGrantedScopes": [
"a",
"b"
]
},
"https://another-trusted.as.com": {
"automaticallyGrantedScopes": [
"d"
]
}
}
```
Configure the AS using steps explained in the [link](#curl-commands-to-configure-jans-auth-server)
Expand Down Expand Up @@ -501,9 +518,25 @@ When create entry in `dcrSsaValidationConfigs` configuration property :
**Via other configuration properties**
* **trustedSsaIssuers** - map of trusted SSA issuers with configuration (e.g. automatically granted scopes). When empty - no issuers validation is performed. When not empty - AS forces validation and each SSA must match at least one entry from list.
* **automaticallyGrantedScopes** - automatically granted scopes for trusted issuer
```json
{
"https://trusted.as.com": {
"automaticallyGrantedScopes": [
"a",
"b"
]
},
"https://another-trusted.as.com": {
"automaticallyGrantedScopes": [
"d"
]
}
}
```
* **dcrSignatureValidationJwks** - specifies JWKS for DCR's validations
* **dcrSignatureValidationJwksUri** - specifies JWKS URI for DCR's validations
* **trustedSsaIssuers** - List of trusted SSA issuers. If MTLS private key is used to sign DCR JWT, certificate issuer is checked as well.
* **dcrSignatureValidationSharedSecret** - if HMAC is used, this is the shared secret
* **dcrSignatureValidationEnabled** - boolean value enables DCR signature validation. Default is false
* **dcrSignatureValidationSoftwareStatementJwksURIClaim** - specifies claim name inside software statement. Value of claim should point to JWKS URI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ tags:
| tokenEndpointAuthSigningAlgValuesSupported | A list of the JWS signing algorithms (alg values) supported by the Token Endpoint for the signature on the JWT used to authenticate the Client at the Token Endpoint for the private_key_jwt and client_secret_jwt authentication methods | [Details](#tokenendpointauthsigningalgvaluessupported) |
| tokenRevocationEndpoint | The URL for the access_token or refresh_token revocation endpoint | [Details](#tokenrevocationendpoint) |
| trustedClientEnabled | Boolean value specifying whether a client is trusted and no authorization is required | [Details](#trustedclientenabled) |
| trustedSsaIssuers | List of trusted SSA issuers. If MTLS private key is used to sign DCR JWT, certificate issuer is checked as well. | [Details](#trustedssaissuers) |
| trustedSsaIssuers | List of trusted SSA issuers with configuration (e.g. automatically granted scopes). | [Details](#trustedssaissuers) |
| uiLocalesSupported | This list details the languages and scripts supported for the user interface | [Details](#uilocalessupported) |
| umaAddScopesAutomatically | Add UMA scopes automatically if it is not registered yet | [Details](#umaaddscopesautomatically) |
| umaConfigurationEndpoint | UMA Configuration endpoint URL | [Details](#umaconfigurationendpoint) |
Expand Down Expand Up @@ -2466,7 +2466,7 @@ tags:

### trustedSsaIssuers

- Description: List of trusted SSA issuers. If MTLS private key is used to sign DCR JWT, certificate issuer is checked as well.
- Description: List of trusted SSA issuers with configuration (e.g. automatically granted scopes).

- Required: No

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -660,8 +660,8 @@ public class AppConfiguration implements Configuration {
@DocProperty(description = "Boolean value indicating if DCR authorization allowed with MTLS", defaultValue = "false")
private Boolean dcrAuthorizationWithMTLS = false;

@DocProperty(description = "List of trusted SSA issuers. If MTLS private key is used to sign DCR JWT, certificate issuer is checked as well.")
private List<String> trustedSsaIssuers = new ArrayList<>();
@DocProperty(description = "List of trusted SSA issuers with configuration (e.g. automatically granted scopes).")
private Map<String, TrustedIssuerConfig> trustedSsaIssuers = new HashMap<>();

@DocProperty(description = "Cache in local memory cache attributes, scopes, clients and organization entry with expiration 60 seconds", defaultValue = "false")
private Boolean useLocalCache = false;
Expand Down Expand Up @@ -1300,12 +1300,12 @@ public void setDcrAuthorizationWithMTLS(Boolean dcrAuthorizationWithMTLS) {
this.dcrAuthorizationWithMTLS = dcrAuthorizationWithMTLS;
}

public List<String> getTrustedSsaIssuers() {
if (trustedSsaIssuers == null) trustedSsaIssuers = new ArrayList<>();
public Map<String, TrustedIssuerConfig> getTrustedSsaIssuers() {
if (trustedSsaIssuers == null) trustedSsaIssuers = new HashMap<>();
return trustedSsaIssuers;
}

public void setTrustedSsaIssuers(List<String> trustedSsaIssuers) {
public void setTrustedSsaIssuers(Map<String, TrustedIssuerConfig> trustedSsaIssuers) {
this.trustedSsaIssuers = trustedSsaIssuers;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.jans.as.model.configuration;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
* @author Yuriy Z
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class TrustedIssuerConfig implements Serializable {

@JsonProperty("automaticallyGrantedScopes")
private List<String> automaticallyGrantedScopes = new ArrayList<>();

public List<String> getAutomaticallyGrantedScopes() {
if (automaticallyGrantedScopes == null) automaticallyGrantedScopes = new ArrayList<>();
return automaticallyGrantedScopes;
}

public void setAutomaticallyGrantedScopes(List<String> automaticallyGrantedScopes) {
this.automaticallyGrantedScopes = automaticallyGrantedScopes;
}

@Override
public String toString() {
return "TrustedIssuerConfig{" +
"automaticallyGrantedScopes=" + automaticallyGrantedScopes +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@
import io.jans.as.model.common.SoftwareStatementValidationType;
import io.jans.as.model.common.SubjectType;
import io.jans.as.model.configuration.AppConfiguration;
import io.jans.as.model.configuration.TrustedIssuerConfig;
import io.jans.as.model.crypto.AbstractCryptoProvider;
import io.jans.as.model.crypto.signature.AlgorithmFamily;
import io.jans.as.model.crypto.signature.SignatureAlgorithm;
import io.jans.as.model.error.ErrorResponseFactory;
import io.jans.as.model.exception.CryptoProviderException;
import io.jans.as.model.exception.InvalidJwtException;
import io.jans.as.model.jwt.Jwt;
import io.jans.as.model.jwt.JwtClaimName;
import io.jans.as.model.register.RegisterErrorResponseType;
import io.jans.as.model.register.RegisterRequestParam;
import io.jans.as.model.ssa.SsaValidationType;
import io.jans.as.model.util.Pair;
import io.jans.as.server.ciba.CIBARegisterParamsValidatorService;
Expand All @@ -37,10 +40,17 @@
import jakarta.ws.rs.core.Response;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.Nullable;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static io.jans.as.model.register.RegisterRequestParam.SOFTWARE_STATEMENT;
import static io.jans.as.model.util.StringUtils.implode;
import static org.apache.commons.lang3.BooleanUtils.isFalse;
import static org.apache.commons.lang3.BooleanUtils.isTrue;

Expand Down Expand Up @@ -214,6 +224,57 @@ private JSONObject getJwks(HttpServletRequest httpRequest, Jwt jwt, String jwksU
}

public JSONObject validateSoftwareStatement(HttpServletRequest httpServletRequest, JSONObject requestObject) {
final JSONObject jsonObject = validateSSA(httpServletRequest, requestObject);
TrustedIssuerConfig trustedIssuerConfig = validateIssuer(jsonObject);
applyTrustedIssuerConfig(trustedIssuerConfig, jsonObject);
return jsonObject;
}

public void applyTrustedIssuerConfig(TrustedIssuerConfig trustedIssuerConfig, JSONObject jsonObject) {
if (trustedIssuerConfig == null) {
return;
}

final List<String> automaticallyGrantedScopes = trustedIssuerConfig.getAutomaticallyGrantedScopes();
if (automaticallyGrantedScopes.isEmpty()) {
return;
}

final Set<String> scopes = new HashSet<>(automaticallyGrantedScopes);
final String scopeString = jsonObject.optString(RegisterRequestParam.SCOPE.toString());
if (StringUtils.isNotBlank(scopeString)) {
scopes.addAll(io.jans.as.model.util.StringUtils.spaceSeparatedToList(scopeString));
}

final JSONArray scopeJsonArray = jsonObject.optJSONArray(RegisterRequestParam.SCOPE.toString());
if (scopeJsonArray != null) {
scopes.addAll(io.jans.as.model.util.StringUtils.toList(scopeJsonArray));
}

jsonObject.putOpt(RegisterRequestParam.SCOPE.toString(), implode(scopes, " "));
}

public TrustedIssuerConfig validateIssuer(JSONObject jsonObject) {
final String issuer = jsonObject.optString(JwtClaimName.ISSUER);
if (StringUtils.isBlank(issuer)) {
log.trace("SSA does not contain 'iss' (issuer).");
throw errorResponseFactory.createWebApplicationException(Response.Status.BAD_REQUEST, RegisterErrorResponseType.INVALID_SOFTWARE_STATEMENT, "Failed to find 'iss' (issuer) in software statement");
}

final Map<String, TrustedIssuerConfig> trustedSsaIssuers = appConfiguration.getTrustedSsaIssuers();
if (trustedSsaIssuers.isEmpty()) {
return null; // nothing to check
}

final TrustedIssuerConfig trustedIssuerConfig = trustedSsaIssuers.get(issuer);
if (trustedIssuerConfig == null) {
log.trace("SSA issuer is not added as trusted in 'trustedSsaIssuers' AS configuration.");
throw errorResponseFactory.createWebApplicationException(Response.Status.BAD_REQUEST, RegisterErrorResponseType.INVALID_SOFTWARE_STATEMENT, "Failed to validate 'iss' (issuer) of software statement.");
}
return trustedIssuerConfig;
}

public JSONObject validateSSA(HttpServletRequest httpServletRequest, JSONObject requestObject) {
if (!requestObject.has(SOFTWARE_STATEMENT.toString())) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.jans.as.client.RegisterRequest;
import io.jans.as.common.model.registration.Client;
import io.jans.as.model.configuration.AppConfiguration;
import io.jans.as.model.configuration.TrustedIssuerConfig;
import io.jans.as.model.error.ErrorResponseFactory;
import io.jans.as.model.error.IErrorType;
import io.jans.as.model.jwt.Jwt;
Expand All @@ -17,18 +18,19 @@
import io.jans.model.SimpleCustomProperty;
import io.jans.model.custom.script.conf.CustomScriptConfiguration;
import io.jans.service.cdi.util.CdiUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.core.Response;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* @author Yuriy Zabrovarnyy
Expand Down Expand Up @@ -130,7 +132,8 @@ public void validateSSA() {
}

public void validateIssuer() {
final List<String> issuers = CdiUtil.bean(AppConfiguration.class).getTrustedSsaIssuers();
final Map<String, TrustedIssuerConfig> issuerConfigs = CdiUtil.bean(AppConfiguration.class).getTrustedSsaIssuers();
final Set<String> issuers = issuerConfigs.keySet();
if (issuers.isEmpty()) { // nothing to check
return;
}
Expand Down
Loading

0 comments on commit abaa10f

Please sign in to comment.