How to Implement an Access Control List in My Web MVC Application

How can I implement an Access Control List in my Web MVC application?

First part/answer (ACL implementation)

In my humble opinion, the best way to approach this would be to use decorator pattern, Basically, this means that you take your object, and place it inside another object, which will act like a protective shell. This would NOT require you to extend the original class. Here is an example:

class SecureContainer
{

protected $target = null;
protected $acl = null;

public function __construct( $target, $acl )
{
$this->target = $target;
$this->acl = $acl;
}

public function __call( $method, $arguments )
{
if (
method_exists( $this->target, $method )
&& $this->acl->isAllowed( get_class($this->target), $method )
){
return call_user_func_array(
array( $this->target, $method ),
$arguments
);
}
}

}

And this would be how you use this sort of structure:

// assuming that you have two objects already: $currentUser and $controller
$acl = new AccessControlList( $currentUser );

$controller = new SecureContainer( $controller, $acl );
// you can execute all the methods you had in previous controller
// only now they will be checked against ACL
$controller->actionIndex();

As you might notice, this solution has several advantages:

  1. containment can be used on any object, not just instances of Controller
  2. check for authorization happens outside the target object, which means that:

    • original object is not responsible for access control, adheres to SRP
    • when you get "permission denied", you are not locked inside a controller, more options
  3. you can inject this secured instance in any other object, it will retain the protection
  4. wrap it & forget it .. you can pretend that it is the original object, it will react the same

But, there are one major issue with this method too - you cannot natively check if secured object implements and interface ( which also applies for looking up existing methods ) or is part of some inheritance chain.

Second part/answer (RBAC for objects)

In this case the main difference you should recognize is that you Domain Objects (in example: Profile) itself contains details about owner. This means, that for you to check, if (and at which level) user has access to it, it will require you to change this line:

$this->acl->isAllowed( get_class($this->target), $method )

Essentially you have two options:

  • Provide the ACL with the object in question. But you have to be careful not to violate Law of Demeter:

    $this->acl->isAllowed( get_class($this->target), $method )
  • Request all the relevant details and provide the ACL only with what it needs, which will also make it a bit more unit-testing friendly:

    $command = array( get_class($this->target), $method );
    /* -- snip -- */
    $this->acl->isAllowed( $this->target->getPermissions(), $command )

Couple videos that might help you to come up with your own implementation:

  • Inheritance, Polymorphism, & Testing
  • Don't Look For Things!

Side notes

You seem to have the quite common ( and completely wrong ) understanding of what Model in MVC is. Model is not a class. If you have class named FooBarModel or something that inherits AbstractModel then you are doing it wrong.

In proper MVC the Model is a layer, which contains a lot of classes. Large part of the classes can be separated in two groups , based on the responsibility:

- Domain Business Logic

(read more: here and here):

Instances from this group of classes deal with computation of values, check for different conditions, implement sales rules and do all the rest what you would call "business logic". They have no clue how data is stored, where it is stored or even if storage exists in first place.

Domain Business object do not depend on database. When you are creating an invoice, it does not matter where data comes from. It can be either from SQL or from a remote REST API, or even screenshot of a MSWord document. The business logic does no change.

- Data Access and Storage

Instances made from this group of classes are sometimes called Data Access Objects. Usually structures that implement Data Mapper pattern ( do not confuse with ORMs of same name .. no relation ). This is where your SQL statements would be (or maybe your DomDocument, because you store it in XML).

Beside the two major parts, there is one more group of instances/classes, that should be mentioned:

- Services

This is where your and 3rd party components come in play. For example, you can think of "authentication" as service, which can be provided by your own, or some external code. Also "mail sender" would be a service, which might knit together some domain object with a PHPMailer or SwiftMailer, or your own mail-sender component.

Another source of services are abstraction on to on domain and data access layers. They are created to simplify the code used by controllers. For example: creating new user account might require to work with several domain objects and mappers. But, by using a service, it will need only one or two lines in the controller.

What you have to remember when making services, is that the whole layer is supposed to be thin. There is no business logic in services. They are only there to juggle domain object, components and mappers.

One of things they all have in common would be that services do not affect the View layer in any direct way, and are autonomous to such an extent, that they can be ( and quit often - are ) used outside the MVC structure itself. Also such self-sustained structures make the migration to a different framework/architecture much easier, because of extremely low coupling between service and the rest of application.

Access Control in a Web Application

This is really a matter of implementation and everyone does it its own way. It also depends on the nature of the data, particularly on the size of your authorization (do your have 5 roles and users are attached to them or does each user have a specific set of articles he can access, different for each user - for instance)

If you do not want to make the processing on the controller, you could store the authorization information in your database, in a table which links a user to a set of KB articles, or a role (which would then be reflected in the article). In that case your SELECT query would just pass the authenticated user ID. This requires that the maintenance of the relationship is done of the database, which may not be obvious.

Alternatively you can store the ACL on the controller and build a query from there - for specific articles or groups of articles.

Getting all the articles and checking them on the controller is not a good idea (as you mention), DBs have been designed also to avoid such issues.

Access control in a ASP.MVC application

There are several options for authorization in ASP.NET MVC. The way you are doing is indeed very inconvenient, but there is a better way! You DO have attribute macros in C# :)

[Authorize]
public ActionResult DoSomething(int someParam)
{
//Do stuff here.
Return View();
}

The [Authorize] tag can be placed directly above any action on a controller or even above the controller class itself to make the entire controller accessible only to authenticated users.

[Authorize]
public class HomeController : Controller
{
//Actions and stuff
}

If you are using the membership and role provider you can even include a role filter in the attribute.

[Authorize(Roles="trader")]
public ActionResult SomeAction(int someParam)
{
//stuff...
}

