8

In ASP MVC we have the Authorize attribute to perform check at either a controller level or at a controller method level. But what if you need to do check permissions inside a controller method e.g let say your doing some update or create action to submit a blog post. Some users with revant permissions can attach files or make the blog post sticky. So when you are creating a new post you need to do all these additional check before saving the model. In Laravel there is the concept of abilites where you can do checks inside a controller method to see if a user has the ability to perform the relevant actions. Similarly you can use those abilitis inside views to check what item to display or hide - all this comes out of the box.

Is there anything similar in ASP MVC. How would you implement checking permissions within a controller method. Do you create a permission class with properties such as

public class Permissions
{
    private readonly IPrincipal user;

    public Permissions (IPrincipal user)
    {
        this.user = user;
    }

    public bool CanUploadFiles
    {
        get { return user.IsInAnyRole("Standard", "Admin"); }
    }

    public bool CanDeleteItems
    {
        get { return user.IsInRole("Admin"); }
    }

     public bool CanLockPost
    {
        get { return user.IsInRole("Admin"); }
    }

    // other permissions
}

Then inside controller action:

 public ActionResult Create(PostViewModel viewModel)
 {
       var permissions = new Permissions(User);

        if (ModelState.IsValid)
        {
                var post = new Post
                {
                   if (permissions.CanLockPost)
                    {
                        post.IsLocked = viewModel.IsLocked;
                    }
                    if (permissions.CanStickyPost)
                    {
                        post.IsSticky = viewModel.IsSticky;
                    }
                    // Set other properties
                }

               _postRepository.Add(post);
        }   
  }

Or would you save permissions in the database. I would like to hear your thoughts on how you go about implementing checks at a more granular level than simply at a controller or controller action level. Some code examples to demonstrate would be helpful.

adam78
  • 291
  • 3
  • 9

4 Answers4

1

Take a look at this: https://docs.asp.net/en/latest/security/authorization/roles.html

"How these roles are created and managed depends on the backing store of the authorization process. Roles are exposed to the developer through the IsInRole property on the ClaimsPrincipal class."

So as long as you're using the IsInRole() of the principal to check roles, I think you're are on the right track.

Some other random thoughts for you - To make it easier to use your permissions checks, if they are specific to a view model, you could expose them in the view model class. That way the razor view can check the view model to see what it should/shouldn't show to the user.

You could also use those same permissions checks to throw validation errors by implementing IValidatableObject in the view model class and putting your custom checks in the Validate method. Then you can handle permissions errors that prevent saving just like validation errors.

Bryan
  • 11
  • 2
1

We are implementing this using extension methods to the IPrincipal interface:

public static class PermissionExtenions
{
    public bool CanUploadFiles(this IPrincipal user)
    {
        return user.IsInAnyRole("Standard", "Admin");
    }

    public bool CanDeleteItems(this IPrincipal user)
    {
        return user.IsInRole("Admin");
    }
}

The nice this about this is you don't need to create a new object that wraps the IPrincipal. If you add a using directive at the top of your controllers, and add it to the namespaces to the Views/Web.config file you can use this in controllers and views by accessing the User property.

You can also use this in your own custom Authorize atttributes:

public void OnAuthorization(AuthorizationContext filterContext)
{
    var controller = filterContext.Controller as Controller;

    if (controller == null)
        return;

    var user = controller.User;

    if (!user.CanUploadFiles())
    {
        // redirect to some page
    }
Greg Burghardt
  • 34,276
  • 8
  • 63
  • 114
0

You can mimic the structure of the AuthorizeAttribute. In one of my past projects, I segregated "authorizable" functionality into different methods and appended a customized authorization attribute to those methods. So if you need to authorize a sub-function of your controller methods, put that functionality into it's own method.

You can pass static data to the constructor of the attribute to indicate an identifier of the authorizable object, and your attribute can do what ever authorization logic it needs to on the authorizable object, taking into context the security principal of the requesting user/service identity, and the security action.

How you authorize something is a whole different story. I would recommend selecting a proven authorization model (like RBAC, ABAC, ReBAC, etc.) and seeing if there are any pre-existing open-source libraries that implement those models. It's generally recommended that you do not come-up with your own authorization model.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
Mackers
  • 169
  • 4
0

I've used custom attributes in combination with the CQRS and decorator pattern. Talk is cheap so I'll just give you teh codez. Note that the attribute can be used at the controller level OR the Command level, or both if you're really paranoid.

Please note: This is not all my code, the creator of SimpleInjector (Steven van Deursen) inspired this in a post that I cannot find anymore. This is all wired up through IoC (SimpleInjector).

/// <summary>
/// For the Command Side
/// </summary>
[Permission(Permission.StickyPost)]
public class StickyPostCommand
{
    public int PostId { get; set; }
}

public class StickyPostCommandHandler : ICommandHandler<StickyPostCommand>
{
    public void Handle(StickyPostCommand command)
    {
        //We only handle what we are here to handle (stickying a post), not checking permissions
    }
}

/// <summary>
/// This decorates all commands, and does some checks before passing it on to the real command (decoartee)
/// </summary>
/// <typeparam name="TCommand"></typeparam>
public class PermissionCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
{
    private readonly MessagePermissionChecker<TCommand> permissionChecker;
    private readonly ICommandHandler<TCommand> decoratee;

    public PermissionCommandHandlerDecorator(MessagePermissionChecker<TCommand> permissionChecker, ICommandHandler<TCommand> decoratee)
    {
        this.decoratee = decoratee;
        this.permissionChecker = permissionChecker;
    }
    public void Handle(TCommand command)
    {
        this.permissionChecker.CheckPermissionForCurrentUser();
        this.decoratee.Handle(command);
    }
}

/// <summary>
/// Pulls the custom attribute out of the command, delegate to the UserPermissionChecker if it has an attribute
/// </summary>
/// <typeparam name="TMessage"></typeparam>
public class MessagePermissionChecker<TMessage>
{
    private static readonly Guid? permissionId;
    private readonly IUserPermissionChecker permissionChecker;

    static MessagePermissionChecker()
    {
        var permissionAttribute = typeof(TMessage).GetCustomAttribute<PermissionAttribute>();
        if (permissionAttribute != null)
        {
            permissionId = Guid.Parse(permissionAttribute.PermissionId);
        }
    }

    public MessagePermissionChecker(IUserPermissionChecker permissionChecker)
    {
        this.permissionChecker = permissionChecker;
    }

    public void CheckPermissionForCurrentUser()
    {
        if (permissionId.HasValue)
        {
            this.permissionChecker.CheckPermission(permissionId);
        }
    }
}

public interface IUserPermissionChecker
{
    void CheckPermission(Guid id);
}

public class UserPermissionChecker : IUserPermissionChecker
{
    public void CheckPermission(Guid id)
    {
        //Go out to the database, check a role, whatever
        throw new SecurityException("Negative ghostrider, you can't do that!");
    }
}

/// <summary>
/// For the Controller Side
/// </summary>
public class PermissionActionFilter : IActionFilter<PermissionAttribute>
{
    private readonly IUserPermissionChecker _permissionChecker;

    public PermissionActionFilter(IUserPermissionChecker permissionChecker)
    {
        _permissionChecker = permissionChecker;
    }

    public void OnActionExecuting(PermissionAttribute attribute, ActionExecutingContext context)
    {
        this._permissionChecker.CheckPermission(Guid.Parse(attribute.PermissionId));
    }
}
Jack
  • 101
  • 3