Structured attributes
Enforcer 5.0 introduces structured attributes to ALFA. Structured attributes allow for related data to be grouped together and consumed by policies in a manner similar to classes in object oriented languages.
To use structured attributes, it is assumed you have an understanding of ALFA, and implementing custom attribute value providers.
Defining structured attributes
Attribute declarations have a type, and structured attributes have a structured type. Structured types are defined using the type
keyword, followed by a name and a list of fields, each field consisting of a name and a data type.
The alfa below defines a type called Permission, which groups a resource with the actions permitted on that resource.
namespace AcmeCorp {
type Permission {
Resource:string
Action:string
}
}
Once a structured type has been declared it can be used in attribute declarations, and used throughout your policies.
attribute DoorAccessPermission {
type=Permission
category=resourceCat
}
Using structured attributes
In ALFA, all attributes are bags of values. The same holds true with structured attributes. The DoorAccessPermission
defined above may have many permission values at runtime. Additionally, each field on the permission is also a bag which may contain multiple values.
In the following examples, consider that the values at runtime for the DoorAccessPermission are as follows:
{
{
"Resource": ["MainDoor"],
"Action": ["Open", "Close", "Lock", "Unlock"]
},
{
"Resource": ["FireExit"],
"Action": ["Open", "Close"]
}
}
Structured attributes can be used in conditions. The simplest form is directly accessing a single field on the attribute using the .
operator.
// Within a policy or rule
condition AcmeCorp.DoorAccessPermission.Resource == "MainDoor"
The above condition evaluates to true
if any Resource
value of any of the DoorAccessPermissions
attribute has the value MainDoor
.
In most cases you will want to verify multiple fields on the attribute at the same time, for instance: If there is a permission for the fire exit, is the user allowed to lock the door?
Using the above property access method, you may (incorrectly) write a condition as follows:
// Within a policy or rule
condition AcmeCorp.DoorAccessPermission.Resource == "FireExit" and
AcmeCorp.DoorAccessPermission.Action == "Lock"
This condition will be true
if there is any permission with FireExit
as the resource and any permission with Lock
as the action. Not necessarily the same permission!
To test if there is a permission for the fire exit and that permission for the fire exit has Lock
as a permitted action, you must first filter the structured attribute to narrow the scope of the condition. Filters appear within square braces after the attribute name.
E.g.
condition AcmeCorp.DoorAccessPermission[Resource == "FireExit"].Action == "Lock"
The condition here limits the scope of the field access to only those actions where the resource is FireExit
, and so only evaluates true
if the permission has both of those values.
Note: If there is a namespace clash between a field name and another attribute in scope, the field always takes precedence within a structured attribute filter. To access another attribute with the same name, use a fully qualified identifier.
namespace AcmeCorp {
import Oasis.Attributes.*
type Permission {
Resource:string
Action:string
}
attribute DoorAccessPermission {
type=Permission
category=resourceCat
}
condition DoorAccessPermission[Resource == Oasis.Attributes.Resource].Action == Action
}
Attribute values
Values for structured attributes are delivered using the IAttributeValueProvider
infrastructure in Enforcer. The recommended approach is to use a RecordAttributeValueProvider
, with a property on your record type be a class which models your structured attribute. See the documentation page on Custom Attribute Value Providers for the funamentals on this topic.
In the door permissions example above, you would need a class to represent the Permission type. This type should derive from Rsk.Enforcer.PolicyModels.StructuredAttributes.StructuredAttribute<T>
for .Net or Netstandard projects, or Rsk.Enforcer.PolicyModels.StructuredAttributes.StructuredAttribute
for .Net Framework projects.
class Permission : StructuredAttribute<Permission>
{
public string Resource { get; set; }
public string Action { get; set; }
}
This class can then be used for a property on the record type when implementing a RecordAttributeValueProvider
.
class PermissionRecord
{
[PolicyAttributeValue(PolicyAttributeCategories.Resource, "AcmeCorp.DoorAccessPermission")]
public Permission DoorAccessPermission { get; set; }
}
class PermissionAttributeValueProvider : RecordAttributeValueProvider<PermissionRecord>
{
protected override async Task<PermissionRecord> GetRecordValue(IAttributeResolver attributeResolver, CancellationToken ct)
{
// Obtain values for the attribute
}
}
Limitations
At present the following use cases for structured attributes are on the roadmap but are currently not supported:
- As arguments or return types in functions
- With obligations or advice
- As literal values in comparison expressions
If you have a need for any of the above, we would love to hear from you.