Skip to content

Access Evaluation Custom Script#

The Jans-Auth server implements OpenID AuthZEN Authorization API 1.0. The AuthZEN Authorization API 1.0 specification defines a standardized interface for communication between Policy Enforcement Points (PEPs) and Policy Decision Points (PDPs) to facilitate consistent authorization decisions across diverse systems. It introduces an Access Evaluation API that allows PEPs to query PDPs about specific access requests, enhancing interoperability and scalability in authorization processes. The specification is transport-agnostic, with an initial focus on HTTPS bindings, and emphasizes secure, fine-grained, and dynamic authorization mechanisms.

This script is used to control Access Evaluation Endpoints described in specification.

Behavior#

The Access Evaluation Endpoint in the AuthZEN specification serves as a mechanism for Policy Enforcement Points (PEPs) to request access decisions from a Policy Decision Point (PDP) for specific resources and actions. Upon receiving a request, the endpoint evaluates the subject, resource, and action against defined policies to determine if access should be granted, denied, or if additional information is needed. The endpoint's responses are typically concise, aiming to provide a rapid decision that PEPs can enforce in real-time. The goal is to provide a scalable, secure interface for dynamic and fine-grained access control across applications.

During Access Evaluation request processing the Access Evaluation custom script is executed. Name of the script must be specified by accessEvaluationScriptName configuration property. If AS can't find such script or if configuration property is not specified then server executes first script it finds on database. AS comes with sample demo script which shows simple example of custom validation and granting access.

Sample Single Evaluation Request

POST /jans-auth/restv1/access/v1/evaluation HTTP/1.1
Host: happy-example.gluu.info
Content-Type: application/json
Authorization: Basic <encoded_credentials>

{
  "subject": {
    "type": "super_admin",
    "id": "alice@acmecorp.com"
  },
  "resource": {
    "type": "account",
    "id": "123"
  },
  "action": {
    "name": "can_read"
  },
  "context": {
    "ip_address": "192.168.1.1",
    "time": "2024-01-15T10:30:00Z"
  }
}

Sample Batch Evaluations Request

POST /jans-auth/restv1/access/v1/evaluations HTTP/1.1
Host: happy-example.gluu.info
Content-Type: application/json
Authorization: Basic <encoded_credentials>

{
  "subject": {
    "type": "user",
    "id": "alice@acmecorp.com"
  },
  "evaluations": [
    {"action": {"name": "read"}},
    {"action": {"name": "write"}},
    {"action": {"name": "delete"}}
  ],
  "options": {
    "evaluations_semantic": "deny_on_first_deny"
  }
}

Interface#

The Access Evaluation script implements the AccessEvaluationType 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
evaluate(request, context) Called for single evaluation requests. Must return AccessEvaluationResponse.
searchSubject(request, context) Called for subject search requests. Returns SearchResponse<Subject> or null for empty response.
searchResource(request, context) Called for resource search requests. Returns SearchResponse<Resource> or null for empty response.
searchAction(request, context) Called for action search requests. Returns SearchResponse<Action> or null for empty response.

Method Details#

evaluate(request, context)#

The main evaluation method called when a single evaluation or batch evaluation request is received.

Parameters: - request - AccessEvaluationRequest containing subject, resource, action, and context - context - ExternalScriptContext providing access to HTTP request and other contextual data

Returns: AccessEvaluationResponse indicating the access decision (true/false) with optional context

searchSubject(request, context)#

Called when a subject search request is received at /access/v1/search/subject.

Parameters: - request - SearchSubjectRequest containing search criteria - context - Script context

Returns: SearchResponse<Subject> with matching subjects, or null for default empty response

searchResource(request, context)#

Called when a resource search request is received at /access/v1/search/resource.

Parameters: - request - SearchResourceRequest containing search criteria - context - Script context

Returns: SearchResponse<Resource> with matching resources, or null for default empty response

searchAction(request, context)#

Called when an action search request is received at /access/v1/search/action.

Parameters: - request - SearchActionRequest containing search criteria - context - Script context

Returns: SearchResponse<Action> with matching actions, or null for default empty response

Objects#

Object name Object description
customScript The custom script object. Reference
context Reference
AccessEvaluationRequest Request containing subject, resource, action, context, evaluations (for batch), and options
AccessEvaluationResponse Response with decision (boolean) and optional context
SearchResponse Paginated response for search operations

Sample Demo Custom Script#

Script Type: Java#

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.jans.as.server.service.external.context.ExternalScriptContext;
import io.jans.model.SimpleCustomProperty;
import io.jans.model.authzen.*;
import io.jans.model.custom.script.model.CustomScript;
import io.jans.model.custom.script.type.authzen.AccessEvaluationType;
import io.jans.service.custom.script.CustomScriptManager;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.UUID;

/**
 * Sample AccessEvaluation script demonstrating:
 * - Single evaluation with custom validation
 * - Search method stubs (return null for default empty response)
 *
 * @author Yuriy Z
 */
public class AccessEvaluation implements AccessEvaluationType {

    private static final Logger scriptLogger = LoggerFactory.getLogger(CustomScriptManager.class);
    private static final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public AccessEvaluationResponse evaluate(AccessEvaluationRequest request, Object scriptContext) {

        ExternalScriptContext context = (ExternalScriptContext) scriptContext;
        // 1. access http request via context.getHttpRequest()
        // 2. access all access evaluation specific data directly with 'request', e.g. request.getSubject()

        // 3. perform custom validation if needed
        validateResource(request.getResource());

        // typically some internal validation must be performed here
        // request data alone must not be trusted, it's just sample to demo script with endpoint
        if ("super_admin".equalsIgnoreCase(request.getSubject().getType())) {
            final ObjectNode reasonAdmin = objectMapper.createObjectNode();
            reasonAdmin.put("reason", "super_admin");

            final AccessEvaluationResponseContext responseContext = new AccessEvaluationResponseContext();
            responseContext.setId(UUID.randomUUID().toString());
            responseContext.setReasonAdmin(reasonAdmin);

            return new AccessEvaluationResponse(true, responseContext);
        }
        return AccessEvaluationResponse.FALSE;
    }

