-
Notifications
You must be signed in to change notification settings - Fork 0
Scenario Access Control
Controlling access to backend actions and front end user interface elements is an important aspect of enterprise web application development. This guide will discuss access control mechanisms in Agent and demonstrate how developers can use them.
Privileges are controlled in Clarify by Privilege Classes. In Dovetail applications we have we have created smaller units of access called web_cmds
I'l be referring to these as Privileges. Each Privilege Class has many Privileges represented by a string e.g. "EDIT CASE".
If you change a user's Privilege Class or add/remove Privileges from their Privilege Class that user will need to logout and log back in to see changes. You can also reload the web application.
To make Dovetail's concept of Privileges mesh with ASP.Net. We copy all privileges as roles into the HttpContext's current user's identity. This allows you to use ASP.Net role authorization mechanisms to control access to routes in the Web.config.
<location path="_fubu" allowOverride="true">
<system.web>
<authorization>
<allow roles="Admin" />
<deny users="*" />
</authorization>
</system.web>
</location>
This snippet from the web.config
prevents non-administrators from seeing the web application diagnostics.
Actions in the web application can support authorization roles. You can either Allow or Deny access to an action, an thus a URL endpoint, using an attribute.
[AllowRole(new[] { "Admin" })]
public ShowAdminModel get_core_admin(ShowAdminRequest request)
{
//....
}
Here we only allow access to administrators for the route /core/admin
.
If you have a need for more advanced backend authorization. FubuMVC has support for this. Here is an example for a selfservice application which only allows users to see cases for which they are the contact.
public class ContactCaseAuthorizationPolicy : IAuthorizationPolicy
{
private readonly ICurrentUser _currentUser;
private readonly IModelBuilder<Case> _caseBuilder;
public ContactCaseAuthorizationPolicy(ICurrentUser currentUser, IModelBuilder<Case> caseBuilder)
{
_currentUser = currentUser;
_caseBuilder = caseBuilder;
}
public AuthorizationRight RightsFor(IFubuRequest request)
{
var caseAction = request.Get<EntityAction>();
var @case = _caseBuilder.GetOne(caseAction.Id);
if (@case == null)
{
//It is OK to allow access to a case that doesn't exist.
// The action will return 404 for this scenario.
return AuthorizationRight.Allow;
}
return @case.Contact.DatabaseIdentifier != _currentUser.Contact.DatabaseIdentifier ? AuthorizationRight.Deny : AuthorizationRight.Allow;
}
}
This example makes sure that the contact is the contact of the case they are trying to view.
You'll need to add this authorization to the appropriate action by using this attribute:
[AuthorizedBy(typeof(ContactCaseAuthorizationPolicy))]
public Domain.Case get_case_Id(ShowCaseRequest request)
{
return _builder.GetOne(request.Id);
}
If you need to manually check on the backend if a user has a particular privilege you can take a dependency on ICurrentSDKUser
and use it's HasPermission
method. Take a look at the
public class SimpleYakValidator
{
private readonly ICurrentSDKUser _currentUser;
public SimpleYakValidator(ICurrentSDKUser currentUser)
{
_currentUser = currentUser;
}
public bool CanShaveYak(Yak yak)
{
return _currentUser.HasPermission("Shaver");
}
}
This class checks the current user to see if they have permission to Shave Yaks.
Often, you need to do more than just check for a privilege. Maybe, you also need to validate the object is in the correct state to do an action. The following pattern is used by Agent to do these types of validations.
In this Yak Shaving Validator example we check that the user has the proper privilege for the action and that the object is ready for the action.
public class YakValidator
{
private readonly ICurrentSDKUser _currentUser;
private readonly List<StringToken> _errors = new List<StringToken>();
public YakValidator(ICurrentSDKUser currentUser)
{
_currentUser = currentUser;
}
public bool CanShaveYak(Yak yak)
{
return validateAction(() =>
{
MustBeHairy(yak);
UserMustHavePermission("Shaver");
});
}
protected void UserMustHavePermission(string permissionName)
{
if (_currentUser.HasPermission(permissionName) == false)
{
addError(UserMessageKeys.USER_DOES_NOT_HAVE_REQUIRED_PERMISSION);
}
}
protected void MustBeHairy(Yak yak)
{
if (!yak.IsHairy)
{
addError(StringToken.FromKeyString("YAK_IS_NOT_HAIRY", "You cannot shave a yak that is not yet hairy."));
}
}
public StringToken[] ValidationErrors
{
get { return _errors.ToArray(); }
}
protected void addError(StringToken errorToken)
{
_errors.Add(errorToken);
}
protected bool validateAction(Action actionValidator)
{
_errors.Clear();
actionValidator();
return _errors.Count < 1;
}
}
Putting the validation actions into separate methods fosters reuse for other validations. When the validation fails the validator can be inspected for error messages which can be easily relayed to the front end.
We've covered ways to determine on the back end if the user has the desired permissions. Now let's look at the front end.
If your Javascript module takes a dependency on app/core/user
you can ask the resulting user model if the current user hasPermission
define([
'app/core/user'
],
function (user) {
if(user.hasPermission('Admin')) {
alert('You are an administrator!')
}
});
We have some permission support in our in handlebars templates. If your Backbone Marionette includes the AllowRoles Behavior your permissions can be declared on elements which will be disabled unless the user has the proper permission.
{{ selectFor "supervisor" permission="Edit Employee" selectClass="not-single"}}
{{ dateFor "startDate" permission="Edit Employee"}}
These two edit controls (rendered using helpers) require Edit Employee permission to be present.
If you want to do the same thing the helpers do but manually you can add a data attribute to a disable-able element
<button data-privilegesRequired="Edit Employee, Admin">Edit</button>
This button will be disabled unless the user has all required privileges.
We hope you enjoyed this helpful Agent training document. If you see an error or omission please feel free to fork this repo to correct the change, and submit a pull request. Any questions? Contact Support.