Skip to main content

Policy testing framework

Introduction

Policy Testing Framework is a Gradle project and JUnit extension to develop and test ALFA policies, attribute connectors and lifecycle your entire Attribute Based Access Control (ABAC) domain.

An ALFA project can be a fundamental part in achieving DevOps and a CI/CD pipeline of ALFA and ABAC and can be integrated into your organization's available build tools (such as Jenkins, Azure DevOps or OpenShift).

It is also ideal for learning ALFA and Axiomatics' components.

This page contains introduction and documentation for writing policies and tests. In the left menu you can find other topics, such as ADM (Axiomatics Domain Manager) and pipeline integrations.

This project is based on Gradle, a build automation tool that integrates well with build severs like Jenkins as well as IDEs such as IntelliJ or Visual Studio Code.

This tool provides:

  • Unit and white box testing of individual ALFA policies

  • Integration testing of multiple policies and/or policy combination algorithms

  • System testing of the main policy

  • Be configured to run with or without attribute connectors

  • Test attribute connectors in isolation as a unit

  • Platform or accepting testing by executing the system tests towards an external ADS service

  • Supports externalizing runtime configuration to the environment, to be able to reuse tests from a local developer's laptop all the way up to system test of a production environment.

  • Supports externalizing logical configuration (parametrized tests) to the environment, to be able to handle different test data sets in different environments

  • Resolve external policies from Maven-like repository

  • Building a docker image of Axiomatics Decision Manager (ADM) with the authorization domain in the image.

  • Push, pull a domain to/from Axiomatics Domain Manager (ADM) and ASM 7.

  • Promote or copy domain within or between multiple Axiomatics Domain Managers (ADM) and ASM7.

This tool aims at letting you treat ALFA policies as code to implement a Test Driven Development (TDD) and shift-left approach to Attribute Based Access Control (ABAC).

Support

Use your normal Axiomatics support channel for support, bug reports or feature requests. If you are not yet a customer we still welcome your questions. Please visit our website, https://www.axiomatics.com

Java Requirement

Java 11 or greater is required to compile and run this project.

Prerequisites

Make sure you fulfill the following requirements before proceeding:

  • Internet access
  • Supported JDK installed
  • Axiomatics S3 Maven credentials
  • An IDE suitable for Java development (optional, but Visual Studio Code recommended)
  • Axiomatics ADS software license (optional)
  • Docker (optional)

Getting started

Follow these steps to get started with Policy testing framework.

Clone git repo

Clone the GitHub project https://github.com/axiomatics/policy-testing-framework.

git clone https://github.com/axiomatics/policy-testing-framework
note

If you are asked for credentials you need to log in with the credentials given to you by Axiomatics.

Configure Axiomatics Maven keys

Move into the cloned git repo and install Axiomatics repository key for access to Axiomatics' Maven repository in file gradle.properties, ~/.gradle/gradle.properties or set the environment variables with the same names. If you have not received the access keys, contact your sales representative at Axiomatics.

gradle.properties
AXIOMATICS_ACCESS_KEY_ID=<key_id>
AXIOMATICS_SECRET_ACCESS_KEY=<access_key>

Copy license (optional)

Copy your software license file axiomatics_PDP.license to the license/ directory.

Project structure

The root directory contains a Gradle example project with the following content:

Directory/FileDescription
src/authorizationDomainSources for the authorization attribute connector configuration) as outlined in Authorization domain V2 directory layout convention
src/testPolicies and attribute connectors tests written in Java.
src/extraExtra files needed in ADS or test runtime, such as key- or truststore.
buildSrcAxiomatics internal script to bootstrap the projects. Should not be modified.
extra/exampleRequests/Example of sending requests using curl.
gradle/Gradle internal files. Should not be modified
license/Place for Axiomatics ADS license file
build.gradleProject configuration.

Quick start in Visual Studio Code

The sample ALFA project can be loaded and worked with as any other software project. This section show how to do that in Visual Studio Code, but you can use any other Integrated Developer Environment (IDEs) that integrates with Gradle and Java.

  1. Open and install necessary extensions in Visual Studio Code:

    note

    If you are asked to install a Java or JDK, please choose a version listed in Java Requirement.

In Visual Studio Code, chose Open Folder and select the folder where you cloned the Git repo. After the project has loaded you should see the project in the Explorer sidebar. It can take several minutes for the project to initialize as it needs to download software from internet.

info

If the project does not load or loads with errors, make sure you added the correct Axiomatics Maven credentials.

tip

Make sure you can see the Gradle elephant icon in the activity sidebar to the right. If you do not, it is accessibly from menu View➜Open View...➜Gradle.

note

If your corporate network policy does not allow software to be downloaded from Maven Central or AWS S3, please contact Axiomatics for an air-gap (offline) solution.

ALFA Project in Visual Studio Code

The image above shows a view in Visual Studio Code after policy testing framework project has been loaded correctly. ALFA policies and attribute dictionary are located under src/authorizationDomain.

Run tests

First we are going to make sure everything is set up correctly. We will do so by executing the tests from the example project that is included in the repo.

Select the Gradle (elephant) icon in the sidebar. Double-click on my-alfa-project > Tasks > verification > test. The tests will run and the output should finish with BUILD SUCCESSFUL message. For a full reference on the gradle tasks, see Gradle.

Testing in Visual Studio Code

Build the authorization domain

From the Gradle sidebar the authorization domain can be built by target axiomatics > buildAuthzDomain. The authorization domain configuration will be written to build/alfa/domain/ads as domain.yaml.

Build authorization domain in Visual Studio  Code

Authorization domain V2 directory layout convention

The following table describes the content of the project.

