The STAPL policy language for tree-structured, attribute-based access control policies
STAPL or the Simple Tree-structured Attribute-based Policy Language is a Scala DSL designed to express and evaluate XACML-like policies easily. STAPL provides a human-friendly policy syntax and an efficient evaluation engine that is easy to test and use in Scala and Java applications.
For quickly getting started with STAPL, check out the Quick Start below.
This repository hosts stapl-core
. Some other projects extend the core:
Here is a simple example of a STAPL policy based on an e-health scenario:
package mypackage
import stapl.core._
object PolicyFromTheReadMe extends BasicPolicy {
import stapl.core.dsl._
subject.roles = ListAttribute(String)
subject.treated = ListAttribute(String)
resource.type_ = SimpleAttribute(String)
resource.owner_id = SimpleAttribute(String)
// action.id is defined by STAPL itself
// Permit only if the physician treated the owner of the patient data.
val policy = Policy("e-health example") := when ((action.id === "view") & (resource.type_ === "patient-data")
& ("physician" in subject.roles)) apply PermitOverrides to (
Rule("requirement-for-permit") := permit iff (resource.owner_id in subject.treated),
Rule("default deny") := deny
)
}
Quite consice and readable, no? As comparison, here is the XACML representation of this same policy:
<Policy PolicyId="e-health example"
RuleCombiningAlgId="urn:oasis:names:tc:xacml:1.0:rule-combining-algorithm:permit-overrides">
<Description>Permit only if the physician treated the owner of the patient data.</Description>
<Target>
<Actions>
<Action>
<ActionMatch MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal">
<AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">view</AttributeValue>
<ActionAttributeDesignator AttributeId="action:id" DataType="http://www.w3.org/2001/XMLSchema#string"/>
</ActionMatch>
</Action>
</Actions>
<Resources>
<Resource>
<ResourceMatch MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal">
<AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">patient-data</AttributeValue>
<ResourceAttributeDesignator AttributeId="resource:type" DataType="http://www.w3.org/2001/XMLSchema#string"/>
</ResourceMatch>
</Resource>
</Resources>
<Subjects>
<Subject>
<SubjectMatch MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal">
<AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">physician</AttributeValue>
<SubjectAttributeDesignator AttributeId="subject:roles" DataType="http://www.w3.org/2001/XMLSchema#string"/>
</SubjectMatch>
</Subject>
</Subjects>
</Target>
<Rule RuleId="requirement-for-permit" Effect="Permit">
<Description>Permit if the physician treated the owner of the patient data.</Description>
<Condition>
<Apply FunctionId="urn:oasis:names:tc:xacml:1.0:function:string-is-in">
<Apply FunctionId="urn:oasis:names:tc:xacml:1.0:function:string-one-and-only">
<ResourceAttributeDesignator AttributeId="resource:owner:id" DataType="http://www.w3.org/2001/XMLSchema#string"/>
</Apply>
<SubjectAttributeDesignator AttributeId="subject:treated" DataType="http://www.w3.org/2001/XMLSchema#string"/>
</Apply>
</Condition>
</Rule>
<Rule RuleId="deny" Effect="Deny">
<Description>Deny otherwise</Description>
</Rule>
</Policy>
For more examples, see the stapl-examples project.
No more need for XML copy-pasting.
These allow you to build large policies modularly. If you want, you can even use the Scala package system to structure large policy sets.
These allow you increase modularity even more by encapsulating common policy patterns.
STAPL policies can be compiled just as any other Scala and Java code. However, your application should not be recompiled for every policy change. Therefore, STAPL allows you to load policies at run-time. If you would like to know, this is done internally using the Scala interpreter.
Compared to our optimized version of the SunXACML engine, STAPL policies can be evaluated multiple times faster than their XACML equivalents.
Wondering why a certain decision is Permit or Deny? Turn on logging and get an overview of the whole policy evaluation process.
Towards the future, we plan to add the following features.
The STAPL evaluation engine can be compiled directly into your Scala or Java application. However, for other languages, we plan to allow the engine to be deployed stand-alone and reached using Apache Thrift.
To get an access control decision, simply load the policy in the policy decision point (PDP) and ask for a decision:
import mypackage.PolicyFromTheReadMe.policy
val pdp = new PDP(policy)
pdp.evaluate("john", "view", "doc123",
subject.roles -> List("physician"),
subject.treated -> List("patient1", "patient2"),
resource.type_ -> "patient-data",
resource.owner_id -> "patient3") // so this will result in a Deny
}
If you cannot provide all attributes with the decision request or want to dynamically fetch attributes from a database, write your own AttributeFinderModule and add it to the PDP:
class MyAttributeFinderModule extends AttributeFinderModule {
override def find(ctx: EvaluationCtx, cType: AttributeContainerType, name: String, aType: AttributeType):
// fetch the attribute from the database and return it
...
}
val finder = new AttributeFinder
finder += new MyAttributeFinderModule
val pdp = new PDP(policy, finder)
Because STAPL is implemented in Scala, you're best off with writing your policies in Scala. However, the policy evaluation engine can easily be used from Java as well. In theory, Scala classes can directly be used in Java. In practice however, this leads to some annoying boiler-plate code. Therefore we also provide a simple API that wraps the STAPL evaluation engine in a clean Java API, see https://github.com/maartendecat/stapl-java-api.
Here is an example of using the Java API (taken from the examples):
package stapl.examples.javaapi;
import static stapl.javaapi.pdp.attributevalues.AttributeValueFactory.list;
import static stapl.javaapi.pdp.attributevalues.AttributeValueFactory.simple;
import stapl.core.AttributeContainer;
import stapl.core.Policy;
import stapl.core.Result;
import stapl.examples.policies.EdocsPolicy;
import stapl.javaapi.pdp.PDP;
public class SimpleExample {
public static void main(String[] args) {
/* Java imitation of the following Scala code:
println(pdp.evaluate("subject1", "view", "resource1",
subject.role -> List("helpdesk"),
subject.tenant_name -> List("provider"),
subject.tenant_type -> List("provider"),
subject.assigned_tenants -> List("tenant1","tenant3"),
resource.type_ -> "document",
resource.owning_tenant -> "tenant4",
resource.confidential -> false))
*/
Policy policy = EdocsPolicy.policy();
AttributeContainer subject = EdocsPolicy.subject();
AttributeContainer resource = EdocsPolicy.resource();
AttributeContainer action = EdocsPolicy.action();
AttributeContainer env = EdocsPolicy.environment();
PDP pdp = new PDP(policy);
Result result = pdp.evaluate("subject1", "view", "resource1",
list(subject.get("role"), "helpdesk"),
list(subject.get("tenant_name"), "provider"),
list(subject.get("tenant_type"), "provider"),
list(subject.get("assigned_tenants"), "tenant1", "tenant2"),
simple(resource.get("type_"), "document"),
simple(resource.get("owning_tenant"), "tenant4"),
simple(resource.get("confidential"), false));
System.out.println(result);
}
}
For more examples, check the package stapl.javaapi.examples
.
Using the interface to the policy engine shown above, STAPL policies can easily be tested for correctness in JUnit unit tests:
assert(pdp.evaluate("subject1", "view", "resource1",
... // any attributes for the test
) === Result(Permit, List(
... // any obligations
)
))
For elaborate examples of policy testing, see the tests of stapl-examples.
Wondering why a certain decision is Permit or Deny? STAPL can provide you with a detailed overview of the whole policy evaluation process. For example, for the policy above, this could be the logging output:
09:36:46.191 [main] DEBUG stapl.core.Policy - FLOW: starting evaluation of PolicySet #e-health example
09:36:46.194 [main] DEBUG stapl.core.pdp.BasicEvaluationCtx - FLOW: found value of SimpleAttribute(ACTION,id,String) in cache: view
09:36:46.194 [main] DEBUG stapl.core.pdp.BasicEvaluationCtx - FLOW: found value of SimpleAttribute(RESOURCE,type_,String) in cache: patient-data
09:36:46.194 [main] DEBUG stapl.core.pdp.BasicEvaluationCtx - FLOW: found value of ListAttribute(SUBJECT,roles,String) in cache: List(physician)
09:36:46.196 [main] DEBUG stapl.core.Rule - FLOW: starting evaluation of Policy #e-health example>requirement-for-permit
09:36:46.196 [main] DEBUG stapl.core.pdp.BasicEvaluationCtx - FLOW: found value of SimpleAttribute(RESOURCE,owner_id,String) in cache: patientX
09:36:46.196 [main] DEBUG stapl.core.pdp.BasicEvaluationCtx - FLOW: found value of ListAttribute(SUBJECT,treated,String) in cache: List(patientX)
09:36:46.196 [main] DEBUG stapl.core.Rule - FLOW: Policy #e-health example>requirement-for-permit returned Permit with obligations List()
09:36:46.198 [main] DEBUG stapl.core.Policy - FLOW: PolicySet #e-health example returned Result(Permit,List())
By default, logging is turned off. To turn it on, edit the file stapl-core/src/main/resources/logback.xml
by changing the line
<logger name="stapl.core" level="OFF"/>
to
<logger name="stapl.core" level="DEBUG"/>
Here are a few simple steps you can follow to get started with STAPL. If you want to skip even this explanation and want to dive right into STAPL, the stapl-getting-started project contains the resulting Maven project, just git clone
it.
STAPL was built using Scala 2.10. How to install Scala depends on your operating system. For Fedora:
> sudo yum install scala
If you don't know where to look, try http://scala-ide.org/.
> git clone https://github.com/stapl-dsl/stapl-core
> cd stapl-core
> mvn install
In the Scala IDE: Create a new Maven project with archetype scala-archetype-simple
of group net.alchim31.maven
. For more information: http://scala-ide.org/docs/tutorials/m2eclipse/
Add the following lines to the <dependencies>
section in your pom.xml
:
<dependency>
<groupId>stapl</groupId>
<artifactId>stapl-core</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
The simplest way to create a new policy is to declare a Scala object
and assign the policy to a val
. This way, you can easily access the policy from other parts of your code, such as the policy decision point.
For example, add this simple policy to the end of the App.scala
file generated by the Scala IDE:
import stapl.core._
object ExamplePolicy extends BasicPolicy {
import stapl.core.dsl._
subject.roles = ListAttribute(String)
val policy = Policy("example policy") := when (action.id === "view") apply PermitOverrides to (
Rule("permit physicians") := permit iff ("physician" in subject.roles),
Rule("default deny") := deny
)
}
Explanation:
stapl.core._
imports the main part of STAPL, such as the Policy
and Rule
classes and the implicit conversions of strings and ints to STAPL attributes.BasicPolicy
class is a convenience class that avoids some boiler-plate code in your own policies. For example, by extending it, you automatically have subject
, resource
, action
and environment
declared in your scope.subject.roles = ListAttribute(String)
declares that subjects have an attribute called Roles
, that is is multi-valued and that is is of the type String
. After this declaration, you can use subject.roles
in your policies.view
, permits physicians using the role
attribute and denies all other subjects.val
is required to be able to access the policy from another part of your code, as shown in the example below.Now that we have declared a new policy, you can use it by importing it, putting it in a policy decision point (PDP) and request an access decision.
For example, let's test the policy in the main
method generated by the Scala IDE. First, add the following import to the top of App.scala
(but below the package
declaration):
import stapl.core.pdp._
Then change the main method of object App
to the following:
def main(args : Array[String]) {
import ExamplePolicy._
val pdp = new PDP(policy)
println(pdp.evaluate("subject1", "view", "resource1",
subject.roles -> List("physician"))) // will return Permit
println(pdp.evaluate("subject1", "view", "resource1",
subject.roles -> List("another role"))) // will return Deny
println(pdp.evaluate("subject1", "another action", "resource1",
subject.roles -> List("physician"))) // will return NotApplicable
}
Explanation:
ExamplePolicy
into the current scope. Most importantly, this includes subject,
resource
, action
, env
and policy
.view
action and the role physician
. The second evaluation will return Deny because of the wrong role. The third evaluation will return NotApplicable because of the incorrect action id.Some important notes here:
AttributeFinder
s. These can be used to allow the PDP to search for attributes you did not pass with the request at run-time, for example in a database. For more information, take a look above.stapl.core.examples
.Try out some stuff yourself. If you want inspiration, take a look at the more elaborate examples in stapl-examples. Also take a look at the policy templates in stapl-templates, they make it even easier for you to specify policies.