Richer Authorization Context
In this part of the tutorial, you will add security to ensure that a user can only update a purchase order generated by someone in their department. For the PDP to perform this restriction, it requires both the department of the current user and the department associated with the Purchase Order. Using the EnforcerAuthorization Attribute to protect the methods results in an initial authorization context that includes just the ResourceType and Action. The Claims attribute value provider provides the current user's department, but you are still missing the department associated with the purchase order.
You could build an attribute value provider to determine this information based on ResourceType and Resource. However, since the controller's business logic will also need to locate the purchase order to perform its action, it would be more efficient to have the controller build a richer authorization context. For this part of the tutorial, you will not use the EnforcerAuthorization attribute but invoke the PEP from inside the controller method. Invoking the PEP inside the controller method will allow you to provide a richer initial authorization context.
- To replace the EnforceAuthorization Attribute for authorization, you will directly use the PEP from inside the controller action. Inject the PEP into the controller via the constructor.
private readonly IPolicyEnforcementPoint pep;
public PurchaseOrdersController(IManagePurchaseOrders purchaseOrders,
IPolicyEnforcementPoint pep)
{
this.purchaseOrders = purchaseOrders;
this.pep = pep;
}
- You will now need to invoke the PEP from the Edit action methods inside the controller to determine if a user can edit the purchase order. Invoking the PEP Evaluate method to obtain the authorization decision requires you to supply the initial authorization context. Initially, set the context to null.
[HttpGet]
[Route("/PurchaseOrders/{id}", Name = "Edit")]
public async Task<IActionResult> Edit(int id)
{
var po = purchaseOrders.FindById(id);
IAttributeValueProvider context = null;
var authorizationResult = await pep.Evaluate(context);
if (authorizationResult.Outcome != PolicyOutcome.Permit)
{
return AuthorizationFailed(authorizationResult);
}
return View("PurchaseOrder", po);
}
private IActionResult AuthorizationFailed(PolicyEvaluationOutcome po)
{
var view = View("NotAuthorized", new List<string> {$"You are not authorized to Edit PO "});
view.StatusCode = (int) HttpStatusCode.Forbidden;
return view;
}
- To obtain access to the user's department claim from the policy, you will need to create a department attribute. A set of predefined claims already exist under the namespace
Oasis.Attributes.Subject
, so it makes sense to add the department attribute to the same namespace. Add a new file in the policies folder called Subject.alfa, and add the following attribute definition.
namespace Oasis.Attributes.Subject
{
attribute Department
{
id = "department"
category = subjectCat
type = string
}
}
- Inside finance.alfa, add an attribute to represent the department associated with the purchase order, ensuring it's inside the
AcmeCorp.Finance
namespace.
attribute PurchaseOrderDepartment
{
id = "PurchaseOrderDepartment"
category = resourceCat
type = string
}
- Now create a new rule inside the PurchaseOrder policy to only allow the editing of purchase orders by the purchase orders’ department manager.
rule RestrictEdit
{
deny
target clause Action == 'Edit'
condition not (Subject.Role == 'manager' and PurchaseOrderDepartment == Subject.Department )
}
- Now you will need to modify the Get and Post Edit Action on the PurchaseOrdersController to supply an authorization context that includes the department associated with the purchase order the user has requested to edit. There are multiple ways to provide the context:
DynamicAttributeValueProvider
DelegatingRecordAttributeValueProvider
AuthorizationContext<T>
- Own custom type that implements
IAttributeValueProvider
In this scenario, you will use theAuthorizationContext<T>
base class. Define a class in the models folder that represents the request context that derives from theAuthorizationContext<T>
.
public class EditPurchaseOrderAuthorizationContext : AuthorizationContext<EditPurchaseOrderAuthorizationContext>
{
public EditPurchaseOrderAuthorizationContext(string action) :base("PurchaseOrder",action)
{
}
[PolicyAttributeValue(PolicyAttributeCategories.Resource,"PurchaseOrderDepartment")]
public string PurchaseOrderDepartment { get; set; }
[PolicyAttributeValue(PolicyAttributeCategories.Action,"PurchaseOrderTotal")]
public double? PurchaseOrderAmount { get; set; }
}
- Now create an instance of the EditPurchaseOrderAuthorizationContext and assign it to the context variable inside the first Edit method.
IAttributeValueProvider context = new EditPurchaseOrderAuthorizationContext("Edit")
{
PurchaseOrderDepartment = po.Department
};
- Re-run the application; you will now only be able to edit purchase orders if you are a manager of the department the purchase order was created for. Try the following:
- Create a purchase order as Bob.
- Attempt to edit as Bob, and it will be denied.
- Attempt to edit as Alice, and it will be sucesful
- Attempt to edit as Sally, and it will be denied.
- Update the Post version of the Edit action to use a similar authorization logic. Note to prevent an updated purchase order exceeding its department's limits. You will need to:
- Create the same authorization context but with an action called "Update", via the constructor.
- Set the PurchaseOrderAmount from the inbound request.
- Update the
RestrictCreation
andRestrictEditRule
to also consider the Update action.