📦alfa                               
┣ 📂build 📖 Temporary directory with outputs
┣ 📂buildSrc ⛔ Axiomatics internal bootstrapping scripts
┣ 📂extra 📖 Extra resources
┣ 📂gradle ⛔ Gradle internal bootstrapping scripts
┣ 📂license ✍ Directory to place Axiomatics software licenses
┣ 📂src Source code directory
┃ ┣ 📂authorizationDomain Authorization domain source code directory
┃ ┃ ┣ 📂alfaSpecifications ✍ ALFA policies
┃ ┃ ┣ 📂attributeConnectors ✍ Attribute connectors
┃ ┃ ┣ 📜attributeCache.yaml ✍ Attribute cache settings
┃ ┃ ┣ 📜attributes.yaml ✍ Attribute dictionary
┃ ┃ ┣ 📜decisionParameters.yaml ✍ ADS evaluation parameters
┃ ┃ ┣ 📜identity.yaml Authorization Domain identifier, override automatic identity based on Git
┃ ┃ ┗ 📜metadata.yaml ✍ Authorization Domain metadata
┃ ┗ 📂extra ✍ Runtime resources
┃ ┗ 📂test Test source code directory
┃ ┃ ┣ 📂java ✍ ALFA JUnit tests
┃ ┃ ┗ 📂resources ✍ Test resources
┣ 📜build.gradle ✍ Project build definition
┣ 📜deployment.yaml ✍ ADS deployment descriptor
┣ 📜Dockerfile ✍ Dockerfile to build ADS service
┣ 📜gradle.properties ✍ Credentials to Axiomatics Maven repository
┣ 📜gradlew ⛔ Gradle internal bootstrapping scripts
┣ 📜gradlew.bat ⛔ Gradle internal bootstrapping scripts
┣ 📜headers.json ✍ Example of optional HTTP headers to ADS
┣ 📜README.md ✍ Project description
┗ 📜settings.gradle ✍ Project settings
Legend:
✍ File or directory that you are likely to write to or add files to
📖 File or directory that contains output (read only).
⛔ File or directory that you normally should not touch

More information

For full reference of Authorization Domain layout, see Authorization domain V2 directory layout convention.

Using the Policy Test framework

By now, if you followed the documentation, you should have a functional setup of Policy Testing Framework and also have run some tasks based on the example project included.

This section describes a workflow to create an authorization domain with ALFA policies, attribute connectors to your PIPs and tests. Workflow is only a suggestion, and you can start anywhere to explore and learn ALFA and policy testing framework.

Step 1. Capture requirements

Before starting with implementation of your authorization domain, capture your business requirements and use cases for authorization in an existing tool that you have (JIRA, Confluence, Excel or a text file in this repository).

Step 2. Dictionary

Extract the necessary attributes to implement such requirements and specify them in an attribute dictionary document close to the requirements document created earlier. Maintaining a good dictionary document makes it easier to communicate with external stakeholders (business owner, PIP owners or auditors, for example) in the future.

Namespace and nameCategoryTypeSourceKeyResponsible/SLACardinalityReferenceDescriptionExamples
acme.user.identitysubjectstring- (PEP)--1..1HR AD, 6 character alphanumAcme employee signaturejohsmi, jandoe
acme.customer.identityresourcestring- (PEP)--0...​*Salesforce customer idThe customer(s) involved in action231698, 249111
acme.customer.locationresourcestringSalesforce customer PIPacme.customer.identityBusiness Operation team, 99%0...​*ISO 3166-1Customer origin countrySE, US
Important

id is a reserved keyword in ALFA and can not be used in attribute names. For a full list of reserved keywords, see here.

Creating attributes

Next, attributes need to be defined in dictionaries before they can be used in ALFA policies or Attribute Connectors. There are two dictionaries that needs to be populated.

  1. src/authorizationDomain/alfaSpecifications/attributes.alfa contains attributes used in ALFA policies and in tests. These attributes are called policy attributes.

  2. src/authorizationDomain/attributes.yaml contains attributes that are used as input and output from Attribute Connectors. These attributes are called attribute connector attributes.

info

As attribute connector output attributes are used in policies, some attributes must be defined in both dictionaries mentioned above.

Policy attributes

To use an attribute in ALFA policy or in a Junit test define it in an ALFA file inside the authorizationDomain directory. Organize your attribute in namespaces hierarchies as needed.

attributes.alfa
    namespace acme {
namespace user {
attribute role {
id = "acme.user.role"
type = string
category = subjectCat
}
}

namespace resource {
attribute location {
id = "acme.resource.location"
type = string
category = resourceCat
}

attribute identity {
id = "acme.resource.identity"
type = string
category = resourceCat
}
}
}

The id should always be set to namespaces concatenated with a dot plus the attribute name.

type is the JSON shorthand type code defined here.

category is any of: subjectCat, resourceCat, actionCat or environmentCat and defines if the attribute is a property of a subject (such as role) or resource (such as document) etc.

The format of this file is ALFA. Attributes can be defined in any ALFA file and is not restricted to the attributes.alfa file.

Attribute connector attributes

Attributes that used in Attribute Connectors (either as input or output attributes) needs to be defined in the attribute.yaml file. This file is YAML format. More information here.

attributes.yaml
acme.user.role:
xacmlId: acme.user.role
category: AccessSubject
datatype: string
acme.user.location:
xacmlId: acme.user.location
category: AccessSubject
datatype: string
acme.resource.location:
xacmlId: acme. resource.location
category: Resource
datatype: string

xacmlId can for convenience always be set to namespaces concatenated with a dot plus the attribute name.

category is any of: AccessSubject, Resource, Action or Environment and defines if the attribute is a property of a subject (such as role) or resource (such as owner) etc.

datatype is the JSON shorthand type code defined here.

The format of this file is YAML.

