Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(jans-auth-server): new HealthCheck custom sсript #8144

Merged
merged 8 commits into from
Mar 28, 2024
1 change: 1 addition & 0 deletions docs/admin/developer/interception-scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ calling external APIs
1. [Authz Detail](./scripts/authz-detail.md)
1. [Create User](./scripts/create-user.md)
1. [Select Account](./scripts/select-account.md)
1. [Health Check](./scripts/health-check.md)
1. Resource Owner Password Credentials
1. UMA 2 RPT Authorization Policies
1. UMA 2 Claims-Gathering
Expand Down
2 changes: 1 addition & 1 deletion docs/admin/developer/scripts/create-user.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ tags:
# Create User Custom Script

## Overview
"CreateUser" custom script is use when Authorization Endpoint is called with `prompt=create` parameter.
"CreateUser" custom script is used when Authorization Endpoint is called with `prompt=create` parameter.
In this case AS shows user registration form instead of authn/authz UI.
"CreateUser" custom script allows inject/modify user registration process.

Expand Down
98 changes: 98 additions & 0 deletions docs/admin/developer/scripts/health-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
tags:
- administration
- developer
- scripts
---

# Create User Custom Script

## Overview
"HealthCheck" custom script is used when Health Check Endpoint is called.
Custom script allows inject/modify response from Health Check Endpoint.

