Introduction
Structured Attributes provide an easy way to group related pieces of data together. This tutorial takes you through the steps required to use Structured Attributes in your ALFA policies in Enforcer.
This tutorial assumes you are familiar with building API projects, and are familiar with Enforcer and ALFA. If you are not familiar with either Enforcer or Alfa, you may find the ALFA Tutorial and Securing APIs beneficial prior to starting this.
Learning objectives:
- Define and use Structured Attributes within ALFA policies
- Implement Attribute Value Providers for Structured Attributes
Scenario
AcmeCorp have a website which is driven by a content management system. Alan and Bob are both users of the system and have differing levels of access. The system requires that users authenticate with 2-factor authentication in order to perform any action.
Access levels are defined by roles, Alan has the editor role while Bob has the writer role. Levels of access for each role are defined in the table below:
Role | Create Document | Edit Document | Publish Document |
---|---|---|---|
writer | X | X | |
editor | X | X |
When a user makes a request to the CMS, the security policy ensures that they have used 2-factor authentication, and that the user's role provides the correct level of access.
The goal of this tutorial is to migrate from a role-based permission model, to a finer-grained permission model using Structured Attributes.
The starter solution
During this tutorial, you will be modifying ALFA policies. We recommend installing an ALFA plugin for your editor; this will colorize and highlight syntax errors in the ALFA.
If you don't have a license key for Enforcer, obtain a demo one by visiting https://identityserver.com/products/enforcer.
To get the starter solution, clone the following GIT repository to your machine:
https://github.com/RockSolidKnowledge/Samples.Enforcer
Go to the StructuredAttributeTutorial/Before
folder and open the StructuredAttributeTutorial.sln
in your development environment.
The code you will be working from has the features described above already implemented. The API functional, but authentication has been stubbed out. In order to keep this tutorial focussed on Structured Attributes, the user details and authentication type will be supplied in the requests rather than coming from an identity management system. Lets look at the code to see how it works.
The CMS controller
The system has a single controller for all requests, the CmsController
which implements three endpoints: Create
, Edit
and Publish
. Enforcer authorization is enabled for each endpoint, with ResourceType
attribute set to document
and Action
attribute set to either create
, edit
or publish
, as appropriate.
Each endpoint takes a request body, containing the User
(one of Alan
or Bob
) and AuthenticationType
(either 2FA
or CredentialOnly
). In a production system, these would be delivered as claims from your identity provider. Doing something as egregious as identifying the user in the request body is solely for demonstration purposes! The CmsRequest
body populates the two policy attribute values, User
and AuthenticationType
.
The Edit
and Publish
endpoints also require a document Id, which is supplied in the route. The Create
endpoint will create incrementing document Ids, starting from 1
.
The relative URLs for the operations are:
* POST /cms/create
* PUT /cms/{documentId}/edit
* PUT /cms/{documentId}/publish
Each endpoint either returns a 200 OK
when the user is authorized, and a 403 Forbidden
when not.
Run the project and verify that these work as expected for both Alan and Bob.
The document store
The system contains a simple service, the DocumentStore
which tracks the Ids of documents created in the system and provides access to a list of the document Ids already created.
Authorization policies
There is a Policy
folder in the solution containing the file policy.alfa
, the contents are as follows:
namespace AcmeCorp {
import Oasis.Functions.*
import Oasis.Attributes.*
attribute AuthenticationType { type=string category=subjectCat id="request.authenticationType" }
attribute User { type=string category=subjectCat id="request.user" }
policy CMS {
apply firstApplicable
target clause ResourceType == "document"
rule mustUse2FA {
deny
condition AuthenticationType != "2FA"
}
rule createDocument {
permit
target clause Action == "create"
condition Role == "writer"
}
rule editDocument {
permit
target clause Action == "edit"
condition Role == "writer" or Role == "editor"
}
rule publish {
permit
target clause Action == "publish"
condition Role == "editor"
}
}
}
The policy applies to all requests that target a ResourceType
of document
. The rule mustUse2FA
ensures that the user must have used 2-factor authentication and then the rules createDocument
, editDocument
and publishDocument
ensure that the access levels for each action are enforced.
Role Attribute Value Provider
There is a PIP
folder in the solution which contains a custom attribute value provider: UserRoleAttributeValueProvider
. The purpose of this attribute value provider is to determine which user is making a request and then provide a value for their Role
attribute, which the authorization policy depends on. The user is already mapped to an attribute from the request body, so this value provider reads that value and outputs a new attribute with the user's role.
See the Custom Attribute Value Providers page if you need a refresher.