0

In an MVC app, I want to implement a set of rules, which super users can create, read, update and delete.

Each rule explicitly allows/forbids a user to perform an action in the format:

<Allow || Deny> userId <action_key> <condition>

The action key would be something like "DoSomeAction" as a string.

I then intend to use those rules for authorisation inside controllers for authorisation. For example:

//GET ViewProduct/id
public ActionResult ViewProduct(string productId)
{
    var product = // get product from repository;
    if(RulesAuthorizer.Authorise("ViewProduct", User.Identity.GetUserId(), product){

    //proceed to product... JSON or partial view, etc.
    }
    return new HttpStatusCodeResult(403);
}

ViewProduct is an example action_key above.

In the Authorise(string action_key, string userId, object ruleArg = null) method, I would load all the user relevant rules from the DB for this action key and decide if the user should be allowed.

However, and this is really the question, how could I use a condition for a rule as a string. For example, a condition would be:

a user must be a member of the group "Customers" and the product must not be of type "cheese" or

a custom method result such as if the product was added by group X, and group Y must not see it, I could have my method Product.GetAddedBy() and include this method in the LINQ expression.

How could I store such conditions as strings for each rule and then build LINQ expressions from them?

I intend to pass the object in question (Product in the example) in the optional ruleArg parameter.

Any ideas are much appreciated for a convenient way to store strings, which can be made into LINQ expressions at run time or any alternative approach such as perhaps map conditions to delegates with parameters?

Dimitar Nikovski
  • 973
  • 13
  • 15
  • 4
    Read up on action filters and also user claims. You are over engineering and reinventing the wheel with cross cutting concerns. – Nkosi Dec 31 '17 at 17:27
  • Thanks, however, action filters wouldn't enable me to evaluate the object properties in more complex ways such as by who has published a product, or where it is located, etc. and super users would not be able to add/edit rules, which is a requirement. I will edit the question to signify this. Also user claims would be only for the user. – Dimitar Nikovski Dec 31 '17 at 17:32
  • 1
    Are you looking for something like [tag:dynamic-linq]? See [How to create LINQ Query from string?](https://stackoverflow.com/q/5139467). – dbc Dec 31 '17 at 17:34
  • You can do anything in an action filter including querying databases etc. Take the advice from @Nkosi – sheavens Dec 31 '17 at 20:45

1 Answers1

2

Here is an example of user access via Attributes using strings to determine what they have access to. This is using Action/Controller to determine access but you can modify it for any string(s) you want.

Decorate the controller(s) with [AuthoriseByRole]

First the Attribute

namespace PubManager.Authorisation
{
    public class AuthoriseByRoleAttribute : AuthorizeAttribute
    {
        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            var isAuthorized = base.AuthorizeCore(httpContext);

            if (!isAuthorized && httpContext.Request.IsAjaxRequest())
            {
                httpContext.Response.StatusCode = 401;
                httpContext.Response.End();
            }

            if (isAuthorized)
            {
                var request = httpContext.Request;
                var r = request.RequestContext.RouteData.Values["r"]
                    ?? request["r"];
                var currentUser = (UserModel) HttpContext.Current.Session["user"];
                if (currentUser == null)
                {
                    currentUser = HttpContext.Current.User.GetWebUser();
                }
                var rd = httpContext.Request.RequestContext.RouteData;
                string currentAction = rd.GetRequiredString("action");
                string currentController = rd.GetRequiredString("controller");
                if (currentUser.HasAccess(currentController, currentAction))
                    return true;

            }
            httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
            return false;
        }
    }
}

Then the UserModel that is used to determine access:

namespace PubManager.Domain.Users
{
    public class UserModel
    {
        public int UserId { get; set; }
        public string UserName { get; set; }
        public string Title { get; set; }
        [Required]
        [DisplayName("Forename")]
        public string FirstName { get; set; }
        [Required]
        public string Surname { get; set; }
        [Required]
        [DisplayName("Company name")]
        public DateTime? LastLogin { get; set; }
        public bool LockedOut { get; set; }
        public DateTime? LockedOutUntil { get; set; }
        public bool IsGlobalAdmin { get; set; }
        public bool? IsSystemUser { get; set; }
        public IEnumerable<RoleModel> Roles { get; set; }

        public bool HasAccess(string controller, string view)
        {
            if (IsGlobalAdmin || IsSystemUser.Value)
            {
                return true;
            }
            var isAuthorized = false;

            if (!Roles.Any())
                return false;

            foreach (var role in Roles)
            {
                if (role.PageToRoles == null)
                    return false;

                foreach (var pg in role.PageToRoles)
                {
                    if (pg.RolePage.Controller.Equals(controller, StringComparison.InvariantCultureIgnoreCase) && (pg.RolePage.View.Equals(view, StringComparison.InvariantCultureIgnoreCase) || pg.RolePage.View.Equals("*")))
                        isAuthorized = true;
                }
            }
            return isAuthorized;
        }
    }
}

Finally the GetWebUser class to get the user

namespace PubManager.Domain.Users
{
    public static class SecurityExtensions
    {
        public static string Name(this IPrincipal user)
        {
            return user.Identity.Name;
        }

        public static UserModel GetWebUser(this IPrincipal principal)
        {
            if (principal == null)
                return new UserModel();

            var db = new DataContext();

            var user = (from usr in db.Users
                        where usr.UserName == principal.Identity.Name
                        select new UserModel
                        {
                            Title = usr.Person.Title,
                            UserName = usr.UserName,
                            FirstName = usr.Person.FirstName,
                            Surname = usr.Person.LastName,
                            Email = usr.Person.Email,
                            LockedOut = usr.LockedOut,
                            UserId = usr.UserId,
                            IsSystemUser = usr.IsSystemUser,
                            IsGlobalAdmin = usr.IsGlobalAdmin.Value,
                            PersonId = usr.PersonId,
                            Roles = from r in usr.UserToRoles
                                    select new RoleModel
                                    {
                                        RoleId = r.RoleId,
                                        PageToRoles = from ptr in r.Role.PageToRoles
                                                      select new PageToRoleModel
                                                      {
                                                          RolePage = new RolePageModel
                                                          {
                                                              Controller = ptr.RolePage.Controller,
                                                              View = ptr.RolePage.View
                                                          }
                                                      }
                                    }
                        }).FirstOrDefault();

            if (user != null)
            {                     
                    HttpContext.Current.Session["user"] = user;
            }

            return user;
        }
    }
}
sheavens
  • 685
  • 7
  • 14