Adding Advice
In this part of the tutorial, you will add specific authorization failure messages. The failure messages will originate from the ALFA, allowing them to be very precise in why the PEP is denying a request. The failure messages will be delivered using Advice. Your application can pre-register Advice handlers with the PEP, or the PEP returns any unhandled advice as part of the Evaluate method.
- To issue advice from a rule or policy, you will first define the advice identifier and any attributes required to deliver the advice. Create an advice.alfa file inside the policies folder and place the following ALFA inside the file.
namespace AcmeCorp.Advice
{
category adviceCat = "Advice"
attribute AuthorizeFailureMessage
{
id = "AuthorizationFailureMessage"
category = adviceCat
type = string
}
advice AuthorizationFailure = "AuthorizationFailure"
}
- To make use of the advice in the Finance.alfa file, add the import statement.
import AcmeCorp.Advice
- Now update the
RestrictEdit
rule to issue the appropriate advice when the rule denies.
rule RestrictEdit
{
deny
target clause Action == 'Edit' or Action == 'Update'
condition not (Subject.Role == 'manager' and PurchaseOrderDepartment == Subject.Department )
on deny
{
advice AuthorizationFailure
{
AuthorizeFailureMessage = "Managers can only edit their own department POs this PO is for " + Single(PurchaseOrderDepartment)
}
}
}
- To make use of the
Single
function you will need to import the Oasis.Functions namespace.
import Oasis.Functions
- Re-running the application will have no effect since no code has been included in the application to handle this type of advice. Add a new class to the Models folder called AuthorizationFailureAdvice. This class will contain properties to be bound to attributes associated with the AuthorizationFailure advice.
[EnforcerAdvice("AuthorizationFailure")]
public class AuthorizationFailureAdvice
{
[PolicyAttribute("Advice","AuthorizationFailureMessage")]
public string Message { get; set; }
}
- Now update the AuthorizationFailed method on the PurchaseOrdersController to locate the AuthorizationFailure advice. To obtain the advice use the Find method on the UnresolvedAdvice property from the PolicyEvalutionOutcome. The Find method has a generic type argument representing the type to map the advice attributes into. The EnforcerAdvice attribute associated with the class identifies the piece of advice.
- Use the returned advice as the model for the NotAuthorizedView. Note that the Find method can return many matching advice, so the View takes a model of IEnumerable
.
private IActionResult AuthorizationFailed(PolicyEvaluationOutcome po)
{
IEnumerable<AuthorizationFailureAdvice>failureReason =
po.UnresolvedAdvice.Find<AuthorizationFailureAdvice>();
var view = View("NotAuthorized", failureReason);
view.StatusCode = (int) HttpStatusCode.Forbidden;
return view;
}
- Update the NotAuthorized view to use this new model.
@model IEnumerable<AuthorizationFailureAdvice>
@{
ViewBag.Title = "title";
Layout = "_Layout";
}
<h2>Not Authorized</h2>
@foreach (var reason in Model)
{
@reason.Message
}
- Re-run the application, and perform the following actions to verify that you receive the correct authorization failure message
- Login as bob.
- Create a purchase order for some pencils at £20.
- Attempt to edit the pencils.
- To handle any unresolved advice when using the EnforcerAuthorizationAttribute you will now add a global handler for this type of advice.
services.AddEnforcer( . . .)
. . .
.AddEnforcerAuthorizationDenyHandler<AuthorizationFailureAdvice>("~/Views/Shared/NotAuthorized.cshtml");
- The AddEnforcerAuthorizationDenyHandler takes a generic argument indicating the .NET type for the model and the method argument indicating which view binds to the model.
- Add additional pieces of advice for the other rules and verify you now get authorization failure messages.