    private void validateResource(Resource resource) {
        // sample for custom validation
        if (resource.getType().equalsIgnoreCase("file")) {
            throw new WebApplicationException(Response
                    .status(Response.Status.BAD_REQUEST)
                    .entity("{\n" +
                            "  \"error\": \"invalid_resource_type\",\n" +
                            "  \"error_description\": \"Resource type 'file' is not allowed.\"\n" +
                            "}")
                    .build());
        }
    }

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

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

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

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

    @Override
    public SearchResponse<Subject> searchSubject(SearchSubjectRequest request, Object context) {
        // Implement subject search logic here
        // Return null to use default empty response
        return null;
    }

    @Override
    public SearchResponse<Resource> searchResource(SearchResourceRequest request, Object context) {
        // Implement resource search logic here
        // Return null to use default empty response
        return null;
    }

    @Override
    public SearchResponse<Action> searchAction(SearchActionRequest request, Object context) {
        // Implement action search logic here
        // Return null to use default empty response
        return null;
    }
}

Search Implementation Example#

Here's an example of implementing a search method that returns actual results:

@Override
public SearchResponse<Subject> searchSubject(SearchSubjectRequest request, Object context) {
    // Example: Return users who have access to the requested resource/action
    List<Subject> subjects = new ArrayList<>();

    // Query your authorization backend/database here
    // This is just a sample - implement your actual logic
    subjects.add(new Subject().setType("user").setId("alice@acmecorp.com"));
    subjects.add(new Subject().setType("user").setId("bob@acmecorp.com"));

    SearchResponse<Subject> response = new SearchResponse<>();
    response.setResults(subjects);

    // Set pagination info
    PageResponse page = new PageResponse();
    page.setCount(subjects.size());
    page.setTotal(subjects.size());
    response.setPage(page);

    return response;
}

Context Object#

The context field in requests is fully dynamic and accepts any key-value pairs per AuthZEN specification:

{
  "context": {
    "ip_address": "192.168.1.100",
    "time": "2024-01-15T10:30:00Z",
    "device_type": "mobile",
    "region": "us-east-1",
    "custom_field": "any_value"
  }
}

Access context values in your script:

Context ctx = request.getContext();
String ipAddress = (String) ctx.get("ip_address");
String region = (String) ctx.get("region");

Batch Evaluation Options#

When handling batch evaluations, the options.evaluations_semantic field controls processing:

Value Behavior
execute_all Process all evaluations regardless of results (default)
deny_on_first_deny Stop on first deny result
permit_on_first_permit Stop on first permit result

The AS handles semantic logic automatically - your evaluate() method is called for each individual evaluation.

Sample Scripts#

Access Evaluation Script#

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.jans.as.server.service.external.context.ExternalScriptContext;
import io.jans.model.SimpleCustomProperty;
import io.jans.model.authzen.*;
import io.jans.model.custom.script.model.CustomScript;
import io.jans.model.custom.script.type.authzen.AccessEvaluationType;
import io.jans.service.custom.script.CustomScriptManager;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.UUID;

/**
 * @author Yuriy Z
 */
public class AccessEvaluation implements AccessEvaluationType {

    private static final Logger scriptLogger = LoggerFactory.getLogger(CustomScriptManager.class);
    private static final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public AccessEvaluationResponse evaluate(AccessEvaluationRequest request, Object scriptContext) {

        ExternalScriptContext context = (ExternalScriptContext) scriptContext;
        // 1. access http request via context.getHttpRequest()
        // 2. access all access evaluation specific data directly with 'request', e.g. request.getSubject()

        // 3. perform custom validation if needed
        validateResource(request.getResource());

        // typically some internal validation must be performed here
        // request data alone must not be trusted, it's just sample to demo script with endpoint
        if ("super_admin".equalsIgnoreCase(request.getSubject().getType())) {
            final ObjectNode reasonAdmin = objectMapper.createObjectNode();
            reasonAdmin.put("reason", "super_admin");

            final AccessEvaluationResponseContext responseContext = new AccessEvaluationResponseContext();
            responseContext.setId(UUID.randomUUID().toString());
            responseContext.setReasonAdmin(reasonAdmin);

            return new AccessEvaluationResponse(true, responseContext);
        }
        return AccessEvaluationResponse.FALSE;
    }

    private void validateResource(Resource resource) {
        // sample for custom validation
        if (resource.getType().equalsIgnoreCase("file")) {
            throw new WebApplicationException(Response
                    .status(Response.Status.BAD_REQUEST)
                    .entity("{\n" +
                            "  \"error\": \"invalid_resource_type\",\n" +
                            "  \"error_description\": \"Resource type 'file' is not allowed.\"\n" +
                            "}")
                    .build());
        }
    }

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

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

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

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

    @Override
    public SearchResponse<Subject> searchSubject(SearchSubjectRequest request, Object context) {
        // Not implemented - return null to use default empty response
        return null;
    }

    @Override
    public SearchResponse<Resource> searchResource(SearchResourceRequest request, Object context) {
        // Not implemented - return null to use default empty response
        return null;
    }

    @Override
    public SearchResponse<Action> searchAction(SearchActionRequest request, Object context) {
        // Not implemented - return null to use default empty response
        return null;
    }
}