## Interface
The HealthCheck script implements the [HealthCheckType](https://github.com/JanssenProject/jans/blob/main/jans-core/script/src/main/java/io/jans/model/custom/script/type/health_check/HealthCheckType.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 healthCheck(self, context)`| Returns response for health check endpoint


### 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) |


### Saample Scrip in Java

```java
package io.jans.as.server._scripts;

import io.jans.as.server.service.external.context.ExternalScriptContext;
import io.jans.model.SimpleCustomProperty;
import io.jans.model.custom.script.model.CustomScript;
import io.jans.model.custom.script.type.health.HealthCheckType;
import io.jans.service.custom.script.CustomScriptManager;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;

/**
* @author Yuriy Z
*/
public class HealthCheck implements HealthCheckType {

private static final Logger log = LoggerFactory.getLogger(HealthCheck.class);
private static final Logger scriptLogger = LoggerFactory.getLogger(CustomScriptManager.class);

@Override
public String healthCheck(Object context) {
ExternalScriptContext scriptContext = (ExternalScriptContext) context;
HttpServletRequest httpRequest = scriptContext.getExecutionContext().getHttpRequest();

String appStatus = "running";
String dbStatus = "online";
return String.format("{\"status\": \"%s\", \"db_status\":\"%s\"}", appStatus, dbStatus);
}

@Override
public boolean init(Map<String, SimpleCustomProperty> configurationAttributes) {
scriptLogger.info("Initialized HealthCheck Java custom script.");
return true;
}

@Override
public boolean init(CustomScript customScript, Map<String, SimpleCustomProperty> configurationAttributes) {
scriptLogger.info("Initialized HealthCheck Java custom script.");
return true;
}

@Override
public boolean destroy(Map<String, SimpleCustomProperty> configurationAttributes) {
scriptLogger.info("Destroyed HealthCheck Java custom script.");
return true;
}

@Override
public int getApiVersion() {
return 11;
}
}

```

## Sample Scripts
- [Sample HealthCheck script](../../../script-catalog/health_check/HealthCheck.java)
54 changes: 54 additions & 0 deletions docs/script-catalog/health_check/HealthCheck.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.jans.as.server._scripts;

import io.jans.as.server.service.external.context.ExternalScriptContext;
import io.jans.model.SimpleCustomProperty;
import io.jans.model.custom.script.model.CustomScript;
import io.jans.model.custom.script.type.health.HealthCheckType;
import io.jans.service.custom.script.CustomScriptManager;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;

/**
* @author Yuriy Z
*/
public class HealthCheck implements HealthCheckType {

private static final Logger log = LoggerFactory.getLogger(HealthCheck.class);
private static final Logger scriptLogger = LoggerFactory.getLogger(CustomScriptManager.class);

@Override
public String healthCheck(Object context) {
ExternalScriptContext scriptContext = (ExternalScriptContext) context;
HttpServletRequest httpRequest = scriptContext.getExecutionContext().getHttpRequest();

String appStatus = "running";
String dbStatus = "online";
return String.format("{\"status\": \"%s\", \"db_status\":\"%s\"}", appStatus, dbStatus);
}

@Override
public boolean init(Map<String, SimpleCustomProperty> configurationAttributes) {
scriptLogger.info("Initialized HealthCheck Java custom script.");
return true;
}

@Override
public boolean init(CustomScript customScript, Map<String, SimpleCustomProperty> configurationAttributes) {
scriptLogger.info("Initialized HealthCheck Java custom script.");
return true;
}

@Override
public boolean destroy(Map<String, SimpleCustomProperty> configurationAttributes) {
scriptLogger.info("Destroyed HealthCheck Java custom script.");
return true;
}

@Override
public int getApiVersion() {
return 11;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,15 @@ public boolean externalCreate(ExecutionContext context) {

public boolean externalCreate(CustomScriptConfiguration scriptConfiguration, ExecutionContext context) {
try {
log.trace("Executing external 'externalCreateUser' method, script name: {}, context: {}", scriptConfiguration.getName(), context);
log.trace("Executing external 'createUser' method, script name: {}, context: {}", scriptConfiguration.getName(), context);

CreateUserType script = (CreateUserType) scriptConfiguration.getExternalType();
context.setScript(scriptConfiguration);

final ExternalScriptContext scriptContext = new ExternalScriptContext(context);
final boolean result = script.createUser(scriptContext);

log.trace("Finished external 'externalCreateUser' method, script name: {}, context: {}, result: {}", scriptConfiguration.getName(), context, result);
log.trace("Finished external 'createUser' method, script name: {}, context: {}, result: {}", scriptConfiguration.getName(), context, result);
return result;
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package io.jans.as.server.service.external;

import com.google.common.collect.Sets;
import io.jans.as.server.model.common.ExecutionContext;
import io.jans.as.server.service.external.context.ExternalScriptContext;
import io.jans.model.custom.script.CustomScriptType;
import io.jans.model.custom.script.conf.CustomScriptConfiguration;
import io.jans.model.custom.script.type.health.HealthCheckType;
import io.jans.service.custom.script.ExternalScriptService;
import jakarta.ejb.DependsOn;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Named;
import org.apache.commons.lang3.StringUtils;

import java.util.Set;

/**
* @author Yuriy Z
*/
@ApplicationScoped
@DependsOn("appInitializer")
@Named
public class ExternalHealthCheckService extends ExternalScriptService {

public ExternalHealthCheckService() {
super(CustomScriptType.HEALTH_CHECK);
}

public String externalHealthCheck(ExecutionContext context) {
final Set<CustomScriptConfiguration> scripts = getScriptsToExecute();
if (scripts.isEmpty()) {
return null;
}

log.trace("Found {} health-check scripts.", scripts.size());

for (CustomScriptConfiguration script : scripts) {
final String result = externalHealthCheck(script, context);
if (StringUtils.isNotBlank(result)) {
log.debug("'healthCheck' returned result {}, script: {}", result, script.getName());
return result;
}
}

return null;
}

private String externalHealthCheck(CustomScriptConfiguration scriptConfiguration, ExecutionContext context) {
try {
log.trace("Executing external 'healthCheck' method, script name: {}, context: {}", scriptConfiguration.getName(), context);

HealthCheckType script = (HealthCheckType) scriptConfiguration.getExternalType();
context.setScript(scriptConfiguration);

final ExternalScriptContext scriptContext = new ExternalScriptContext(context);
final String result = script.healthCheck(scriptContext);

log.trace("Finished external 'healthCheck' method, script name: {}, context: {}, result: {}", scriptConfiguration.getName(), context, result);
return result;
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
saveScriptError(scriptConfiguration.getCustomScript(), ex);
return null;
}
}

private Set<CustomScriptConfiguration> getScriptsToExecute() {
Set<CustomScriptConfiguration> result = Sets.newHashSet();
if (this.customScriptConfigurations == null) {
return result;
}

result.addAll(customScriptConfigurations);
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,22 @@

import io.jans.as.model.common.FeatureFlagType;
import io.jans.as.model.error.ErrorResponseFactory;
import io.jans.as.server.model.common.ExecutionContext;
import io.jans.as.server.service.external.ExternalAuthenticationService;
import io.jans.as.server.service.external.ExternalDynamicScopeService;
import io.jans.as.server.service.external.ExternalHealthCheckService;
import io.jans.orm.PersistenceEntryManager;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import org.apache.commons.lang3.StringUtils;

/**
* Health check controller
Expand All @@ -42,16 +47,30 @@ public class HealthCheckController {
@Inject
private ErrorResponseFactory errorResponseFactory;

@Inject
private ExternalHealthCheckService externalHealthCheckService;

@GET
@POST
@Path("/health-check")
@Produces(MediaType.APPLICATION_JSON)
public String healthCheckController() {
public String healthCheckController(@Context HttpServletRequest httpRequest, @Context HttpServletResponse httpResponse) {
errorResponseFactory.validateFeatureEnabled(FeatureFlagType.HEALTH_CHECK);
boolean isConnected = persistenceEntryManager.getOperationService().isConnected();
String dbStatus = isConnected ? "online" : "offline";
String appStatus = getAppStatus();
return "{\"status\": \"" + appStatus + "\", \"db_status\":\"" + dbStatus + "\"}";
final String responseString = String.format("{\"status\": \"%s\", \"db_status\":\"%s\"}", appStatus, dbStatus);

if (externalHealthCheckService.isEnabled()) {
final ExecutionContext executionContext = new ExecutionContext(httpRequest, httpResponse);
final String externalResponse = externalHealthCheckService.externalHealthCheck(executionContext);
if (StringUtils.isNotBlank(externalResponse)) {
return externalResponse;
}

}

return responseString;
}

public String getAppStatus() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import io.jans.model.custom.script.type.discovery.DummyDiscoveryType;
import io.jans.model.custom.script.type.fido2.DummyFido2ExtensionType;
import io.jans.model.custom.script.type.fido2.Fido2ExtensionType;
import io.jans.model.custom.script.type.health.DummyHealthCheck;
import io.jans.model.custom.script.type.health.HealthCheckType;
import io.jans.model.custom.script.type.id.DummyIdGeneratorType;
import io.jans.model.custom.script.type.id.IdGeneratorType;
import io.jans.model.custom.script.type.idp.DummyIdpType;
Expand Down Expand Up @@ -110,6 +112,7 @@ public enum CustomScriptType implements AttributeEnum {
PERSISTENCE_EXTENSION("persistence_extension", "Persistence Extension", PersistenceType.class, CustomScript.class, "PersistenceExtension", new DummyPeristenceType()),
IDP("idp", "Idp Extension", IdpType.class, CustomScript.class, "IdpExtension", new DummyIdpType()),
DISCOVERY("discovery", "Discovery", DiscoveryType.class, CustomScript.class, "Discovery", new DummyDiscoveryType()),
HEALTH_CHECK("health_check", "Health Check", HealthCheckType.class, CustomScript.class, "HealthCheck", new DummyHealthCheck()),
AUTHZ_DETAIL("authz_detail", "Authorization Detail", AuthzDetailType.class, CustomScript.class, "AuthzDetail", new DummyAuthzDetail()),
UPDATE_TOKEN("update_token", "Update Token", UpdateTokenType.class, CustomScript.class, "UpdateToken", new DummyUpdateTokenType()),
CONFIG_API("config_api_auth", "Config Api Auth", ConfigApiType.class, CustomScript.class,"ConfigApiAuthorization", new DummyConfigApiType()),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.jans.model.custom.script.type.health;

import io.jans.model.SimpleCustomProperty;
import io.jans.model.custom.script.model.CustomScript;

import java.util.Map;

/**
* @author Yuriy Z
*/
public class DummyHealthCheck implements HealthCheckType {

@Override
public String healthCheck(Object context) {
return null;
}

@Override
public boolean init(Map<String, SimpleCustomProperty> configurationAttributes) {
return true;
}

@Override
public boolean init(CustomScript customScript, Map<String, SimpleCustomProperty> configurationAttributes) {
return true;
}

@Override
public boolean destroy(Map<String, SimpleCustomProperty> configurationAttributes) {
return true;
}

@Override
public int getApiVersion() {
return 11;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.jans.model.custom.script.type.health;

import io.jans.model.custom.script.type.BaseExternalType;

/**
* @author Yuriy Z
*/
public interface HealthCheckType extends BaseExternalType {

/**
* @param context script context
* @return health check response
*/
String healthCheck(Object context);
}