views:

534

answers:

2

I already know about User and Role-based security in ASP.NET MVC. But now I need something a little more granular.

Let's say I have a list of documents, some of which the user is authorized for, some not. Each document has a corresponding record in a documents table in a database. Documents can be downloaded for viewing, if the user has security access. Documents can also be added, if you have the role. Each document has an URL, and each document list has an URL.

I would like to security trim the list so that the user only sees those documents for which he is authorized. But I also need to authenticate the URL requests for these lists and documents, since there is nothing preventing a user from bookmarking a document they no longer have access to, or simply typing the URL into the browser.

Is the built-in role-based security model suitable for this, or do I need to create separate, table-based security? Can I put the security in my repository, so that the returned records are already trimmed, or should it be part of the controller? Do I need a security attribute to validate the controller request, or should I just put it in the controller method as the first few lines of code?

+1  A: 

@Robert, I think you've already answered your own question when you said you should trim them (before) they reach the view. So in your Business logic, as a preference over the repository, you might want to do a lamda to trim off the excess so to speak.

Im my opinion I would never return any records to the view that the user wasn't allowed to see. Why increase risk and traffic?

As for the bookmarks I think there you're going to need to do some business logic preventing them from going to the url when access no longer exists.

I thought the controller was simply there to service the data to the page and not to have any logic as such so I'd prefer the business layer approach for this one as it does appear to be a business rule.

That might not be what you had in mind but unless there is a better approach it's the one I would use.

griegs
It sounds like you favor an approach using business logic residing in a partial class of the DAL/ORM. Does that sound about right?
Robert Harvey
Yeah kinda but more seperated than that. Whilst I don't mind, having thought about it, the idea of a partial in the DAL, my prefered approach has always been to extrapolate that to an actual business layer. Sure, this layer will reference the DAL objects but it seperates my business logic from my database. It just sounds like access rights is a Business rule not a DAL concern.
griegs
I like the idea of putting it in the business logic; you still get to hide the security check from the controller, and you only have to do it once (it works for security trimming as well).
Robert Harvey
+1  A: 

I'll try to explain how I intended to implement this in my project. The requirement is similar as yours: Users have Roles which have Permissions and everything can change from Permission definition, Role's Permission list, and User's Role list etc. So in one moment it's possible that User has access to something and in another, if Administrator alter something, he does not have access.

Before I put some code, I'll answer to your questions.

Do I need to create separate, table-based security?

-Yes

Can I put the security in my repository, so that the returned records are already trimmed, or should it be part of the controller?

-I think security should be a part of business logic so I would put it somewhere in between controller and repository.

Do I need a security attribute to validate the controller request?

-In my project, I've put it in attribute, but sometimes i need to access it from controller to, but since that I keep security logic in business layer, I don't think it is a problem.

First attribute is simple attribute that just allows logged users to execute action:

public class LoggedUserFilterAttribute : ActionFilterAttribute
{
    public bool Logged { get; set; }
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (!SessionManager.IsUserLogged)
        {
            filterContext.Result = new RedirectToRouteResult(GetRedirectToNotLoggedRouteValues());
            this.Logged = false;
        }
        else
            this.Logged = true;
    }

    public RouteValueDictionary GetRedirectToNotAuthorizedRouteValues()
    {
        RouteValueDictionary routeValues = new RouteValueDictionary();
        routeValues.Add("action", "NotAuthorized");
        routeValues.Add("controller", "Authorization");
        return routeValues;
    }
    public RouteValueDictionary GetRedirectToNotLoggedRouteValues()
    {
        RouteValueDictionary routeValues = new RouteValueDictionary();
        routeValues.Add("action", "NotLogged");
        routeValues.Add("controller", "Authorization");
        return routeValues;
    }
}

and then I have, for example, attribute which allows only SuperUsers to access it:

public class SuperUserFilterAttribute : LoggedUserFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);

        if (Logged)
        {
            MyBaseController controller = filterContext.Controller as MyBaseController;
            if (controller == null)
                throw new Exception("Please use MyBaseController instead of built in Controller");

            User loggedUser = controller.Model.UserBO.GetUserByID(SessionManager.LoggedUser.UserID);

            if(!loggedUser.IsSuperUser)
            {
                filterContext.Result = new RedirectToRouteResult(GetRedirectToNotAuthorizedRouteValues());
            }
        }
    }
}

The MyBaseController is class that inherits Controller and has an instance of Model class which represent container for business objects. In controllers action body, if needed I check users rights on current entity and depending on that I return proper view:

    [LoggedUserFilter]
    public ActionResult LoadSomeEntity(int customerServiceID,int entityID)
    {
        UserRights userPermissionsView = Model.SecurityBO.GetUsersRightsOnEntity(SessionManager.LoggedUser.UserID, entityID);

        if(userPermissionsView.Write) 
            return View("EditEntity",Model.EntityBO.GetEntityByID(entityID));
        if(userPermissionsView.Read) 
            return View("ViewEntity",Model.EntityBO.GetEntityByID(entityID));

        return View("NotAuthorized");     
    }

p.s. I'm not sure if I can suggest anything to someone that obviously has much more experience that me :), so if I'm spamming, I apologize for that.

Misha N.
Thanks for the reply. You have the right idea; I have seen the Attribute pattern as it applies to User and Role authentication. I saw someone else post a question here about using Attributes for table-based security. The problem with this approach is you need access to the URL parameters to do it properly. Since the attribute doesn't have access to the controller method's parameters, you now have to jump through some hoops to get them.
Robert Harvey
I see, the AuthorizeAttribute in OnAuthorize method does not have access to the controller method's parameters, but the ActionFilterAttribute in OnActionExecuting that I've used does: there is filterContext.ActionParameters property that is returning IDictionary<string,object> where key is parameter name and value is value passed to the method.
Misha N.
That is a good observation, but it just seems a little too far removed for my taste. Since I'm going to be retrieving data from my model anyway, I can just security trim it there. If a user doesn't have access to certain records, they will simply not appear in the list, and if a single record is being retrieved for a detail view, I can check for null and redirect.
Robert Harvey
Totally agree with you. In mine project the business logic haven't demanded that granular approach as "record level security".
Misha N.