Step 3. Creating tests

We advocate test-driven development (TDD) of ALFA and your authorization domain. This means that before you are allowed to write any effective code (policies or attribute connectors) you must first write and run a test that fails. So, go ahead and write a unit test that fails. See Testing ALFA policies.

Step 4. Creating attribute connectors

Attribute Connectors connects the PDP with external PIPs. With the help of attribute connectors the PDP can in runtime call out to your business services to fetch external information, such as a user's role based on the user's identity. It is possible to cache these attribute values.

An attribute connector consists of two files.

  • A deployment descriptor file
  • A configuration file

Attribute Connector description and configuration files

In the authorizationDomain/attributeConnectors directory, create a new Attribute Connector deployment descriptor file. Specify the classname of the connector and which attribute(s) it provides.

authorizationDomain/attributeConnectors/myConnector.yaml
className: <class name>
provides:
- user.role
- user.location

The table below lists the built-in Attribute Connectors available. If you have developed a custom connector, specify your own classname.

tip

A typical scenario is to retrieve attribute values from a remote REST/JSON API. In such case you first need one HTTP Attribute Connector which is linked to a JSON Parser attribute connector.

Attribute ConnectorClass name
LDAPcom.axiomatics.acs.plugin.pips.ldap.LdapPipModule
SQLcom.axiomatics.acs.plugin.pips.sql.SqlPipModule
SQL Tablecom.axiomatics.acs.plugin.pips.table.TablePipModule
Constantcom.axiomatics.cr.pip.constant.ConstantAttributeConnector
HTTPcom.axiomatics.attributeconnector.http.ConnectorModule
JSON parsercom.axiomatics.attributeconnector.parser.json.ConnectorModule
JWT parsercom.axiomatics.attributeconnector.parser.jwt.ConnectorModule
XML parsercom.axiomatics.attributeconnector.parser.xml.ConnectorModule
Http, old versioncom.axiomatics.pip.http.HttpPIP (old deprecated)
Parser (JSON or XML), oldcom.axiomatics.pip.parser.ParserPIP (old deprecated)
info

In 2023, Axiomatics has released new versions of HTTP and Parser connectors. If you are using the deprecated classNames above, you are encourage to upgrade to new version and new class name. The HTTP XML configuration is backwards compatible, whereas the Parser XML configuration is not and you should translate the XML to the new configuration format.

Next, create a configuration file. It needs to start with the same name as you deployment file, but with the suffix _configurationString.[ext].

tip

If your deployment file is called myConnector.yaml, the configuration file (given it accepts YAML) should be called myConnector.configurationString.yaml.

The extension of the file can be chosen to reflect the actual configuration format supported by the connector.

The next sections outline example configuration for respective connector. These are just an example and each connector's own documentation can be consulted for detailed information. See https://docs.axiomatics.com/attribute-connectors/

info

In all attribute connector configurations it is possible to use environment variables, see variable substitution.

LDAP

LDAP AC documentation

Example of myLdapConnector.configurationString.xml
<pip:configuration xmlns:pip="http://www.axiomatics.com/ldap.config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.axiomatics.com/ldap.config ldap.config.xsd ">
<pip:connnection>
<pip:key name="java.naming.factory.initial" value="com.sun.jndi.ldap.LdapCtxFactory"/>
<pip:key name="java.naming.security.credentials" value="´${AC_LDAP1_PASSWORD:-secret}"/>
<pip:key name="java.naming.security.principal" value="${AC_LDAP1_USERNAME:-uid=admin,ou=system}"/>
<pip:key name="java.naming.security.authentication" value="simple"/>
<pip:key name="java.naming.provider.url" value="${AC_LDAP1_URL:-ldap://10.0.1.196:10389}"/>
</pip:connnection>
<pip:mapping>
<pip:xacmlAttribute AttributeId="acme.role" Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject" DataType="http://www.w3.org/2001/XMLSchema#string" Issuer=""/>
<pip:nativeAttribute>
<pip:searchSelection>sn</pip:searchSelection>
<pip:searchBase>dc=axiomatics,dc=com</pip:searchBase>
<pip:searchBaseKey><pip:xacmlAttribute AttributeId="urn:oasis:names:tc:xacml:1.0:subject:subject-id" Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject" DataType="http://www.w3.org/2001/XMLSchema#string" Issuer=""/></pip:searchBaseKey>
<pip:searchScope>2</pip:searchScope>
<pip:searchFilter>cn=?</pip:searchFilter>
<pip:key allowMultiple="false">
<pip:xacmlAttribute AttributeId="acme.username" Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject" DataType="http://www.w3.org/2001/XMLSchema#string" Issuer=""/>
</pip:key>
</pip:nativeAttribute>
</pip:mapping>
</pip:configuration>
SQL

SQL AC documentation

Example of mySqlConnector.configurationString.xml
<cfg:configuration xmlns:cfg="http://www.axiomatics.com/jdbc.config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.axiomatics.com/jdbc.config jdbc.config.xsd ">
<cfg:connnection>
<cfg:url>jdbc:postgresql://localhost/usda?user=usda&amp;password=password</cfg:url>
<cfg:driver>org.postgresql.Driver</cfg:driver>
</cfg:connnection>
<!-- mapping for allergy test -->
<cfg:mapping>
<cfg:xacmlAttribute AttributeId="food-group-name" Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource" DataType="http://www.w3.org/2001/XMLSchema#string" Issuer=""/>
<cfg:nativeAttribute>
<cfg:sqlType>2004</cfg:sqlType>
<cfg:query>SELECT fddrp_desc FROM fd_group WHERE fdgrp_cd=?</cfg:query>
<cfg:key allowMultiple="false" sqlType="12">
<cfg:xacmlAttribute AttributeId="food-id" Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource" DataType="http://www.w3.org/2001/XMLSchema#string" Issuer=""/>
</cfg:key>
</cfg:nativeAttribute>
<cfg:uId>1</cfg:uId>
</cfg:mapping>
</cfg:configuration>
SQL Table

