Introduction to XACML
XACML (eXtensible Access Control Markup Language) is an OASIS standard for attribute-based access control (ABAC). It provides a fine-grained, flexible authorization framework that evaluates access requests based on attributes of the subject, resource, action, and environment.
Key XACML Concepts
Core Components
- Policy Decision Point (PDP) - Evaluates policies and renders decisions
- Policy Administration Point (PAP) - Manages policy storage and creation
- Policy Information Point (PIP) - Retrieves attribute values
- Policy Enforcement Point (PEP) - Intercepts requests and enforces decisions
Decision Types
- Permit - Access granted
- Deny - Access denied
- NotApplicable - No policy applies
- Indeterminate - Evaluation error occurred
Dependencies and Setup
Maven Configuration
<properties>
<balana.version>1.1.0</balana.version>
</properties>
<dependencies>
<!-- Balana - WSO2 XACML Implementation -->
<dependency>
<groupId>org.wso2.balana</groupId>
<artifactId>balana</artifactId>
<version>${balana.version}</version>
</dependency>
<!-- Alternative: AuthzForce -->
<dependency>
<groupId>org.ow2.authzforce</groupId>
<artifactId>authzforce-ce-core-pdp-engine</artifactId>
<version>16.0.0</version>
</dependency>
<!-- XML Processing -->
<dependency>
<groupId>org.apache.santuario</groupId>
<artifactId>xmlsec</artifactId>
<version>2.3.0</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
</dependencies>
Core XACML Engine Implementation
Basic PDP Engine
package com.xacml.engine;
import org.wso2.balana.*;
import org.wso2.balana.finder.*;
import org.wso2.balana.finder.impl.*;
import org.wso2.balana.PDP;
import org.wso2.balana.PDPConfig;
import org.wso2.balana.ctx.*;
import org.wso2.balana.ctx.xacml3.RequestCtx;
import org.wso2.balana.ctx.xacml3.ResponseCtx;
import org.wso2.balana.xacml3.*;
import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.util.*;
public class BasicPDPEngine {
private PDP pdp;
private PolicyFinder policyFinder;
private PolicyFinderModule policyFinderModule;
public BasicPDPEngine() {
initializePDP();
}
private void initializePDP() {
try {
// Create policy finder
policyFinder = new PolicyFinder();
policyFinderModule = new FileBasedPolicyFinderModule();
Set<PolicyFinderModule> finderModules = new HashSet<>();
finderModules.add(policyFinderModule);
policyFinder.setModules(finderModules);
// Create attribute finder
AttributeFinder attributeFinder = new AttributeFinder();
Set<AttributeFinderModule> attributeFinderModules = new HashSet<>();
attributeFinderModules.add(new CurrentEnvModule());
attributeFinder.setModules(attributeFinderModules);
// Create resource finder
ResourceFinder resourceFinder = new ResourceFinder();
Set<ResourceFinderModule> resourceFinderModules = new HashSet<>();
resourceFinder.setModules(resourceFinderModules);
// Create PDP configuration
PDPConfig pdpConfig = new PDPConfig(attributeFinder, policyFinder, resourceFinder);
// Create PDP
pdp = new PDP(pdpConfig);
} catch (Exception e) {
throw new RuntimeException("Failed to initialize PDP engine", e);
}
}
/**
* Evaluate XACML request
*/
public ResponseCtx evaluateRequest(String xacmlRequest) throws Exception {
RequestCtx request = RequestCtx.getInstance(new ByteArrayInputStream(xacmlRequest.getBytes()));
return pdp.evaluate(request);
}
/**
* Evaluate access request with simplified parameters
*/
public Decision evaluateAccess(String subjectId, String subjectRole,
String resourceId, String action,
Map<String, String> environment) throws Exception {
String xacmlRequest = createXACMLRequest(subjectId, subjectRole, resourceId, action, environment);
ResponseCtx response = evaluateRequest(xacmlRequest);
return extractDecision(response);
}
private String createXACMLRequest(String subjectId, String subjectRole,
String resourceId, String action,
Map<String, String> environment) {
StringBuilder request = new StringBuilder();
request.append("<Request xmlns=\"urn:oasis:names:tc:xacml:3.0:core:schema:wd-17\" ")
.append("CombinedDecision=\"false\" ReturnPolicyIdList=\"false\">\n")
.append(" <Attributes Category=\"urn:oasis:names:tc:xacml:1.0:subject-category:access-subject\">\n")
.append(" <Attribute AttributeId=\"urn:oasis:names:tc:xacml:1.0:subject:subject-id\" IncludeInResult=\"false\">\n")
.append(" <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">").append(subjectId).append("</AttributeValue>\n")
.append(" </Attribute>\n")
.append(" <Attribute AttributeId=\"urn:oasis:names:tc:xacml:2.0:subject:role\" IncludeInResult=\"false\">\n")
.append(" <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">").append(subjectRole).append("</AttributeValue>\n")
.append(" </Attribute>\n")
.append(" </Attributes>\n")
.append(" <Attributes Category=\"urn:oasis:names:tc:xacml:3.0:attribute-category:resource\">\n")
.append(" <Attribute AttributeId=\"urn:oasis:names:tc:xacml:1.0:resource:resource-id\" IncludeInResult=\"false\">\n")
.append(" <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">").append(resourceId).append("</AttributeValue>\n")
.append(" </Attribute>\n")
.append(" </Attributes>\n")
.append(" <Attributes Category=\"urn:oasis:names:tc:xacml:3.0:attribute-category:action\">\n")
.append(" <Attribute AttributeId=\"urn:oasis:names:tc:xacml:1.0:action:action-id\" IncludeInResult=\"false\">\n")
.append(" <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">").append(action).append("</AttributeValue>\n")
.append(" </Attribute>\n")
.append(" </Attributes>\n");
if (environment != null && !environment.isEmpty()) {
request.append(" <Attributes Category=\"urn:oasis:names:tc:xacml:3.0:attribute-category:environment\">\n");
for (Map.Entry<String, String> entry : environment.entrySet()) {
request.append(" <Attribute AttributeId=\"").append(entry.getKey()).append("\" IncludeInResult=\"false\">\n")
.append(" <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">").append(entry.getValue()).append("</AttributeValue>\n")
.append(" </Attribute>\n");
}
request.append(" </Attributes>\n");
}
request.append("</Request>");
return request.toString();
}
private Decision extractDecision(ResponseCtx response) {
if (response.getResults().length == 0) {
return Decision.INDETERMINATE;
}
Result result = response.getResults()[0];
return result.getDecision();
}
/**
* Load policy from string
*/
public void loadPolicy(String policyXml) throws Exception {
Policy policy = Policy.getInstance(new StringReader(policyXml));
((FileBasedPolicyFinderModule) policyFinderModule).loadPolicy(policy);
}
/**
* Load policy from file
*/
public void loadPolicyFromFile(String filePath) throws Exception {
((FileBasedPolicyFinderModule) policyFinderModule).loadPolicy(filePath);
}
}
Policy Management
Policy Administration Point (PAP)
package com.xacml.pap;
import org.wso2.balana.Policy;
import org.wso2.balana.PolicySet;
import org.wso2.balana.combine.PolicyCombiningAlgorithm;
import org.wso2.balana.combine.xacml3.DenyOverridesPolicyAlg;
import java.io.StringReader;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class PolicyAdministrationPoint {
private Map<String, Policy> policies = new ConcurrentHashMap<>();
private Map<String, PolicySet> policySets = new ConcurrentHashMap<>();
private PolicyCombiningAlgorithm combiningAlgorithm = new DenyOverridesPolicyAlg();
/**
* Add a policy to the repository
*/
public void addPolicy(String policyId, String policyXml) throws Exception {
Policy policy = Policy.getInstance(new StringReader(policyXml));
policies.put(policyId, policy);
}
/**
* Remove a policy from the repository
*/
public void removePolicy(String policyId) {
policies.remove(policyId);
}
/**
* Create a policy set from multiple policies
*/
public void createPolicySet(String policySetId, List<String> policyIds,
String combiningAlgorithmId) throws Exception {
List<Policy> policyList = new ArrayList<>();
for (String policyId : policyIds) {
Policy policy = policies.get(policyId);
if (policy != null) {
policyList.add(policy);
}
}
PolicySet policySet = new PolicySet(policySetId, combiningAlgorithm,
null, policyList, null, null, null);
policySets.put(policySetId, policySet);
}
/**
* Get policy by ID
*/
public Policy getPolicy(String policyId) {
return policies.get(policyId);
}
/**
* Get all policies
*/
public Collection<Policy> getAllPolicies() {
return policies.values();
}
/**
* Validate policy syntax
*/
public boolean validatePolicy(String policyXml) {
try {
Policy.getInstance(new StringReader(policyXml));
return true;
} catch (Exception e) {
return false;
}
}
/**
* Export policies as XML
*/
public String exportPolicies() {
StringBuilder sb = new StringBuilder();
sb.append("<PolicyRepository>\n");
for (Policy policy : policies.values()) {
sb.append(policy.encode()).append("\n");
}
sb.append("</PolicyRepository>");
return sb.toString();
}
}
Policy Information Point (PIP)
package com.xacml.pip;
import org.wso2.balana.attr.*;
import org.wso2.balana.ctx.EvaluationCtx;
import org.wso2.balana.finder.AttributeFinderModule;
import java.net.URI;
import java.util.*;
public class CustomAttributeFinder extends AttributeFinderModule {
private Map<String, Map<String, String>> userAttributes = new HashMap<>();
private Map<String, Map<String, String>> resourceAttributes = new HashMap<>();
public CustomAttributeFinder() {
initializeSampleData();
}
private void initializeSampleData() {
// Sample user attributes
Map<String, String> user1Attrs = new HashMap<>();
user1Attrs.put("department", "HR");
user1Attrs.put("clearance", "high");
user1Attrs.put("location", "US");
userAttributes.put("user1", user1Attrs);
Map<String, String> user2Attrs = new HashMap<>();
user2Attrs.put("department", "Engineering");
user2Attrs.put("clearance", "medium");
user2Attrs.put("location", "EU");
userAttributes.put("user2", user2Attrs);
// Sample resource attributes
Map<String, String> resource1Attrs = new HashMap<>();
resource1Attrs.put("sensitivity", "confidential");
resource1Attrs.put("owner", "HR");
resourceAttributes.put("document1", resource1Attrs);
}
@Override
public Set<String> getSupportedCategories() {
Set<String> categories = new HashSet<>();
categories.add("urn:oasis:names:tc:xacml:1.0:subject-category:access-subject");
categories.add("urn:oasis:names:tc:xacml:3.0:attribute-category:resource");
categories.add("urn:oasis:names:tc:xacml:3.0:attribute-category:environment");
return categories;
}
@Override
public Set<String> getSupportedIds() {
Set<String> ids = new HashSet<>();
ids.add("urn:oasis:names:tc:xacml:1.0:subject:subject-id");
ids.add("urn:oasis:names:tc:xacml:2.0:subject:role");
ids.add("urn:oasis:names:tc:xacml:1.0:subject:department");
ids.add("urn:oasis:names:tc:xacml:1.0:subject:clearance");
ids.add("urn:oasis:names:tc:xacml:1.0:resource:resource-id");
ids.add("urn:oasis:names:tc:xacml:1.0:resource:sensitivity");
ids.add("urn:oasis:names:tc:xacml:1.0:resource:owner");
ids.add("urn:oasis:names:tc:xacml:1.0:environment:current-time");
ids.add("urn:oasis:names:tc:xacml:1.0:environment:current-date");
return ids;
}
@Override
public AttributeValue findAttribute(URI category, URI attributeType,
URI attributeId, String issuer,
EvaluationCtx context) {
String categoryStr = category.toString();
String attributeIdStr = attributeId.toString();
try {
if (categoryStr.equals("urn:oasis:names:tc:xacml:1.0:subject-category:access-subject")) {
return findSubjectAttribute(attributeIdStr, context);
} else if (categoryStr.equals("urn:oasis:names:tc:xacml:3.0:attribute-category:resource")) {
return findResourceAttribute(attributeIdStr, context);
} else if (categoryStr.equals("urn:oasis:names:tc:xacml:3.0:attribute-category:environment")) {
return findEnvironmentAttribute(attributeIdStr, context);
}
} catch (Exception e) {
// Log error and return null
}
return null;
}
private AttributeValue findSubjectAttribute(String attributeId, EvaluationCtx context) {
// Extract subject ID from context
String subjectId = extractSubjectId(context);
if (subjectId == null) return null;
Map<String, String> attrs = userAttributes.get(subjectId);
if (attrs == null) return null;
String value = attrs.get(getSimpleAttributeName(attributeId));
if (value != null) {
return StringAttribute.getInstance(value);
}
return null;
}
private AttributeValue findResourceAttribute(String attributeId, EvaluationCtx context) {
// Extract resource ID from context
String resourceId = extractResourceId(context);
if (resourceId == null) return null;
Map<String, String> attrs = resourceAttributes.get(resourceId);
if (attrs == null) return null;
String value = attrs.get(getSimpleAttributeName(attributeId));
if (value != null) {
return StringAttribute.getInstance(value);
}
return null;
}
private AttributeValue findEnvironmentAttribute(String attributeId, EvaluationCtx context) {
switch (attributeId) {
case "urn:oasis:names:tc:xacml:1.0:environment:current-time":
return StringAttribute.getInstance(new Date().toString());
case "urn:oasis:names:tc:xacml:1.0:environment:current-date":
return StringAttribute.getInstance(java.time.LocalDate.now().toString());
default:
return null;
}
}
private String extractSubjectId(EvaluationCtx context) {
// Implementation to extract subject ID from evaluation context
// This is a simplified version
return "user1"; // Placeholder
}
private String extractResourceId(EvaluationCtx context) {
// Implementation to extract resource ID from evaluation context
// This is a simplified version
return "document1"; // Placeholder
}
private String getSimpleAttributeName(String fullAttributeId) {
// Convert full attribute ID to simple name
if (fullAttributeId.contains(":")) {
return fullAttributeId.substring(fullAttributeId.lastIndexOf(":") + 1);
}
return fullAttributeId;
}
// Methods to manage attribute data
public void addUserAttribute(String userId, String attributeName, String attributeValue) {
userAttributes.computeIfAbsent(userId, k -> new HashMap<>())
.put(attributeName, attributeValue);
}
public void addResourceAttribute(String resourceId, String attributeName, String attributeValue) {
resourceAttributes.computeIfAbsent(resourceId, k -> new HashMap<>())
.put(attributeName, attributeValue);
}
}
Sample Policies
Role-Based Access Policy
public class SamplePolicies {
public static final String ROLE_BASED_POLICY =
"<Policy xmlns=\"urn:oasis:names:tc:xacml:3.0:core:schema:wd-17\" " +
"PolicyId=\"role-based-policy\" RuleCombiningAlgId=\"urn:oasis:names:tc:xacml:1.0:rule-combining-algorithm:permit-overrides\">\n" +
" <Target>\n" +
" <AnyOf>\n" +
" <AllOf>\n" +
" <Match MatchId=\"urn:oasis:names:tc:xacml:1.0:function:string-equal\">\n" +
" <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">document</AttributeValue>\n" +
" <AttributeDesignator AttributeId=\"urn:oasis:names:tc:xacml:1.0:resource:resource-type\" " +
"Category=\"urn:oasis:names:tc:xacml:3.0:attribute-category:resource\" DataType=\"http://www.w3.org/2001/XMLSchema#string\" MustBePresent=\"true\"/>\n" +
" </Match>\n" +
" </AllOf>\n" +
" </AnyOf>\n" +
" </Target>\n" +
" <Rule Effect=\"Permit\" RuleId=\"admin-rule\">\n" +
" <Target>\n" +
" <AnyOf>\n" +
" <AllOf>\n" +
" <Match MatchId=\"urn:oasis:names:tc:xacml:1.0:function:string-equal\">\n" +
" <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">admin</AttributeValue>\n" +
" <AttributeDesignator AttributeId=\"urn:oasis:names:tc:xacml:2.0:subject:role\" " +
"Category=\"urn:oasis:names:tc:xacml:1.0:subject-category:access-subject\" DataType=\"http://www.w3.org/2001/XMLSchema#string\" MustBePresent=\"true\"/>\n" +
" </Match>\n" +
" </AllOf>\n" +
" </AnyOf>\n" +
" </Target>\n" +
" </Rule>\n" +
" <Rule Effect=\"Permit\" RuleId=\"user-rule\">\n" +
" <Target>\n" +
" <AnyOf>\n" +
" <AllOf>\n" +
" <Match MatchId=\"urn:oasis:names:tc:xacml:1.0:function:string-equal\">\n" +
" <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">user</AttributeValue>\n" +
" <AttributeDesignator AttributeId=\"urn:oasis:names:tc:xacml:2.0:subject:role\" " +
"Category=\"urn:oasis:names:tc:xacml:1.0:subject-category:access-subject\" DataType=\"http://www.w3.org/2001/XMLSchema#string\" MustBePresent=\"true\"/>\n" +
" </Match>\n" +
" </AllOf>\n" +
" </AnyOf>\n" +
" </Target>\n" +
" <Condition>\n" +
" <Apply FunctionId=\"urn:oasis:names:tc:xacml:1.0:function:string-equal\">\n" +
" <Apply FunctionId=\"urn:oasis:names:tc:xacml:1.0:function:string-one-and-only\">\n" +
" <AttributeDesignator AttributeId=\"urn:oasis:names:tc:xacml:1.0:action:action-id\" " +
"Category=\"urn:oasis:names:tc:xacml:3.0:attribute-category:action\" DataType=\"http://www.w3.org/2001/XMLSchema#string\" MustBePresent=\"true\"/>\n" +
" </Apply>\n" +
" <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#string\">read</AttributeValue>\n" +
" </Apply>\n" +
" </Condition>\n" +
" </Rule>\n" +
" <Rule Effect=\"Deny\" RuleId=\"deny-rule\"/>\n" +
"</Policy>";
public static final String TIME_BASED_POLICY =
"<Policy xmlns=\"urn:oasis:names:tc:xacml:3.0:core:schema:wd-17\" " +
"PolicyId=\"time-based-policy\" RuleCombiningAlgId=\"urn:oasis:names:tc:xacml:1.0:rule-combining-algorithm:permit-overrides\">\n" +
" <Rule Effect=\"Permit\" RuleId=\"business-hours-rule\">\n" +
" <Condition>\n" +
" <Apply FunctionId=\"urn:oasis:names:tc:xacml:1.0:function:and\">\n" +
" <Apply FunctionId=\"urn:oasis:names:tc:xacml:2.0:function:time-greater-than-or-equal\">\n" +
" <Apply FunctionId=\"urn:oasis:names:tc:xacml:1.0:function:time-one-and-only\">\n" +
" <AttributeDesignator AttributeId=\"urn:oasis:names:tc:xacml:1.0:environment:current-time\" " +
"Category=\"urn:oasis:names:tc:xacml:3.0:attribute-category:environment\" DataType=\"http://www.w3.org/2001/XMLSchema#time\" MustBePresent=\"true\"/>\n" +
" </Apply>\n" +
" <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#time\">09:00:00</AttributeValue>\n" +
" </Apply>\n" +
" <Apply FunctionId=\"urn:oasis:names:tc:xacml:2.0:function:time-less-than-or-equal\">\n" +
" <Apply FunctionId=\"urn:oasis:names:tc:xacml:1.0:function:time-one-and-only\">\n" +
" <AttributeDesignator AttributeId=\"urn:oasis:names:tc:xacml:1.0:environment:current-time\" " +
"Category=\"urn:oasis:names:tc:xacml:3.0:attribute-category:environment\" DataType=\"http://www.w3.org/2001/XMLSchema#time\" MustBePresent=\"true\"/>\n" +
" </Apply>\n" +
" <AttributeValue DataType=\"http://www.w3.org/2001/XMLSchema#time\">17:00:00</AttributeValue>\n" +
" </Apply>\n" +
" </Apply>\n" +
" </Condition>\n" +
" </Rule>\n" +
" <Rule Effect=\"Deny\" RuleId=\"deny-after-hours\"/>\n" +
"</Policy>";
}
Spring Boot Integration
Configuration Class
package com.xacml.config;
import com.xacml.engine.BasicPDPEngine;
import com.xacml.pap.PolicyAdministrationPoint;
import com.xacml.pip.CustomAttributeFinder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class XACMLConfig {
@Bean
public BasicPDPEngine pdpEngine() {
return new BasicPDPEngine();
}
@Bean
public PolicyAdministrationPoint policyAdministrationPoint() {
return new PolicyAdministrationPoint();
}
@Bean
public CustomAttributeFinder attributeFinder() {
return new CustomAttributeFinder();
}
}
REST Controller
package com.xacml.controller;
import com.xacml.engine.BasicPDPEngine;
import com.xacml.pap.PolicyAdministrationPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.wso2.balana.ctx.xacml3.ResponseCtx;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/xacml")
public class XACMLController {
@Autowired
private BasicPDPEngine pdpEngine;
@Autowired
private PolicyAdministrationPoint pap;
@PostMapping("/evaluate")
public ResponseEntity<Map<String, Object>> evaluateRequest(@RequestBody AccessRequest request) {
Map<String, Object> response = new HashMap<>();
try {
ResponseCtx result = pdpEngine.evaluateRequest(request.getXacmlRequest());
response.put("decision", result.getResults()[0].getDecision().toString());
response.put("success", true);
// Add additional response details
Map<String, String> details = new HashMap<>();
details.put("policyId", result.getResults()[0].getPolicyIdentifier().toString());
response.put("details", details);
return ResponseEntity.ok(response);
} catch (Exception e) {
response.put("success", false);
response.put("error", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
@PostMapping("/policy")
public ResponseEntity<Map<String, Object>> addPolicy(@RequestBody PolicyRequest policyRequest) {
Map<String, Object> response = new HashMap<>();
try {
pap.addPolicy(policyRequest.getPolicyId(), policyRequest.getPolicyXml());
response.put("success", true);
response.put("message", "Policy added successfully");
return ResponseEntity.ok(response);
} catch (Exception e) {
response.put("success", false);
response.put("error", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
@GetMapping("/policy/{policyId}")
public ResponseEntity<Map<String, Object>> getPolicy(@PathVariable String policyId) {
Map<String, Object> response = new HashMap<>();
try {
org.wso2.balana.Policy policy = pap.getPolicy(policyId);
if (policy != null) {
response.put("success", true);
response.put("policy", policy.encode());
} else {
response.put("success", false);
response.put("error", "Policy not found");
}
return ResponseEntity.ok(response);
} catch (Exception e) {
response.put("success", false);
response.put("error", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
// Request DTOs
public static class AccessRequest {
private String xacmlRequest;
public String getXacmlRequest() { return xacmlRequest; }
public void setXacmlRequest(String xacmlRequest) { this.xacmlRequest = xacmlRequest; }
}
public static class PolicyRequest {
private String policyId;
private String policyXml;
public String getPolicyId() { return policyId; }
public void setPolicyId(String policyId) { this.policyId = policyId; }
public String getPolicyXml() { return policyXml; }
public void setPolicyXml(String policyXml) { this.policyXml = policyXml; }
}
}
Advanced Features
Policy Validation Service
package com.xacml.validation;
import org.wso2.balana.Policy;
import org.wso2.balana.PolicySet;
import org.wso2.balana.parser.PolicyBuilder;
import org.wso2.balana.parser.PolicySetBuilder;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class PolicyValidationService {
/**
* Validate XACML policy syntax
*/
public ValidationResult validatePolicySyntax(String policyXml) {
try {
Policy policy = Policy.getInstance(new ByteArrayInputStream(policyXml.getBytes()));
return new ValidationResult(true, "Policy syntax is valid", policy.getVersion());
} catch (Exception e) {
return new ValidationResult(false, "Invalid policy syntax: " + e.getMessage(), null);
}
}
/**
* Validate policy set syntax
*/
public ValidationResult validatePolicySetSyntax(String policySetXml) {
try {
PolicySet policySet = PolicySet.getInstance(new ByteArrayInputStream(policySetXml.getBytes()));
return new ValidationResult(true, "PolicySet syntax is valid", policySet.getVersion());
} catch (Exception e) {
return new ValidationResult(false, "Invalid PolicySet syntax: " + e.getMessage(), null);
}
}
/**
* Check for policy conflicts
*/
public List<Conflict> detectConflicts(List<Policy> policies) {
List<Conflict> conflicts = new ArrayList<>();
// Simple conflict detection based on target overlaps
for (int i = 0; i < policies.size(); i++) {
for (int j = i + 1; j < policies.size(); j++) {
if (policiesOverlap(policies.get(i), policies.get(j))) {
conflicts.add(new Conflict(
policies.get(i).getId().toString(),
policies.get(j).getId().toString(),
"Target overlap detected"
));
}
}
}
return conflicts;
}
private boolean policiesOverlap(Policy policy1, Policy policy2) {
// Simplified overlap detection
// In practice, this would involve complex target matching logic
return policy1.getTarget().equals(policy2.getTarget());
}
public static class ValidationResult {
private final boolean valid;
private final String message;
private final String version;
public ValidationResult(boolean valid, String message, String version) {
this.valid = valid;
this.message = message;
this.version = version;
}
public boolean isValid() { return valid; }
public String getMessage() { return message; }
public String getVersion() { return version; }
}
public static class Conflict {
private final String policyId1;
private final String policyId2;
private final String description;
public Conflict(String policyId1, String policyId2, String description) {
this.policyId1 = policyId1;
this.policyId2 = policyId2;
this.description = description;
}
public String getPolicyId1() { return policyId1; }
public String getPolicyId2() { return policyId2; }
public String getDescription() { return description; }
}
}
Testing the Implementation
JUnit Tests
package com.xacml.test;
import com.xacml.engine.BasicPDPEngine;
import com.xacml.pap.PolicyAdministrationPoint;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.wso2.balana.Decision;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
class XACMLEngineTest {
private BasicPDPEngine pdpEngine;
private PolicyAdministrationPoint pap;
@BeforeEach
void setUp() throws Exception {
pdpEngine = new BasicPDPEngine();
pap = new PolicyAdministrationPoint();
// Load sample policies
pap.addPolicy("role-policy", SamplePolicies.ROLE_BASED_POLICY);
pdpEngine.loadPolicy(SamplePolicies.ROLE_BASED_POLICY);
}
@Test
void testRoleBasedAccess() throws Exception {
// Test admin access
Decision decision1 = pdpEngine.evaluateAccess(
"user1", "admin", "document1", "write", new HashMap<>());
assertEquals(Decision.PERMIT, decision1);
// Test user read access
Decision decision2 = pdpEngine.evaluateAccess(
"user2", "user", "document1", "read", new HashMap<>());
assertEquals(Decision.PERMIT, decision2);
// Test user write access (should be denied)
Decision decision3 = pdpEngine.evaluateAccess(
"user2", "user", "document1", "write", new HashMap<>());
assertEquals(Decision.DENY, decision3);
}
@Test
void testPolicyManagement() throws Exception {
// Test policy addition
pap.addPolicy("test-policy", SamplePolicies.TIME_BASED_POLICY);
assertNotNull(pap.getPolicy("test-policy"));
// Test policy removal
pap.removePolicy("test-policy");
assertNull(pap.getPolicy("test-policy"));
}
}
Performance Optimization
Caching PDP
package com.xacml.engine;
import org.wso2.balana.ctx.xacml3.RequestCtx;
import org.wso2.balana.ctx.xacml3.ResponseCtx;
import java.util.concurrent.*;
public class CachedPDPEngine extends BasicPDPEngine {
private final Cache<RequestCtx, ResponseCtx> decisionCache;
private final long cacheTimeoutMs;
public CachedPDPEngine(long cacheTimeoutMs, int maxCacheSize) {
super();
this.cacheTimeoutMs = cacheTimeoutMs;
this.decisionCache = new Cache<>(maxCacheSize, cacheTimeoutMs);
}
@Override
public ResponseCtx evaluateRequest(String xacmlRequest) throws Exception {
RequestCtx request = RequestCtx.getInstance(
new ByteArrayInputStream(xacmlRequest.getBytes()));
// Check cache first
ResponseCtx cachedResponse = decisionCache.get(request);
if (cachedResponse != null) {
return cachedResponse;
}
// Evaluate and cache result
ResponseCtx response = super.evaluateRequest(xacmlRequest);
decisionCache.put(request, response);
return response;
}
/**
* Simple LRU cache implementation
*/
private static class Cache<K, V> {
private final ConcurrentHashMap<K, V> map;
private final ConcurrentLinkedQueue<K> queue;
private final int maxSize;
private final long timeoutMs;
public Cache(int maxSize, long timeoutMs) {
this.maxSize = maxSize;
this.timeoutMs = timeoutMs;
this.map = new ConcurrentHashMap<>();
this.queue = new ConcurrentLinkedQueue<>();
}
public V get(K key) {
return map.get(key);
}
public void put(K key, V value) {
if (map.size() >= maxSize) {
K oldestKey = queue.poll();
if (oldestKey != null) {
map.remove(oldestKey);
}
}
queue.add(key);
map.put(key, value);
}
}
}
Conclusion
This comprehensive XACML policy engine implementation in Java provides:
- Complete PDP engine with Balana integration
- Policy administration for managing XACML policies
- Attribute-based authorization with custom PIP
- RESTful API for integration with applications
- Policy validation and conflict detection
- Performance optimization with caching
The engine supports complex authorization scenarios including role-based access control, attribute-based rules, time-based restrictions, and custom business logic. It can be integrated into enterprise applications to provide fine-grained, centralized authorization management following the XACML standard.