Before reading this section, ensure you fully understand the OASIS policy model.
How are policies defined in ALFA?
ALFA is a curly braced based language with simalirities to other curly braced programming languages
namespace AcmeCorp
{
import Oasis.Attributes
policy buildingAccess
{
apply denyOverrides
target clause ResourceType == "door"
rule openMainDoor
{
target clause Resource == "mainDoor" and Action == "open"
permit
condition Subject.Role == "employee" and
CurrentTime > "08:00:00":time and
CurrentTime < "18:00:00":time
}
}
}
ALFA has following similarities to C# and Java:-
- Curly braces used to scope items
- == != used for equality
- && || used for Boolean expressions
- Case sensitive
It's dissimilar to C# and Java in that
- It does not use semi-colons to terminate statements
- 'and' and 'or' keywords provide an alternative to && and || respectively.
At the heart of all policy decisions are rules. A Rule evaluates to one of the following
- Permit
- Deny
- Indeterminate
- Not Applicable
When evaluating a rule, the Policy Decision Point (PDP) first considers any target clause to determine if the rule is relevant for the request in hand. If the target clause evaluates to false, the outcome of the rule is Not Applicable.
If the target clause evaluates to true, the PDP then evaluates the condition. If the condition evaluates to false, the outcome of the rule is not applicable. If the condition evaluates to true, the PDP returns the rule's defined outcome; in the case of the openMainDoor rule below, it is permit.
rule openMainDoor
{
target clause Resource == "mainDoor" and Action == "open"
permit
condition Subject.Role == "employee" and
CurrentTime > "08:00:00":time and
CurrentTime < "18:00:00":time
}
If either the condition or the target clause fails to evaluate, perhaps due to a missing attribute, the outcome of the rule is indeterminate.
Rules reside inside policies. In addition to rules, a policy may provide a target clause. The PDP only considers the rules inside the policy if the target clause is missing or one or more target clause evaluates to true.
Defining a policy that includes two or more rules presents a potential issue. If two rules were to execute and produce different outcomes, the PDP needs to decide which one to use for the policy outcome. To define this behavior, the policy defines a combining algorithm using the apply clause. Here is a buildingAccess policy with a denyOverrides combining algorithm.
namespace AcmeCorp
{
import Oasis.Attributes
policy buildingAccess
{
apply denyOverrides
target clause ResourceType == "door"
rule openMainDoor { ... }
rule lockdown { ... }
}
}
The table below shows the effect of evaluating the buildingAccess policy based on the outcome of the openMainDoor and lockdown rules.
openMainDoor | lockdown | Outcome |
---|---|---|
permit | not applicable | permit |
permit | permit | permit |
permit | deny | deny |
permit | indeterminate | indeterminate |
not applicable | not applicable | not applicable |
not applicable | permit | permit |
not applicable | deny | deny |
not applicable | indeterminate | indeterminate |
deny | not applicable | deny |
deny | permit | deny |
deny | deny | deny |
deny | indeterminate | indetermimate |
indeterminate | not applicable | indeterminate |
indeterminate | permit | indeterminate |
indeterminate | deny | indeterminate |
indeterminate | indeterminate | indeterminate |
Combining policies
Creating a single policy for an entire organization would quickly become unmanageable. Defining policies for specific areas, and then aggregating them into a single overall policy would enable:
- Sharing of policies between applications, for cross-cutting concerns
- Simpler to maintain and manage
This is the role of a policy set.
The policy set global
defined below combines the buildingAccess and finance policies. The PDP considers each policy in turn and uses the combining algorithm to produce the final outcome
policyset global
{
apply denyOverrides
policy buildingAccess
policy finance
}
As per policies, policy sets may also contain a target clause. Here is the finance
policy set that aggregates the purchaseOrders
and payroll
policies. This time the combining algorithm is first applicable which means the first of the policies to produce an outcome that is not Not Applicable is taken forward as the outcome of the policy set.
policyset finance
{
apply firstApplicable
target clause Subject.Role == "finance"
policy purchaseOrders
policy payroll
}
Policy sets may contain other policy sets. They can be referenced as shown above or written inline.
policyset global
{
apply denyOverrides
policy employeeOnlyAccess
policy buildingAccess { ... }
policyset finance { ... }
}
Attributes
Attributes supply the information necessary to drive the evaluation of policies and rules. Attributes do not represent a single value, they represent a bag of values of a defined type. ALFA supports the following attribute types. Learn more about data types in ALFA.
- Integer ( 64 bit )
- Double
- Boolean
- String
- Date
- Time
- DateTime
- Duration
For example, a rule, that only permits users who are a member of a given role, requires access to a string-based attribute that contains all of the user's roles.
The PDP obtains attributes from the PIP (Policy Information Point). The PIP is extensible, allowing developers to define attributes that originate from anywhere, including the inbound request. Enforcer's PIP ships with following AttributeValueProviders:
- Identity Claims
- Current date/time
- Request
The role attribute has a fully qualified name of Oasis.Attributes.Subject.Role
. A condition referencing this attribute is written below.
rule noContractorAccess
{
deny
condition Oasis.Attributes.Subject.Role == "contractor"
}
Having to type the fully qualified name each time would be very tedious. The import statement brings a namespace into scope, allowing the namespace of an attribute to be inferred. To access the role attribute using just Subject.Role
, use the statement import Oasis.Attributes
.
namespace AcmeCorp
{
import Oasis.Attributes
import Enforcer.Attributes
policy secureRoom
{
apply denyOverrides
rule noContractorAccess
{
deny
condition Subject.Role == "contractor"
}
}
}
Alternatively, to access the role attribute using just Role
, use the statement import Oasis.Attributes.Subject
.
Policy sets, policies, and functions can also be bought into scope using the import statement.
Wildcards can be used at the end of a namespace import, to include all sub-namespaces. For instance, import Oasis.Attributes.*
will bring both Oasis.Attributes
and Oasis.Attributes.Subject
into scope.
Defining Attributes in ALFA
Attributes are declared with the following properties:
- Name (also known as Id)
- Category
- Type
The name of the attribute is a unique string within the scope of a category. Categories are strings that define the equivalent of a namespace. For example, a PIP may declare an attribute as follows:
Property | Example value |
---|---|
Name | PurchaseOrderAmount |
Category | urn:AcmeCorp:Finance |
Type | double |
For the PDP to obtain access to this attribute, the ALFA code must define a binding between an ALFA definition and the PIP’s definition. It does this using the category and attribute keywords.
namespace AcmeCorp
{
category financeCat = "urn:AcmeCorp:Finance"
attribute PurchaseOrderValue
{
id = "PurchaseOrderAmount"
category = financeCat
type = double
}
}
Notice that the attribute name in ALFA does not need to be the same as the one defined in the PIP.
There are a set of built-in categories defined by Oasis, which can be used for your own attributes, or as in the case above, you can define your own category.
Category | ALFA identifier |
---|---|
urn:oasis:names:tc:xacml:3.0:attribute-category:resource | resourceCat |
urn:oasis:names:tc:xacml:1.0:subject-category:access-subject | subjectCat |
urn:oasis:names:tc:xacml:3.0:attribute-category:action | actionCat |
urn:oasis:names:tc:xacml:3.0:attribute-category:environment | environmentCat |
- To learn about PIPs supplied with Enforcer click here
- To learn how to create your own PIP click here
Binary operators on attributes
All attributes are multi-valued bags. The fact that they can contain many values raises the question of how the expression below gets evaluated. The left-hand side of the expression may contain many values, and, on the right, there is one.
// Any Subject.Role is equal to Contractor
condition Subject.Role == "contractor"
The expression above evaluates to true if any values of Subject.Role match the literal “contractor”. However, the case below shows both sides of the equality operator being attributes. In this situation, the expression evaluates to true if any value in Subject.Role attribute matches any value in MedicalProfessionalRoles attribute.
// Any Subject.Role is equal to any MedicalProffesionalRoles
condition Subject.Role == MedicalProffessionalRoles
The logic defined above is the same for all the binary operators ( >, >=, <, <=, != ). The above conditional behavior becomes more evident if we express it in pseudo-code.
// Any Subject.Role is equal to Contractor
condition any Subject.Role == "contractor"
// Any Subject.Role is equal to any MedicalProffesionalRoles
condition any Subject.Role == any MedicalProffessionalRoles
The implicit use of the "any" term works well for the above examples; however, consider the following condition
// Any Subject.Role does is not equal to Contractor
condition Subject.Role != "contractor"
If the user had only a single role of employee, this would evaluate to true. If the user had a single role of contractor, it would evaluate to false. However, if the user had two roles of contractor and admin, then it would still evaluate to true; this becomes evident if we use pseudo-code again
// Any Subject.Role does is not equal to Contractor
condition any Subject.Role != "contractor"
To solve this scenario, the all keyword can be used to wrap the attribute. The "all" keyword ensures that all of its values must meet the condition, not just any of its values.
// all Subject.Role values are not equal to Contractor
condition all(Subject.Role) != "contractor"
To confirm that no values in one attribute bag appear in another attribute bag, the "all" keyword is applied to both attributes.
// All Subject.Role does is not equal to all unAuthorizedRoles
condition all(Subject.Role) != all(unAuthorizedRoles)
Functions
Creating target and condition expressions using binary operators and attributes only goes so far. Often there is a need to transform the attribute value to construct the correct condition. Enforcer comes with a range of prebuilt functions to transform attribute values.
With attributes having the potential to contain multiple values, and many operators and functions only working with a single value, the Single function is critical to provide a transformation from attribute bag to a single value. Note that this only succeeds if the attribute bag contains exactly one value
rule allowIfTennant
{
deny
condition not EndsWith("@" + Single(Tenancy), Single(Subject.Email))
}
For scenarios where there is no applicable built-in function, you can create a custom function in .NET and define a binding in ALFA.
Obligations and Advice
In addition to policy sets, policies and rules producing an outcome, they can also generate outcome actions. Outcome actions are either obligations or advice. Obligations refer to actions that must be successfully executed for the PDP outcome to be maintained. Advice are optional actions that do not affect the overall outcome of the policy or rule. Obligations and advice have a name and a set of attributes used to parameterize the behavior of the outcome action.
policyset main
{
apply permitOverrides
policy medicalRecords
policy buildingAccess
on deny
{
advice AuthorizationFailure.ShowAuthorizationFailure
{
AuthorizationFailure.Message = "You have been denied access"
}
}
}
As the PDP executes the tree of policies, many outcome actions may be triggered. The PDP aggregates the outcome actions until the final policy outcome is determined and then the set of collated actions associated with the final outcome are then executed.
policy medicalRecords
{
apply denyOverrides
rule readPatientsRecords
{
permit
target clause ResourceType == "MedicalRecord" and Action=="Read"
condition Subject.Role == "Doctor"
on permit
{
obligation Auditor.RecordAccess
{
Auditor.Who = Subject.Name
Auditor.When = CurrentDateTime
Auditor.Message = "Reading Medical Record " + Single(Resource)
}
}
on deny
{
advice Auditor.Alert
{
Auditor.Who = Subject.Name
Auditor.When = CurrentDateTime
Auditor.Message = "Attempted to access medical records, but was denied"
}
}
}
}
If the medical records policy evaluates to deny, then both the Auditor.Alert
advice and the AuthorizationFailure.ShowAuthorizationFailure
advice are delivered, by the PDP, to the PEP.
- Put this knowledge into action with the ALFA tutorial
- To learn about outcome actions supplied with enforcer click here.
- To learn how to create your own outcome action behavior click here.