SQL Table AC documentation

Example of mySqlTable.configurationString.xml
<cfg:configuration xmlns:cfg="http://www.axiomatics.com/table.config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.axiomatics.com/table.config table.config.xsd ">
<cfg:connnection>
<cfg:url>jdbc:postgresql://localhost/usda?user=usda&amp;password=password</cfg:url>
<cfg:driver>org.postgresql.Driver</cfg:driver>
</cfg:connnection>

<!-- mapping for allergy test -->
<cfg:mapping isSingleValued="false">
<cfg:xacmlAttribute AttributeId="food-group-name" Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource" DataType="http://www.w3.org/2001/XMLSchema#string" Issuer=""/>
<cfg:tableName>food-group-table</cfg:tableName>
<cfg:columnName>food-group-name</cfg:columnName>
<cfg:key allowMultiple="false">
<cfg:xacmlAttribute AttributeId="food-id" Category="urn:oasis:names:tc:xacml:3.0:attribute-category:resource" DataType="http://www.w3.org/2001/XMLSchema#string" Issuer=""/>
<cfg:columnName>food-id</cfg:columnName>
</cfg:key>
<cfg:uId>1</cfg:uId>
</cfg:mapping>

</cfg:configuration>
HTTP

HTTP AC documentation

Example of myHttpConnector.configurationString.xml
<configuration xmlns="http://www.axiomatics.com/http.config" identifier="sample-unique-id">
<connection>
<url>http://jsonplaceholder.typicode.com/posts/{0}</url>
<connectionKey>
<xacmlAttribute Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject" AttributeId="com.acme.user.employeeId" DataType="http://www.w3.org/2001/XMLSchema#string" Issuer=""/>
</connectionKey>
<method>GET</method>
</connection>
<mapping>
<xacmlAttribute Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject" AttributeId="pip.payload" DataType="http://www.w3.org/2001/XMLSchema#string" Issuer=""/>
<contentType>application/json</contentType>
</mapping>
</configuration>
JSON Parser

JSON Parser AC documentation

Example of myParserConnector.configurationString.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns="http://www.axiomatics.com/attributeconnector/parser/json/configuration"
identifier="mypip">
<source>
<xacmlAttribute AttributeId="pip.payload"
Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject"
DataType="http://www.w3.org/2001/XMLSchema#string"/>
</source>
<mapping>
<xacmlAttribute AttributeId="pip.status"
Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject"
DataType="http://www.w3.org/2001/XMLSchema#string"/>
<jsonPath>$.authorizations[?(@.id=='##1##')].status</jsonPath>
<key>
<xacmlAttribute AttributeId="pip.username"
Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject"
DataType="http://www.w3.org/2001/XMLSchema#string"/>
</key>
</mapping>
</configuration>
JWT Parser

JWT Parser AC documentation

Example of myParserConnector.configurationString.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns="http://www.axiomatics.com/attributeconnector/parser/jwt/configuration" identifier="jwtpip1">
<source>
<xacmlAttribute Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject"
AttributeId="jwt"
DataType="http://www.w3.org/2001/XMLSchema#string"/>
</source>
<signature>
<signatureKey>
{"kty":"oct","k":"NTk2MjhFNUNBNjk1RDc4NjY3RTZCRUIyQzU5MTdFNkU2NjdBQTA2N0I2QjZCRTg5RUI3MUMwQTZDRjYzNTA0MQ"}
</signatureKey>
</signature>
<assertions>
<notExpired skewInSeconds="5"/>
<notBefore optional="true"/>
<issuer value="trusted-iss">
<value>another-trusted-iss</value>
</issuer>
<audience value="accepted-aud"/>
</assertions>
<mapping>
<xacmlAttribute Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject"
AttributeId="subjectId"
DataType="http://www.w3.org/2001/XMLSchema#string"/>
<claim>sub</claim>
</mapping>
</configuration>
XML Parser

XML Parser AC documentation

Example of myParserConnector.configurationString.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns="http://www.axiomatics.com/attributeconnector/parser/xml/configuration"
identifier="xml-parser">
<source encoded="true">
<xml>PHVzZXJzPjxhbGljZT48cm9sZT5tYW5hZ2VyPC9yb2xlPjwvYWxpY2U+PC91c2Vycz4=</xml>
</source>
<mapping>
<xacmlAttribute AttributeId="pip.role"
Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject"
DataType="http://www.w3.org/2001/XMLSchema#string"/>
<xPath>/users/##1##/role/text()</xPath>
<key>
<xacmlAttribute AttributeId="pip.username"
Category="urn:oasis:names:tc:xacml:1.0:subject-category:access-subject"
DataType="http://www.w3.org/2001/XMLSchema#string"/>
</key>
</mapping>
</configuration>
Constant
Example of myConstantConnector.configurationString.xml
mappings:
- destination:
category: AccessSubject
datatype: string
id: user.role
dictionary:
martin: manager
cecilia: consultant
source:
category: AccessSubject
datatype: string
id: user.identity

Attribute caching

Attribute caching is configured in file src/authorizationDoman/attributeCache.yaml.
See ADS attribute caching for more information.

attributeCache.yaml
user.role:
timeToLive: 1 day
maxItems: 1000

Step 5. Creating policies

The last step in using the Policy Testing Framework is implementing the ALFA policies in way such that all tests succeeds. For more information see https://axiomatics.github.io/alfa-vscode-doc/.

Testing ALFA policies and connectors