It is also possible to apply authorization to entire URL routes. Similar to the way you would apply directory level authorization in traditional ASP.NET Web Forms. Just add something like this to your web.config:

<location path="Admin">
<system.web>
<authorization>
<deny users="?"/>
<allow roles="SiteAdmin"/>
<deny users="*"/>
</authorization>
</system.web>
</location>

This tells UrlAuthorizationModule (which is registered for all ASP.NET applications by default) that for the URL ~/Admin and URLs matching ~/Admin/*, it should do the following:

• Deny access for unauthenticated visitors ()

• Allow access for authenticated visitors in the SiteAdmin role ()

• Deny access to all other visitors ()

Implementing access control in ASP.NET MVC 3

If you want to implement your own membership system, then you can follow these steps:

  1. Put an HttpModule (a class inheriting from IHttpModule) in the way of secure requests (requests you want to define access control for)
  2. In that module, create a handler AuthenticateRequest event
  3. In that handler, check the request for an Authentication Cookie. This cookie could be yours or you can use FormsAuthentication class to create, encrypt and decrypt cookies for you.
  4. If cookie is present, then load the roles of the user, and store them in HttpContext.Current.Items as a key/value pair. This way, you can use it anywhere you want.
  5. If not, then redirect the user to login page. The address of the login page could be retrieved from settings or web.config
  6. In login page, get the login information of user including user name and password, then create an Authentication Cookie and send it to the client.

Access Control in ASP.NET MVC depending on input parameters / service layer?

First of all, I think you already half-way figured it, becuase you stated that

as first I'd need a different role for every product, and second I won't know which role to check for until I've retrieved my Product from the Repository

I've seen so many attempts at making role-based security do something it was never intended to do, but you are already past that point, so that's cool :)

The alternative to role-based security is ACL-based security, and I think that is what you need here.

You will still need to retrieve the ACL for a product and then check if the user has the right permission for the product. This is so context-sensitive and interaction-heavy that I think that a purely declarative approach is both too inflexible and too implicit (i.e. you may not realize how many database reads are involved in adding a single attribute to some code).

I think scenarios like this are best modeled by a class that encapsulates the ACL logic, allowing you to either Query for decision or making an Assertion based on the current context - something like this:

var p = this.ProductRepository.GetProductById(id);
var user = this.GetUser();
var permission = new ProductEditPermission(p);

If you just want to know whether the user can edit the product, you can issue a Query:

bool canEdit = permission.IsGrantedTo(user);

If you just want to ensure that the user has rights to continue, you can issue an Assertion:

permission.Demand(user);

This should then throw an exception if the permission is not granted.

This all assumes that the Product class (the variable p) has an associated ACL, like this:

public class Product
{
public IEnumerable<ProductAccessRule> AccessRules { get; }

// other members...
}

You might want to take a look at System.Security.AccessControl.FileSystemSecurity for inspiration about modeling ACLs.

If the current user is the same as Thread.CurrentPrincipal (which is the case in ASP.NET MVC, IIRC), you can simplyfy the above permission methods to:

bool canEdit = permission.IsGranted();

or

permission.Demand();

because the user would be implicit. You can take a look at System.Security.Permissions.PrincipalPermission for inspiration.

How to implement Permission Based Access Control with Asp.Net Core

Based on the comments, here an example on how to use the policy based authorization:

public class PermissionRequirement : IAuthorizationRequirement
{
public PermissionRequirement(PermissionEnum permission)
{
Permission = permission;
}

public PermissionEnum Permission { get; }
}

public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly IUserPermissionsRepository permissionRepository;

public PermissionHandler(IUserPermissionsRepository permissionRepository)
{
if(permissionRepository == null)
throw new ArgumentNullException(nameof(permissionRepository));

this.permissionRepository = permissionRepository;
}

protected override void Handle(AuthorizationContext context, PermissionRequirement requirement)
{
if(context.User == null)
{
// no user authorizedd. Alternatively call context.Fail() to ensure a failure
// as another handler for this requirement may succeed
return null;
}

bool hasPermission = permissionRepository.CheckPermissionForUser(context.User, requirement.Permission);
if (hasPermission)
{
context.Succeed(requirement);
}
}
}

And register it in your Startup class:

services.AddAuthorization(options =>
{
UserDbContext context = ...;
foreach(var permission in context.Permissions)
{
// assuming .Permission is enum
options.AddPolicy(permission.Permission.ToString(),
policy => policy.Requirements.Add(new PermissionRequirement(permission.Permission)));
}
});

// Register it as scope, because it uses Repository that probably uses dbcontext
services.AddScope<IAuthorizationHandler, PermissionHandler>();

And finally in the controller

[HttpGet]
[Authorize(Policy = PermissionEnum.PERSON_LIST.ToString())]
public ActionResult Index(PersonListQuery query)
{
...
}

The advantage of this solution is that you can also have multiple handlers for a requirement, i.e. if first one succeed the second handler can determine it's a fail and you can use it with resource based authorization with little extra effort.

The policy based approach is the preferred way to do it by the ASP.NET Core team.

From blowdart:

We don't want you writing custom authorize attributes. If you need to do that we've done something wrong. Instead you should be writing authorization requirements.

PHP - Where to put RBAC and Authentification in an MVC application?

In a typical MVC application the authentication check (i.e. "if not auth, then stop and render the login page instead") is done very early in processing the request, while the business logic (i.e. "if user has this permission then this happens, otherwise that happens") is handled within the "C" (the controller).

Most frameworks have a mechanism in place for tests like the authentication check your are describing - names vary but I have often seen it called "middleware".

The role based access control is purely your implementation.



Related Topics



Leave a reply



Submit