diff --git a/docs/janssen-server/auth-server/endpoints/access-evaluation.md b/docs/janssen-server/auth-server/endpoints/access-evaluation.md index c7d2cd4602f..ed7b8d8ad04 100644 --- a/docs/janssen-server/auth-server/endpoints/access-evaluation.md +++ b/docs/janssen-server/auth-server/endpoints/access-evaluation.md @@ -24,13 +24,34 @@ The endpoint's responses are typically concise, aiming to provide a rapid decisi The goal is to provide a scalable, secure interface for dynamic and fine-grained access control across applications. -URL to access access evaluation endpoint on Janssen Server is listed in the response of Janssen Server's well-known -[configuration endpoint](./configuration.md) given below. +URL to access access evaluation endpoint on Janssen Server is listed in both: + - the response of Janssen Server's well-known [configuration endpoint](./configuration.md) given below. + - the response of Janssen Server's `/.well-known/authzen-configuration` endpoint. +**OpenID Discovery** ```text https://janssen.server.host/jans-auth/.well-known/openid-configuration ``` +**AuthZEN Discovery** +```text +https://janssen.server.host/jans-auth/.well-known/authzen-configuration +``` + +`/.well-known/authzen-configuration` allows to publish data specific to AuthZEN only. Response of AuthZEN discovery endpoint can be +changed via `AccessEvaluationDiscoveryType` custom script. + +**Snippet of AccessEvaluationDiscoveryType** +```java + @Override + public boolean modifyResponse(Object responseAsJsonObject, Object context) { + scriptLogger.info("write to script logger"); + JSONObject response = (JSONObject) responseAsJsonObject; + response.accumulate("key_from_java", "value_from_script_on_java"); + return true; + } +``` + `access_evaluation_v1_endpoint` claim in the response specifies the URL for access evaluation endpoint. By default, access evaluation endpoint looks like below: diff --git a/docs/janssen-server/developer/interception-scripts.md b/docs/janssen-server/developer/interception-scripts.md index f59ca54c584..2824bbeaaa8 100644 --- a/docs/janssen-server/developer/interception-scripts.md +++ b/docs/janssen-server/developer/interception-scripts.md @@ -38,6 +38,7 @@ calling external APIs 1. [Post Authentication](./scripts/post-authentication.md) 1. [Authorization Challenge](./scripts/authorization-challenge.md) 1. [Access Evaluation](./scripts/access-evaluation.md) +1. [Access Evaluation Discovery](./scripts/access-evaluation-discovery.md) 1. [Authz Detail](./scripts/authz-detail.md) 1. [Create User](./scripts/create-user.md) 1. [Select Account](./scripts/select-account.md) diff --git a/docs/janssen-server/developer/scripts/README.md b/docs/janssen-server/developer/scripts/README.md index e69344737e0..bb14b82c76a 100644 --- a/docs/janssen-server/developer/scripts/README.md +++ b/docs/janssen-server/developer/scripts/README.md @@ -36,6 +36,7 @@ overridden to implement your business case. | [Post Authentication](../../../script-catalog/post_authn/post-authentication.md) | | | [Authorization Challenge](../../../script-catalog/authorization_challenge/authorization-challenge.md) | | | [Access Evaluation](../../../script-catalog/access_evaluation/access-evaluation.md) | Access Evaluation custom script for Access Evaluation Endpoint (AuthZEN) | +| [Access Evaluation Discovery](../../../script-catalog/access_evaluation/access-evaluation-discovery.md) | Access Evaluation Discovery custom script for `/.well-known/authzen-configuration` Access Evaluation Discovery Endpoint (AuthZEN) | | [Select Account](../../../script-catalog/select_account/select-account.md) | | | [Resource Owner Password Credentials](../../../script-catalog/resource_owner_password_credentials/ropc.md) | | | [UMA 2 RPT Authorization Policies](../../../script-catalog/uma_rpt_policy/uma-rpt.md) | | diff --git a/docs/script-catalog/access_evaluation/AccessEvaluationDiscovery.java b/docs/script-catalog/access_evaluation/AccessEvaluationDiscovery.java new file mode 100644 index 00000000000..98cc146b4cb --- /dev/null +++ b/docs/script-catalog/access_evaluation/AccessEvaluationDiscovery.java @@ -0,0 +1,52 @@ +/* Copyright (c) 2024, Gluu + Author: Yuriy Z + */ + +import io.jans.model.SimpleCustomProperty; +import io.jans.model.custom.script.model.CustomScript; +import io.jans.model.custom.script.type.authzen.AccessEvaluationDiscoveryType; +import io.jans.service.custom.script.CustomScriptManager; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +public class AccessEvaluationDiscovery implements AccessEvaluationDiscoveryType { + + private static final Logger log = LoggerFactory.getLogger(AccessEvaluationDiscovery.class); + private static final Logger scriptLogger = LoggerFactory.getLogger(CustomScriptManager.class); + + @Override + public boolean init(Map configurationAttributes) { + log.info("Init of AccessEvaluationDiscovery Java custom script"); + return true; + } + + @Override + public boolean init(CustomScript customScript, Map configurationAttributes) { + log.info("Init of AccessEvaluationDiscovery Java custom script"); + return true; + } + + @Override + public boolean destroy(Map configurationAttributes) { + log.info("Destroy of AccessEvaluationDiscovery Java custom script"); + return true; + } + + @Override + public int getApiVersion() { + log.info("getApiVersion AccessEvaluationDiscovery Java custom script: 11"); + return 11; + } + + @Override + public boolean modifyResponse(Object responseAsJsonObject, Object context) { + scriptLogger.info("write to script logger"); + JSONObject response = (JSONObject) responseAsJsonObject; + response.accumulate("key_from_java", "value_from_script_on_java"); + return true; + } +} + diff --git a/docs/script-catalog/access_evaluation/access-evaluation-discovery.md b/docs/script-catalog/access_evaluation/access-evaluation-discovery.md new file mode 100644 index 00000000000..d64abe3c9ce --- /dev/null +++ b/docs/script-catalog/access_evaluation/access-evaluation-discovery.md @@ -0,0 +1,108 @@ +--- +tags: + - administration + - developer + - script-catalog +--- + +# Access Evaluation Discovery Custom Script + +## Overview + +This script is used to control Access Evaluation Discovery Endpoint (`/.well-known/authzen-configuration`). + +**Sample request** +``` +GET /.well-known/authzen-configuration HTTP/1.1 +Host: happy-example.gluu.info +Content-Type: application/json + +{"access_evaluation_v1_endpoint":"https://happy-example.gluu.info/jans-auth/restv1/evaluation"} +``` + + + +## Interface +The Access Evaluation Discovery script implements the [AccessEvaluationDiscoveryType](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/type/authzen/AccessEvaluationDiscoveryType.java) interface. +This extends methods from the base script type in addition to adding new methods: + +### Inherited Methods +| Method header | Method description | +|:-----|:------| +| `def init(self, customScript, configurationAttributes)` | This method is only called once during the script initialization. It can be used for global script initialization, initiate objects etc | +| `def destroy(self, configurationAttributes)` | This method is called once to destroy events. It can be used to free resource and objects created in the `init()` method | +| `def getApiVersion(self, configurationAttributes, customScript)` | The getApiVersion method allows API changes in order to do transparent migration from an old script to a new API. Only include the customScript variable if the value for getApiVersion is greater than 10 | + +### New methods +| Method header | Method description | +|:-----|:------| +| `def modifyResponse(self, responseAsJsonObject, context)` | This method is called after discovery response is ready. This method can modify discovery response.
`responseAsJsonObject` is `org.codehaus.jettison.json.JSONObject`
`context` is `io.jans.as.server.model.common.ExecutionContext` | + + +`modifyResponse` method returns `true` to access modification or `false` to revert all changes. + + +### Objects +| Object name | Object description | +|:-----|:------| +|`customScript`| The custom script object. [Reference](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/model/CustomScript.java) | +|`context`| [Reference](https://github.com/JanssenProject/jans/blob/main/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/context/ExternalScriptContext.java) | + + +## Sample Demo Custom Script + +### Script Type: Java + +```java +import io.jans.model.SimpleCustomProperty; +import io.jans.model.custom.script.model.CustomScript; +import io.jans.model.custom.script.type.authzen.AccessEvaluationDiscoveryType; +import io.jans.service.custom.script.CustomScriptManager; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +public class AccessEvaluationDiscovery implements AccessEvaluationDiscoveryType { + + private static final Logger log = LoggerFactory.getLogger(AccessEvaluationDiscovery.class); + private static final Logger scriptLogger = LoggerFactory.getLogger(CustomScriptManager.class); + + @Override + public boolean init(Map configurationAttributes) { + log.info("Init of AccessEvaluationDiscovery Java custom script"); + return true; + } + + @Override + public boolean init(CustomScript customScript, Map configurationAttributes) { + log.info("Init of AccessEvaluationDiscovery Java custom script"); + return true; + } + + @Override + public boolean destroy(Map configurationAttributes) { + log.info("Destroy of AccessEvaluationDiscovery Java custom script"); + return true; + } + + @Override + public int getApiVersion() { + log.info("getApiVersion AccessEvaluationDiscovery Java custom script: 11"); + return 11; + } + + @Override + public boolean modifyResponse(Object responseAsJsonObject, Object context) { + scriptLogger.info("write to script logger"); + JSONObject response = (JSONObject) responseAsJsonObject; + response.accumulate("key_from_java", "value_from_script_on_java"); + return true; + } +} +``` + + +## Sample Scripts +- [Access Evaluation Discovery](../../../script-catalog/access_evaluation/AccessEvaluationDiscovery.java) diff --git a/jans-auth-server/docs/swagger.yaml b/jans-auth-server/docs/swagger.yaml index 9f9c8ef6904..a35fa6c8fd0 100644 --- a/jans-auth-server/docs/swagger.yaml +++ b/jans-auth-server/docs/swagger.yaml @@ -19,6 +19,47 @@ tags: description: Janssen Authorization Server is an open source OpenID Connect Provider (OP) and UMA Authorization Server (AS). The project also includes OpenID Connect Client code which can be used by websites to validate tokens. Server currently implements all required aspects of the OpenID Connect stack, including an OAuth 2.0 authorization server, Simple Web Discovery, Dynamic Client Registration, JSON Web Tokens, JSON Web Keys, and User Info Endpoint. paths: + /.well-known/authzen-configuration: + get: + tags: + - AuthZEN Access Evaluation Discovery + summary: Gets Access Evaluation configuration data. + description: Gets Access Evaluation configuration data. + operationId: authzen-configuration + responses: + 200: + description: OK + content: + application/json: + schema: + type: object + description: AuthZEN Access Evaluation Discovery + required: + - access_evaluation_v1_endpoint + properties: + access_evaluation_v1_endpoint: + type: string + description: "URL of the Access Evaluation endpoint." + example: https://sample.com/jans-auth/restv1/evaluate + 500: + description: Error during request processing endpoint. + content: + application/json: + schema: + type: object + required: + - error + - error_description + properties: + error: + type: string + format: enum + example: + - server_error + error_description: + type: string + details: + type: string /evaluation: post: tags: @@ -982,6 +1023,11 @@ paths: type: string introspection_endpoint: type: string + description: "URL of the Introspection endpoint." + access_evaluation_v1_endpoint: + type: string + description: "URL of the Access Evaluation endpoint." + example: https://sample.com/jans-auth/restv1/evaluate auth_level_mapping: type: object additionalProperties: diff --git a/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/AppConfiguration.java b/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/AppConfiguration.java index 2bfbc30c484..ab8b86c45d4 100644 --- a/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/AppConfiguration.java +++ b/jans-auth-server/model/src/main/java/io/jans/as/model/configuration/AppConfiguration.java @@ -152,6 +152,9 @@ public class AppConfiguration implements Configuration { @DocProperty(description = "Access evaluation custom script name.") private String accessEvaluationScriptName; + @DocProperty(description = "Lifetime of access evaluation discovery cache (/.well-known/authzen-configuration).", defaultValue = "5") + private int accessEvaluationDiscoveryCacheLifetimeInMinutes = 5; + @DocProperty(description = "Boolean value true encrypts request object", defaultValue = "false") private Boolean requireRequestObjectEncryption = false; @@ -3532,6 +3535,15 @@ public AppConfiguration setAccessEvaluationAllowBasicClientAuthorization(Boolean return this; } + public int getAccessEvaluationDiscoveryCacheLifetimeInMinutes() { + return accessEvaluationDiscoveryCacheLifetimeInMinutes; + } + + public AppConfiguration setAccessEvaluationDiscoveryCacheLifetimeInMinutes(int accessEvaluationDiscoveryCacheLifetimeInMinutes) { + this.accessEvaluationDiscoveryCacheLifetimeInMinutes = accessEvaluationDiscoveryCacheLifetimeInMinutes; + return this; + } + public String getAccessEvaluationScriptName() { return accessEvaluationScriptName; } diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/authzen/ws/rs/AccessEvaluationDiscoveryService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/authzen/ws/rs/AccessEvaluationDiscoveryService.java new file mode 100644 index 00000000000..e3790ae277b --- /dev/null +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/authzen/ws/rs/AccessEvaluationDiscoveryService.java @@ -0,0 +1,65 @@ +package io.jans.as.server.authzen.ws.rs; + +import io.jans.as.model.common.FeatureFlagType; +import io.jans.as.model.configuration.AppConfiguration; +import io.jans.as.model.error.ErrorResponseFactory; +import io.jans.as.server.model.common.ExecutionContext; +import io.jans.as.server.service.DiscoveryService; +import io.jans.as.server.service.LocalResponseCache; +import io.jans.as.server.service.external.ExternalAccessEvaluationDiscoveryService; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.json.JSONObject; +import org.slf4j.Logger; + +import static io.jans.as.model.configuration.ConfigurationResponseClaim.ACCESS_EVALUATION_V1_ENDPOINT; + +/** + * @author Yuriy Z + */ +@ApplicationScoped +public class AccessEvaluationDiscoveryService { + + @Inject + private Logger log; + + @Inject + private ErrorResponseFactory errorResponseFactory; + + @Inject + private AppConfiguration appConfiguration; + + @Inject + private LocalResponseCache localResponseCache; + + @Inject + private ExternalAccessEvaluationDiscoveryService externalAccessEvaluationDiscoveryService; + + public JSONObject discovery(ExecutionContext context) { + errorResponseFactory.validateFeatureEnabled(FeatureFlagType.ACCESS_EVALUATION); + + final JSONObject cachedResponse = localResponseCache.getAccessEvaluationDiscoveryResponse(); + if (cachedResponse != null) { + log.trace("Cached access evaluation discovery response returned."); + return cachedResponse; + } + + JSONObject jsonObj = createResponse(); + JSONObject clone = new JSONObject(jsonObj.toString()); + + if (!externalAccessEvaluationDiscoveryService.modifyDiscovery(jsonObj, context)) { + jsonObj = clone; // revert to original state if object was modified in script + } + + localResponseCache.putAccessEvaluationDiscoveryResponse(jsonObj); + return jsonObj; + } + + private JSONObject createResponse() { + JSONObject jsonObj = new JSONObject(); + if (appConfiguration.isFeatureEnabled(FeatureFlagType.ACCESS_EVALUATION)) + jsonObj.put(ACCESS_EVALUATION_V1_ENDPOINT, DiscoveryService.getAccessEvaluationV1Endpoint(appConfiguration)); + + return jsonObj; + } +} diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/authzen/ws/rs/AccessEvaluationDiscoveryWS.java b/jans-auth-server/server/src/main/java/io/jans/as/server/authzen/ws/rs/AccessEvaluationDiscoveryWS.java new file mode 100644 index 00000000000..124169a4a40 --- /dev/null +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/authzen/ws/rs/AccessEvaluationDiscoveryWS.java @@ -0,0 +1,52 @@ +package io.jans.as.server.authzen.ws.rs; + +import io.jans.as.model.common.FeatureFlagType; +import io.jans.as.model.error.ErrorResponseFactory; +import io.jans.as.model.gluu.GluuErrorResponseType; +import io.jans.as.server.model.common.ExecutionContext; +import io.jans.as.server.util.ServerUtil; +import jakarta.inject.Inject; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.json.JSONObject; +import org.slf4j.Logger; + +/** + * @author Yuriy Z + */ +@Path("/authzen-configuration") +public class AccessEvaluationDiscoveryWS { + + @Inject + private Logger log; + + @Inject + private ErrorResponseFactory errorResponseFactory; + + @Inject + private AccessEvaluationDiscoveryService accessEvaluationDiscoveryService; + + @GET + @Produces({MediaType.APPLICATION_JSON}) + public Response getDiscovery(@Context HttpServletRequest httpRequest, @Context HttpServletResponse httpResponse) { + errorResponseFactory.validateFeatureEnabled(FeatureFlagType.ACCESS_EVALUATION); + + try { + final ExecutionContext context = new ExecutionContext(httpRequest, httpResponse); + final JSONObject response = accessEvaluationDiscoveryService.discovery(context); + final String entity = ServerUtil.toPrettyJson(response).replace("\\/", "/"); + log.trace("AuthZen Discovery: {}", entity); + + return Response.ok(entity).type(MediaType.APPLICATION_JSON_TYPE).build(); + } catch (Exception ex) { + log.error(ex.getMessage(), ex); + throw errorResponseFactory.createWebApplicationException(Response.Status.INTERNAL_SERVER_ERROR, GluuErrorResponseType.SERVER_ERROR, "Internal error."); + } + } +} diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/DiscoveryService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/DiscoveryService.java index 7a27672907e..aac9400564b 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/service/DiscoveryService.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/DiscoveryService.java @@ -77,7 +77,7 @@ public JSONObject process() { if (appConfiguration.isFeatureEnabled(FeatureFlagType.STATUS_LIST)) jsonObj.put(STATUS_LIST_ENDPOINT, getTokenStatusListEndpoint()); if (appConfiguration.isFeatureEnabled(FeatureFlagType.ACCESS_EVALUATION)) - jsonObj.put(ACCESS_EVALUATION_V1_ENDPOINT, getAccessEvaluationV1Endpoint()); + jsonObj.put(ACCESS_EVALUATION_V1_ENDPOINT, getAccessEvaluationV1Endpoint(appConfiguration)); if (appConfiguration.isFeatureEnabled(FeatureFlagType.REVOKE_TOKEN)) jsonObj.put(REVOCATION_ENDPOINT, appConfiguration.getTokenRevocationEndpoint()); if (appConfiguration.isFeatureEnabled(FeatureFlagType.REVOKE_SESSION)) @@ -243,8 +243,8 @@ public String getTokenStatusListEndpoint() { return endpointUrl("/status_list"); } - public String getAccessEvaluationV1Endpoint() { - return endpointUrl("/access/v1/evaluation"); + public static String getAccessEvaluationV1Endpoint(AppConfiguration appConfiguration) { + return endpointUrl(appConfiguration.getEndSessionEndpoint(), "/access/v1/evaluation"); } diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/LocalResponseCache.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/LocalResponseCache.java index 83d7698804a..6fc7aaac06a 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/service/LocalResponseCache.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/LocalResponseCache.java @@ -24,9 +24,11 @@ public class LocalResponseCache { public static final int DEFAULT_DISCOVERY_LIFETIME = 60; + public static final int DEFAULT_ACCESS_EVALUTION_DISCOVERY_LIFETIME = 5; public static final int DEFAULT_SECTOR_IDENTIFIER_LIFETIME = 1440; // 1 day private static final String DISCOVERY_CACHE_KEY = "DISCOVERY_CACHE_KEY"; + private static final String ACCESS_EVALUATION_DISCOVERY_CACHE_KEY = "ACCESS_EVALUATION_DISCOVERY_CACHE_KEY"; @Inject private AppConfiguration appConfiguration; @@ -36,14 +38,21 @@ public class LocalResponseCache { .expireAfterWrite(DEFAULT_DISCOVERY_LIFETIME, TimeUnit.MINUTES).build(); private Cache> sectorIdentifierCache = CacheBuilder.newBuilder() .expireAfterWrite(DEFAULT_SECTOR_IDENTIFIER_LIFETIME, TimeUnit.MINUTES).build(); + private Cache accessEvaluationDiscoveryCache = CacheBuilder.newBuilder() + .expireAfterWrite(DEFAULT_ACCESS_EVALUTION_DISCOVERY_LIFETIME, TimeUnit.MINUTES).build(); private int currentDiscoveryLifetime = DEFAULT_DISCOVERY_LIFETIME; + private int currentAccessEvaluationDiscoveryLifetime = DEFAULT_ACCESS_EVALUTION_DISCOVERY_LIFETIME; private int currentSectorIdentifierLifetime = DEFAULT_SECTOR_IDENTIFIER_LIFETIME; public void invalidateDiscoveryCache() { discoveryCache.invalidate(DISCOVERY_CACHE_KEY); } + public void invalidateAccessEvaluationDiscoveryCache() { + accessEvaluationDiscoveryCache.invalidate(ACCESS_EVALUATION_DISCOVERY_CACHE_KEY); + } + @Asynchronous public void reloadConfigurationTimerEvent(@Observes @Scheduled AuthConfigurationEvent authConfigurationEvent) { try { @@ -57,6 +66,12 @@ public void reloadConfigurationTimerEvent(@Observes @Scheduled AuthConfiguration discoveryCache = CacheBuilder.newBuilder() .expireAfterWrite(appConfiguration.getDiscoveryCacheLifetimeInMinutes(), TimeUnit.MINUTES).build(); } + if (currentAccessEvaluationDiscoveryLifetime != appConfiguration.getAccessEvaluationDiscoveryCacheLifetimeInMinutes()) { + currentAccessEvaluationDiscoveryLifetime = appConfiguration.getAccessEvaluationDiscoveryCacheLifetimeInMinutes(); + accessEvaluationDiscoveryCache = CacheBuilder.newBuilder() + .expireAfterWrite(appConfiguration.getAccessEvaluationDiscoveryCacheLifetimeInMinutes(), TimeUnit.MINUTES).build(); + + } if (currentSectorIdentifierLifetime != appConfiguration.getSectorIdentifierCacheLifetimeInMinutes()) { currentSectorIdentifierLifetime = appConfiguration.getSectorIdentifierCacheLifetimeInMinutes(); sectorIdentifierCache = CacheBuilder.newBuilder() @@ -92,4 +107,17 @@ public void putDiscoveryResponse(JSONObject response) { discoveryCache.put(DISCOVERY_CACHE_KEY, response); } + + public JSONObject getAccessEvaluationDiscoveryResponse() { + if (accessEvaluationDiscoveryCache == null || rebuilding.get()) + return null; + return accessEvaluationDiscoveryCache.getIfPresent(ACCESS_EVALUATION_DISCOVERY_CACHE_KEY); + } + + public void putAccessEvaluationDiscoveryResponse(JSONObject response) { + if (accessEvaluationDiscoveryCache == null || rebuilding.get()) + return; + + accessEvaluationDiscoveryCache.put(ACCESS_EVALUATION_DISCOVERY_CACHE_KEY, response); + } } \ No newline at end of file diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/ResteasyInitializer.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/ResteasyInitializer.java index c34891e81ee..edb50ff51a0 100644 --- a/jans-auth-server/server/src/main/java/io/jans/as/server/service/ResteasyInitializer.java +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/ResteasyInitializer.java @@ -9,6 +9,7 @@ import io.jans.as.server.authorize.ws.rs.AuthorizationChallengeEndpoint; import io.jans.as.server.authorize.ws.rs.AuthorizeRestWebServiceImpl; import io.jans.as.server.authorize.ws.rs.DeviceAuthorizationRestWebServiceImpl; +import io.jans.as.server.authzen.ws.rs.AccessEvaluationDiscoveryWS; import io.jans.as.server.authzen.ws.rs.AccessEvaluationRestWebServiceImplV1; import io.jans.as.server.bcauthorize.ws.rs.BackchannelAuthorizeRestWebServiceImpl; import io.jans.as.server.bcauthorize.ws.rs.BackchannelDeviceRegistrationRestWebServiceImpl; @@ -54,6 +55,7 @@ public Set> getClasses() { classes.add(AuthorizeRestWebServiceImpl.class); classes.add(AuthorizationChallengeEndpoint.class); classes.add(AccessEvaluationRestWebServiceImplV1.class); + classes.add(AccessEvaluationDiscoveryWS.class); classes.add(RegisterRestWebServiceImpl.class); classes.add(ClientInfoRestWebServiceImpl.class); classes.add(RevokeRestWebServiceImpl.class); diff --git a/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ExternalAccessEvaluationDiscoveryService.java b/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ExternalAccessEvaluationDiscoveryService.java new file mode 100644 index 00000000000..1bab20eab22 --- /dev/null +++ b/jans-auth-server/server/src/main/java/io/jans/as/server/service/external/ExternalAccessEvaluationDiscoveryService.java @@ -0,0 +1,44 @@ +package io.jans.as.server.service.external; + +import io.jans.as.server.model.common.ExecutionContext; +import io.jans.model.custom.script.CustomScriptType; +import io.jans.model.custom.script.conf.CustomScriptConfiguration; +import io.jans.model.custom.script.type.authzen.AccessEvaluationDiscoveryType; +import io.jans.service.custom.script.ExternalScriptService; +import jakarta.enterprise.context.ApplicationScoped; +import org.json.JSONObject; + +/** + * @author Yuriy Zabrovarnyy + */ +@ApplicationScoped +public class ExternalAccessEvaluationDiscoveryService extends ExternalScriptService { + + public ExternalAccessEvaluationDiscoveryService() { + super(CustomScriptType.ACCESS_EVALUATION_DISCOVERY); + } + + public boolean modifyDiscovery(JSONObject jsonObject, ExecutionContext context) { + final CustomScriptConfiguration script = getDefaultExternalCustomScript(); + if (script == null) { + log.debug("No access evaluation discovery script set."); + return false; + } + + try { + log.debug("Executing python 'modifyDiscovery' method, script name: {}, jsonObj: {}, context: {}", script.getName(), jsonObject, context); + context.setScript(script); + + AccessEvaluationDiscoveryType discoveryType = (AccessEvaluationDiscoveryType) script.getExternalType(); + final boolean result = discoveryType.modifyResponse(jsonObject, context); + log.debug("Finished 'modifyDiscovery' method, script name: {}, jsonObj: {}, context: {}, result: {}", script.getName(), jsonObject, context, result); + + return result; + } catch (Exception e) { + log.error(e.getMessage(), e); + saveScriptError(script.getCustomScript(), e); + } + + return false; + } +} diff --git a/jans-auth-server/server/src/test/java/io/jans/as/server/service/LocalResponseCacheTest.java b/jans-auth-server/server/src/test/java/io/jans/as/server/service/LocalResponseCacheTest.java index 9531360840f..2cc840775e1 100644 --- a/jans-auth-server/server/src/test/java/io/jans/as/server/service/LocalResponseCacheTest.java +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/service/LocalResponseCacheTest.java @@ -37,4 +37,10 @@ public void putDiscoveryResponse_whenCalled_shouldContainCache() { localResponseCache.putDiscoveryResponse(new JSONObject()); assertNotNull(localResponseCache.getDiscoveryResponse()); } + + @Test + public void putAccessEvaluationDiscoveryResponse_whenCalled_shouldContainCache() { + localResponseCache.putAccessEvaluationDiscoveryResponse(new JSONObject()); + assertNotNull(localResponseCache.getAccessEvaluationDiscoveryResponse()); + } } diff --git a/jans-auth-server/server/src/test/java/io/jans/as/server/service/TestResteasyInitializer.java b/jans-auth-server/server/src/test/java/io/jans/as/server/service/TestResteasyInitializer.java index 7e17640f7cf..e1146d8728f 100644 --- a/jans-auth-server/server/src/test/java/io/jans/as/server/service/TestResteasyInitializer.java +++ b/jans-auth-server/server/src/test/java/io/jans/as/server/service/TestResteasyInitializer.java @@ -8,6 +8,8 @@ import io.jans.as.server.authorize.ws.rs.AuthorizationChallengeEndpoint; import io.jans.as.server.authorize.ws.rs.AuthorizeRestWebServiceImpl; +import io.jans.as.server.authzen.ws.rs.AccessEvaluationDiscoveryWS; +import io.jans.as.server.authzen.ws.rs.AccessEvaluationRestWebServiceImplV1; import io.jans.as.server.clientinfo.ws.rs.ClientInfoRestWebServiceImpl; import io.jans.as.server.introspection.ws.rs.IntrospectionWebService; import io.jans.as.server.jans.ws.rs.JansConfigurationWS; @@ -59,6 +61,9 @@ public Set> getClasses() { classes.add(UmaMetadataWS.class); classes.add(UmaGatheringWS.class); + classes.add(AccessEvaluationRestWebServiceImplV1.class); + classes.add(AccessEvaluationDiscoveryWS.class); + classes.add(JansConfigurationWS.class); return classes; } diff --git a/jans-core/script/src/main/java/io/jans/model/custom/script/CustomScriptType.java b/jans-core/script/src/main/java/io/jans/model/custom/script/CustomScriptType.java index f15bcf8f8c5..31208509325 100644 --- a/jans-core/script/src/main/java/io/jans/model/custom/script/CustomScriptType.java +++ b/jans-core/script/src/main/java/io/jans/model/custom/script/CustomScriptType.java @@ -17,7 +17,9 @@ import io.jans.model.custom.script.type.authzchallenge.DummyAuthorizationChallengeType; import io.jans.model.custom.script.type.authzdetails.AuthzDetailType; import io.jans.model.custom.script.type.authzdetails.DummyAuthzDetail; +import io.jans.model.custom.script.type.authzen.AccessEvaluationDiscoveryType; import io.jans.model.custom.script.type.authzen.AccessEvaluationType; +import io.jans.model.custom.script.type.authzen.DummyAccessEvaluationDiscoveryType; import io.jans.model.custom.script.type.authzen.DummyAccessEvaluationType; import io.jans.model.custom.script.type.ciba.DummyEndUserNotificationType; import io.jans.model.custom.script.type.ciba.EndUserNotificationType; @@ -102,6 +104,8 @@ public enum CustomScriptType implements AttributeEnum { new UmaDummyClaimsGatheringType()), ACCESS_EVALUATION("access_evaluation", "Access Evaluation", AccessEvaluationType.class, CustomScript.class, "AccessEvaluation", new DummyAccessEvaluationType()), + ACCESS_EVALUATION_DISCOVERY("access_evaluation_discovery", "Access Evaluation Discovery", AccessEvaluationDiscoveryType.class, CustomScript.class, "AccessEvaluationDiscovery", + new DummyAccessEvaluationDiscoveryType()), CONSENT_GATHERING("consent_gathering", "Consent Gathering", ConsentGatheringType.class, CustomScript.class, "ConsentGathering", new DummyConsentGatheringType()), DYNAMIC_SCOPE("dynamic_scope", "Dynamic Scopes", DynamicScopeType.class, CustomScript.class, "DynamicScope", diff --git a/jans-core/script/src/main/java/io/jans/model/custom/script/type/authzen/AccessEvaluationDiscoveryType.java b/jans-core/script/src/main/java/io/jans/model/custom/script/type/authzen/AccessEvaluationDiscoveryType.java new file mode 100644 index 00000000000..a35e629e1d9 --- /dev/null +++ b/jans-core/script/src/main/java/io/jans/model/custom/script/type/authzen/AccessEvaluationDiscoveryType.java @@ -0,0 +1,11 @@ +package io.jans.model.custom.script.type.authzen; + +import io.jans.model.custom.script.type.BaseExternalType; + +/** + * @author Yuriy Z + */ +public interface AccessEvaluationDiscoveryType extends BaseExternalType { + + boolean modifyResponse(Object responseAsJsonObject, Object context); +} diff --git a/jans-core/script/src/main/java/io/jans/model/custom/script/type/authzen/DummyAccessEvaluationDiscoveryType.java b/jans-core/script/src/main/java/io/jans/model/custom/script/type/authzen/DummyAccessEvaluationDiscoveryType.java new file mode 100644 index 00000000000..e827bc63b27 --- /dev/null +++ b/jans-core/script/src/main/java/io/jans/model/custom/script/type/authzen/DummyAccessEvaluationDiscoveryType.java @@ -0,0 +1,37 @@ +package io.jans.model.custom.script.type.authzen; + +import io.jans.model.SimpleCustomProperty; +import io.jans.model.custom.script.model.CustomScript; + +import java.util.Map; + +/** + * @author Yuriy Z + */ +public class DummyAccessEvaluationDiscoveryType implements AccessEvaluationDiscoveryType { + + @Override + public boolean init(Map configurationAttributes) { + return true; + } + + @Override + public boolean init(CustomScript customScript, Map configurationAttributes) { + return true; + } + + @Override + public boolean destroy(Map configurationAttributes) { + return true; + } + + @Override + public int getApiVersion() { + return 1; + } + + @Override + public boolean modifyResponse(Object responseAsJsonObject, Object context) { + return false; + } +} diff --git a/jans-linux-setup/jans_setup/templates/apache/https_jans.conf b/jans-linux-setup/jans_setup/templates/apache/https_jans.conf index fedda79ddfb..60aa0138fba 100644 --- a/jans-linux-setup/jans_setup/templates/apache/https_jans.conf +++ b/jans-linux-setup/jans_setup/templates/apache/https_jans.conf @@ -114,6 +114,7 @@ ProxyPass /.well-known/openid-configuration http://localhost:%(jans_auth_port)s/jans-auth/.well-known/openid-configuration ProxyPass /.well-known/webfinger http://localhost:%(jans_auth_port)s/jans-auth/.well-known/webfinger ProxyPass /.well-known/uma2-configuration http://localhost:%(jans_auth_port)s/jans-auth/restv1/uma2-configuration + ProxyPass /.well-known/authzen-configuration http://localhost:%(jans_auth_port)s/jans-auth/restv1/authzen-configuration ProxyPass /.well-known/fido2-configuration http://localhost:8073/jans-fido2/restv1/configuration ProxyPass /.well-known/scim-configuration http://localhost:8087/jans-scim/restv1/scim-configuration ProxyPass /firebase-messaging-sw.js http://localhost:%(jans_auth_port)s/jans-auth/firebase-messaging-sw.js