A typical deployment with goal of going into production can have the following tests.

TestDescriptionWho, Where
Policy unit testTests a specific policy or rule for a use case in isolation without attribute connectors or other policies.Local developer, dev pipeline before deploying.
Attribute Connector unit testTests that a specific Attribute Connector can reach out to an external PIP to retrieve attributes.Local developer, dev pipeline before deploying.
Policy and Attribute Connector integration testTests that a specific policy for a use case works in integration with the Attribute Connectors it needs.Local developer, dev pipeline before deploying.
Policies integration testTests that multiple usecases with multiple policies works in combination and that combination alogrithms (conflict resolution) worksLocal developer, pipeline before deploying.
System/acceptance testTests that all functional and business use caess are solved by the main policy.Pipeline after deploying to target environemtn, product environment might be excluded.

Create a policy junit test

Create a java test class under src/test:

/src/test/MyUnitTest.java
import  org.junit.jupiter.api.extension.RegisterExtension;
import com.axiomatics.cr.alfa.test.junit.AlfaExtension;

public class MyUnitTest {

@RegisterExtension
public AlfaExtension alfa = new AlfaExtension();

Set policy under test

Set policy under test. This can either be set to your ALFA main policy or a specific policy deeper down, depending on if this is a system test or integration/unit test.

        @RegisterExtension
public AlfaExtension target = new AlfaExtension().withMainPolicy("acme.Main");
note

withMainPolicy is not needed if your alfaSpecifications directory only contains one root policy.

Execute test request and assert result

Execute a test and assert with the help of Hamcrest matchers.

import static com.axiomatics.cr.alfa.test.junit.matchers.AlfaMatchers.*
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;

...

@Test
public void shouldNotPermitConsultantsToApprove() {
TestResponse result = target.newTestRequest()
.with("user.role", "consultant")
.with("action", "approve")
.evaluate();

assertThat(result, is(not(permit())));

The following matchers exist:

  • permit()
  • deny()
  • `notApplicable()
  • intermediate()
note

Hamcrest predicates can be used, for example is() and is(not()).

Naming of test method is important. The method name should be self-explanatory and enough to implement and verify the implementation of the test. If you have a test case specification you can reference to it. Don´t worry if the name becomes long.

Name
✔️shouldPermit_AliceToRead_DocumentABC_Because_SheIsEmployee_And_DocumentIsUnclassified_Usecase_ABAC_APP17()
permitAliceToRead()

Testing Obligations and Advice

To assert for Obligations or Advice from your policy, the following assertion can be used.

Advice assertion example
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.MatcherAssert.assertThat;
import static com.axiomatics.cr.alfa.test.junit.matchers.AttributeAssignmentMatcher.withIdAndText;
import static com.axiomatics.cr.alfa.test.junit.matchers.AttributeAssignmentMatcher.withText;

TestResponse result = target.evaluate();
assertThat(result.getAdvice(), hasItem(withText("Advice from PDP")));

To target a specific attribute from an Obligation or an Advice this slightly longer assertion can be used.

Obligation attribute assertion example
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.MatcherAssert.assertThat;
import static com.axiomatics.cr.alfa.test.junit.matchers.AttributeAssignmentMatcher.withIdAndText;
import static com.axiomatics.cr.alfa.test.junit.matchers.AttributeAssignmentMatcher.withText;


TestResponse result = target.evaluate();
assertThat(result.getObligation(), hasItem(withIdAndText("org.obligation.message", "Obligation from PDP")));
Important

If you are implementing a unit test, you should not use withAttributeConnectors(), but simulate key attributes in a controlled manner by specifying them as test input using the .with(...).

Create attribute connectors tests

Attribute Connectors should also be unit tested.

Create a test file with the content below. The parameter to newAttributeTest(...) is the same as the filename of your connector under src/authorizationDomain/attributeConnectors, but without extension.

src/test/java/OurConnectorUnitTest.java

import com.axiomatics.cr.alfa.test.junit.AlfaExtension;
import com.axiomatics.cr.alfa.test.junit.AttributeConnector;
import ort org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.Test;

import java.util.List;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.MatcherAssert.assertThat;

public class OurConnectorUnitTest {

@RegisterExtension
public AlfaExtension alfa = new AlfaExtension();


@Test
public void shouldGetRoleConsultantforIdentityCecilia() {
AttributeConnector target = alfa.newAttributeTest("ourConnector");

List<String> result = target.lookup("user.role").by("user.identity", "cecilia");

assertThat(result, hasItem("consultant"));
}

Method lookup(...) takes one string and there you put the attribute that you want to retrieve from your PIP.

Method by(...) take pairs of strings and there you put all key attributes names and values needed to preform the lookup.

For example, if an attribute connector supports retrieving a user approval limit based on user's identity and the department of the purchase order the lookup may look target.lookup("user.approval.limit").by("user.identity", "cecilia","resource.purchaseOrder.department, "InternalSales");

Environment variables needed for attribute connectors, such as URLs and credentials should, for unit tests, be added in build.gradle in the test section.

Chained attribute connectors

If you have chained attribute connectors, where one depends on another it is possible to create an attribute connectors integration test by feeding the output of one as the input of another. This is useful for the HTTP and Parser connectors:

chained attribute connector test
   @Test
public void shouldGetCleareanceForUserAlice() {
String user = "alice";
String userServiceHttpBodyValue = rule.newAttributeTest("userHttpService").lookup("user.serviceHttpBody").by("user.identity", user).get(0);
String userClearance = rule.newAttributeTest("userHttpServiceParser").lookup("user.clearance").by("user.serviceHttpBody",userServiceHttpBodyValue).get(0);
assertThat(userClearance, is("classified"));
}

Create authorization domain system tests

Authorization domain system test will test all your policies and attribute connectors together to see that they fulfill the use cases and requirements.

To create a system test, omit the withMainPolicy() declaration in the test rule. By doing so the main policy for your domain will be tested (which you specify in build.gradle).

Secondly, add the withAttributeConnectors() declaration to attach all attribute connectors to the test rule.

The necessary environment variables should already have been defined as part of attribute connector unit tests.

Example of system test
package com.myorg.alfa;

import com.axiomatics.cr.alfa.test.junit.AlfaExtension;
import com.axiomatics.cr.alfa.test.junit.TestRequest;
import com.axiomatics.cr.alfa.test.junit.TestResponse;
import org.junit.jupiter.api.Test;
import ort org.junit.jupiter.api.extension.RegisterExtension;

import static com.axiomatics.cr.alfa.test.junit.matchers.AlfaMatchers.permit;
import static com.axiomatics.cr.alfa.test.junit.matchers.AttributeAssignmentMatcher.withText;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;

public class MySystemTest {

@RegisterExtension
public AlfaExtension alfa = new AlfaExtension().withAttributeConnectors();

@Test
public void shouldGiveMartinAccessToResource1() {
TestRequest target = alfa.newTestRequest()
.with("user.identity", "martin")
.with("resource.identity", "1");

TestResponse result = target.evaluate();

assertThat(result, is(permit()));
assertThat(result.getAdvice(), hasItem(withText("Permit since user is manager")));
}
}

Running test against a remote service

The same tests that are run in your editor can also be run against a remote authorization service (ADS), either from your local computer or a build server (e.g. Jenkins or Azure Devops).

Create a remote test target task

You can test a remote ADS by creating a new test target task in build.gradle.

build.gradle
task test_ProdEnvironment(type: Test) {
group "verification"
environment "ALFA_TEST_REMOTE_URL", "http://abac.acme.com:8081/authorize"
environment "ALFA_TEST_REMOTE_USER", "pdp-user"
environment "ALFA_TEST_REMOTE_PASSWORD", "secret"
environment "ALFA_TEST_REMOTE_HEADER_FILE", "headers.json"

}
tip

HTTP headers can be added for the remote connection to ADS. See Extra headers.

Because we can not control which policy is the main policy of the remote service (it is already started with a main policy), only your system tests will be executed on against the remote ADS. Make sure you have specified
the main policy of your authorization domain is in the alfa block in build.grade. Any test that is not targeting this policy will be ignored as well as any attribute connector tests.

build.gradle
alfa {
mainpolicy "acme.Main"
}

When executing the gradle task, all tests that are marked to be run with no main policy or marked to be run with the main policy specified the in alfa block above, will be executed. Other tests will be skipped (ignored).

Remote
[user@machine project]# ./gradlew test_ProdEnvironment

com.myorg.alfa.MyAttributeconnectorTest > shouldGetRoleConsultantForCecilia SKIPPED

com.myorg.alfa.MyAttributeconnectorTest > shouldGetRoleManagerForMartin SKIPPED

com.myorg.alfa.MyConsultantPolicyUnitTest > shouldPermitIfUserAndResourceAreInSameLocation SKIPPED

com.myorg.alfa.MyConsultantPolicyUnitTest > shouldNotPermitIfUserAndResourceAreInDifferentLocation SKIPPED

com.myorg.alfa.MySystemTest > shouldGiveCeciliaAccessToResource1 PASSED

com.myorg.alfa.MySystemTest > shouldGiveMartinAccessToResource1 PASSED

com.myorg.alfa.MySystemTest > shouldNotGiveCeciliaAccessToResource2 PASSED

Environment variables for controlling remote test execution

Environment variableDescriptionExample
ALFA_TEST_REMOTE_URLThe url to a XACML Rest Version 1.1 servicehttp://127.0.0.1:8081/authorize
ALFA_TEST_REMOTE_USERThe username for authenticationpdp-user
ALFA_TEST_REMOTE_PASSWORDThe password for authenticationsecret
ALFA_TEST_REMOTE_HEADER_FILEA local json file with extra headersSee Extra headers.

Extra headers

In case the test client needs to send extra headers to the external service (for example if there is an intermediate API gateway), these headers can be specified in a separate JSON file and the location of the file can be set in the environment variable (ALFA_TEST_REMOTE_HEADER_FILE). Variable substitution can be used in this file to externalize configuration to the environment.

headers.json
{
"Target-Environment": "PROD",
"Secret-Header": "${SECRET_VALUE}"
}

Variable substitution

Just as Axiomatics Access Decision Service (ADS), the testing framework can do variable substation in policies, attribute connectors, test data and other configuration. Also, a default value can be provided. Examples, with and without default values:

 ${PIP_URL}
${PIP_URL:-http://ldap.myorganization.com}

Variables can be used in the YAML files inside your authorization domain. You typically would use environment variables when you have configuration that differs between environments (such as Dev and Prod). Example to set logging level to INFO by default, but allowing debug level to be enabled in Dev environemnt.

deployment.yaml
...
logging:
level: ${LOGLEVEL:-INFO}
...

Defining variables

The values for variables can be set in two ways. Typically, if the variable is not sensitive you can set the value directly it in the build.gradle script and if it is sensitive you should carry it over from the source environment.

Running unit tests

Inside the test {} block:

test {
environment "LDAP_PIP_URL", "http://ldap-dev.myorg.com"
}

Running test against a specific ADS:

Inside the task definition:

task test_DevEnv(type: Test) {
group "verification"
environment "ALFA_TEST_REMOTE_URL", "http://127.0.0.1:53491/authorize"
environment "ALFA_TEST_REMOTE_USER", "pdp-user"
environment "ALFA_TEST_REMOTE_PASSWORD", "secret"
}

Running a local instance of ADS

When starting a local ADS instance:

runAds {
environment "LOGLEVEL", "debug"
}

Carry over environment variables from source environment

To get the value of source environment variable when executing a task use the following call. The source environment is the host where gradle is running (your laptop or build server):

providers.environmentVariable("LDAP_PIP_PASSWORD").get()

To carry over an environment variable from the source environment to a task you can use

build.gradle
 def  KEY_PIP_LDAP_PASSWORD = 'PIP_LDAP_PASSWORD'
test {
environment KEY_PIP_LDAP_PASSWORD, providers.environmentVariable(KEY_PIP_LDAP_PASSWORD).get()
}
note

The source environment is either your local IDE or, when building on a build server, your Jenkins. With carry over environment variables you can store sensitive credentials in the Jenkins Secrets store.

Variables can also be specified from the command line the standard ways:

linux
$ USER_WITH_ROLE_A=Bob PIP_PASSWORD=abc123 PIP_URL=http://ldapprod.acme.com ./gradlew test_ProductionEnv
windows
set USER_WITH_ROLE_A=Bob
set PIP_PASSWORD=abc123
set PIP_URL=http://ldapprod.acme.com
gradlew test_ProductionEnv

Variables in test data

Variables can also be used in the java tests to parametrize the data. This is useful if your policy information points (PIPs) does not contain the same data in different environments (dev and prod, for example).

 target.newTestRequest().with("user", "${USER_WITH_SPECIAL_ROLE:-Alice}")

Gradle

Tasks

The following Gradle tasks are available. They can be executed from your IDE, the command line or a build automation tool, such as Jenkins or Azure DevOps.

buildAdsDockerImage
buildAuthzDomain
dockerPrepare
compileAlfa
compileAlfaToPackage
runAds
test

Build docker image - buildAdsDockerImage

A Docker image can be built with task buildAdsDockerImage. The image will be tagged \'\<project-name>:latest\' in the local default registry. To push it to a remote repository, tag it for a remote repository and push it like this:

docker tag my-alfa-project:latest myRemoteRegistry.com/my-alfa-project:1.0
docker push myRemoteRegistry.com/my-alfa-project:1.0

The com.palantir.gradle.docker plugin is used to build the image. The image will be built use the Dockerfile in the project root. You can customize it if you need.

By default, everything needed to run ADS will be included in the docker image. This includes runtime dependencies, attribute connectors, custom code in src/main, license, deployment and domain configuration.

👉 Any extra files needed can be placed in src/extra and they will be available in current directory of the docker image.

Build authorization domain - buildAuthzDomain

Task buildAuthzDomain builds the authorization domain into one YAML file suitable for uploading to ADM or give to ADS. The authorization domain, containing policies and attribute connectors, will be written to build/alfa/ads/domain.yaml.

Compiles ALFA to XACML - compileAlfa

Compiles all ALFA policies to XACML and writes output to build/alfa/domain/xacmlSpecifications

Compiles ALFA to ASM policy package - compileAlfaToPackage

Compiles all ALFA policies to XACML and writes output to build/alfa/domain/package/policy_package.zip as an ASM policy package. This file can be uploaded to Axiomatics Core Services (ASM).

Stage docker build context - dockerPrepare

Stages the context to build/docker.

Run ADS locally - runAds

Run ADS locally with the authorization domain from src/ and the deployment descriptor from deployment.yaml.

Important

In order to run ADS you need a valid software license in license/.

Press Ctrl+C to stop it.

Execute the tests - test

Task test runs all unit, integration and system tests and produce test reports in build/reports/tests/. In the report you can see which tests succeeded or faild and also the output or stacktraces from tests.

Dependency configurations

Dependency configuration in gradle is used to configure the classpath for different tasks. The following configurations can be used:

ConfigurationDescription
pipAny additional attribute connector dependencies. A connector dependencies. Axiomatics Table, SQL, LDAP HTTP, Parser and Constant attribute connectors always available on this configuration.
testImplementationAny additional dependencies for executing tests.
adsCompileAny additional dependencies for compiling.
policyAny additional external ALFA policies
build.gradle
    dependencies {
pip '<group>:<name>:<version>'
}

Debugging

Controlling debug logs

Debug logs when running tests or a local ADS can be enabled or disabled in file src/test/resources/logback-test.xml. Change the log level for the axiomatics logger:

src/test/resources/logback-test.xml
...

<logger name="com.axiomatics" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
</logger>
...

PDP Request and responses

For tests that have been executed against a remote service, the generated JSON reqeusts and received JSON responses can be found in /build/alfa/tests/authorizationDomain/trace directory.

Tracing

For the tests that have executed against local ALFA files, an execution trace file can be found in /build/alfa/tests/authorizationDomain/trace directory. This format is Axiomatics proprietary but can be sent to Axiomatics support for further analysis if needed.

PEPs and sending requests

OpenAPI

Open API (previously known as Swagger Specification) of the API to send authorization requests to the Axiomatics Decision Service or any other PDP can be found at https://github.com/axiomatics/xacml-3.0-authz-service-openapi-spec.

OpenAPI specification can be used to generate clients by Swagger tools or imported directly into Postman for example.

Sample java PEP implementation

An example Java PEP implementation can be found in https://github.com/axiomatics/java-json-pep-sdk.

Reference documentation

The XACML JSON reference documentation from OASIS is available here http://docs.oasis-open.org/xacml/xacml-json-http/v1.0/xacml-json-http-v1.0.html

Axiomatics Service Manager integration

See ADM (Axiomatics Domain Manager) and pipeline integrations.

Custom certificate

If your ASM is running with custom certificate that is not yet in your truststore, you either need to add the certificates to your default truststore or run with a custom truststore:

./gradlew testDev -Djavax.net.ssl.trustStore=truststore.jks -Djavax.net.ssl.trustStorePassword=changeit

Upgrading

If Axiomatics releases new functionality or fixes for the ALFA Framework, upgrading can be done by pulling changes via git from the origin. The following command will be sufficient if the remote origin is set up correctly and there are no conflicts.

$ git pull

Traceability

If your domain source code is tracked by a git repository, the domain.yaml produced will automatically contain domain identity and metadata based on the latest git commit. This is good for traceability, since the domain identity is also written in the audit logs.

Commiting changes
$ git commit -m "ABAC-123: My best policy ever!"
[master 50e57b7] ABAC-123: My best policy ever!
5 files changed, 64 insertions(+), 5 deletions(-)
$ git push
build/alfa/domain/ads/domain.yaml

identity: alfa-test-framework-50e57b7
metadata:
Commit-Message: "ABAC-123: My best policy ever!"
Author: Andreas Sjöholm <andreas@acme.com>
policy:
...

When ADS starts it will output the domain it has loaded:

INFO  [2023-10-02 17:30:56,822] com.axiomatics.audit.ads.admin: Domain with id alfa-test-framework-50e57b7 was loaded

And the evaluation logs will contain the domian identity

ADS evaluation log
<EvaluationEvent>
<GroupId>alfa-test-framework-50e57b7</GroupId>
<GroupVersion>0</GroupVersion>
<Timestamp>2023-10-02T15:32:57.860Z</Timestamp>
<EvaluationTimeMillis>0</EvaluationTimeMillis>
...

Reference and further documentation

Axiomatics product documentation

https://axiomatics.github.io/alfa-vscode-doc - ALFA language documentation.

XACML 3.0 Authorization Service - OpenAPI specification

Appendix Authorization Domain layout

The src/authorizationDomain directory represents an authorization domain and contains files and folders mimicking the structure of the single file YAML domain representation used by ADS (reference).

To each of the sections identity, metadata, attributes, and attributeCache corresponds a file with the same name and with extension .yaml.

To the policy field corresponds a folder with the name alfaSpecifications.

To the attributeConnectors field corresponds a folder with the same name.

If you want to omit a field in the domain, just omit the corresponding file or folder.

Important

Be aware that an empty file will actually produce a non-empty section with the YAML value null, which is not a valid value for any of the sections.

identity.yaml

The content of this file corresponds to the value of the identity field in the single file representation.

This file should contain a single valid YAML string.

metadata.yaml

The contents of this file correspond to the value of the metadata field in the single file representation. For example:

status: under development
createdBy: Bob
approvedBy: Alice

alfaSpecifications/

All regular files in this folder will be read as ALFA policies.

attributes.yaml

The contents of this file correspond to the value of the attributes field in the single file representation. For example:

attributes.yaml
acme.role:
xacmlId: acme.role
category: AccessSubject
datatype: string
acme.resource.identity:
xacmlId: acme.resource.identity
category: Resource
datatype: string

attributeConnectors/connector_1.yaml

The settings for an attribute connector identified as connector_1.

All settings can be included in this file. For example:

attributeConnectors/connector_1.yaml
className: com.axiomatics.acs.plugin.pips.sql.SqlPipModule
provides:
- role
- resourceId
configurationString: |
<cfg:configuration xmlns:cfg='http://www.axiomatics.com/jdbc.config' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:schemaLocation=''>
<cfg:connnection>
<!-- Environment variable substitution with default value. -->
<cfg:url>${MY_DB_URL:-jdbc:h2:mem:ac1}</cfg:url>
<cfg:driver>${MY_DB_DRIVER:-org.h2.Driver}</cfg:driver>
</cfg:connnection>
<cfg:mapping>
<cfg:xacmlAttribute AttributeId='com.acme.user.role' Category='urn:oasis:names:tc:xacml:1.0:subject-category:access-subject' DataType='http://www.w3.org/2001/XMLSchema#string'/>
<cfg:nativeAttribute>
<cfg:sqlType>2004</cfg:sqlType>
<cfg:query>SELECT ROLE FROM EMPLOYEES WHERE EMPLOYEE_ID = ?</cfg:query>
<cfg:key allowMultiple='false' sqlType='12'>
<cfg:xacmlAttribute AttributeId='com.acme.user.employeeId' Category='urn:oasis:names:tc:xacml:1.0:subject-category:access-subject' DataType='http://www.w3.org/2001/XMLSchema#string'/>
</cfg:key>
</cfg:nativeAttribute>
<cfg:uId>region_0</cfg:uId>
</cfg:mapping>
</cfg:configuration>

Alternatively, the value of the configurationString field can be represented as a separate file, as shown below, for cases where the value is large or complex, like when it corresponds to XML content.

attributeConnectors/connector_2.yaml

If this file does not specify a configurationString field its value will be read from a file whose name matches connector_2.configurationString.*, as shown below.

This extra file is optional, but it is an error if more than one file matches the pattern.

attributeConnectors/connector_2.configurationString.xml

The file extension is ignored; so you can use any extension that is convenient to support editing the file.

👉 if there is no corresponding connector_2.yaml file, or the corresponding file does specify a configurationString field, this file will be ignored.

attributeCache.yaml

he contents of this file correspond to the value of the attributeCache field in the single file representation. For example:

attributeCache.yaml
acme.role:
timeToLive: 1 day
maxItems: 1000
acme.resource.identity:
timeToLive: 15 minutes
maxItems: 1000

decisionParameters.yaml

The content of this file corresponds to the value of the decisionParameters field in the single file representation. For example:

decisionParameters.yaml
partialEvaluationThreshold: 3