Skip to content

Scenario Access Control

Craig Jennings edited this page Jul 10, 2018 · 3 revisions

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

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.

ASP.Net Roles

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.

Allow/Deny Role

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.

Fubu Authorization

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);
}

Backend Privilege Checks

Current User

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.

Creating a Validator

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.

Front End Privilege Checks

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.

User hasPermission

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!')
  }
});

Allow Roles Marionette Behavior

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.

Helpers

{{ 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.

Hand Rolled

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.

Clone this wiki locally