Duncan
Duncan

Reputation: 10291

Data level security in MVC 3

I'm curious about the best approach to data level security in MVC 3. Allow me to paint a picture.

There exists an Event view. This Event lists the name of the event and a list of what players are playing in this event.

Depending on the current user's relationship with that event, each user would get a substantially different view served.

For example, if the user is an Organiser, then the user can view and manage all other User's availability for that event.

If the user is merely partaking in that event, then obviously that user can only manipulate his own availability.

There are more combinations than this.

I know about custom action filters, but that seems to be overkill.

Instead I've gone for an approach where on the Index of the Event, there is a switch statement that will redirect to the appropriate View, e.g. OrganiserEventView or PlayerEventView.

That's the easy bit. I think.

Where it gets messy is that I've used a shared Editor for the enumeration of PlayerModels (part of the Model of the main view) to list the Players. This shared editor itself would also have to respect data-level security.

Am I on the right track, or is there a better way?

Upvotes: 2

Views: 822

Answers (4)

Shashi Penumarthy
Shashi Penumarthy

Reputation: 813

Duncan,

This is something I encounter in every application. Here's some context: I work in one department in a large university so we have to deal with multiple data sources. Most of our apps have security that integrates university "central IT" security services such as centralized authentication and active directory, as well as "homegrown" roles and permissions.

Our apps need to present different views and actions for different users based on data in both "homegrown" applications (used internally in our dept) plus enterprise data.

Our approach: After looking around a fair bit and not finding a good solution for this problem anywhere, I ended up writing a security framework for our use. I might as well go ahead and describe what it does here. Here's what's involved:

  1. Static and Dynamic roles. Static roles are independent of data (e.g. I get developer role in apps), while dynamic roles depend on the SecurityContext (e.g. a department's fiscal officer gets the 'Fiscal Officer role when she's looking at her department's accounts).
  2. Permissions that are assigned to Static or Dynamic roles.
  3. A SecurityContext that encapsulates all the data needed for a Permission check to be performed, including the current User and any data (e.g. account numbers, proposal document Ids, transaction dates, anything).
  4. A SecurityContextValidator that accepts a SecurityContext and returns a set or Roles that are valid for the specified User in the specified SecurityContext. So the logic that determines who can see what is in this class.
  5. One SecurityContextValidator per SecurityContext. This mapping is registered at initialization with a SecurityContextManager. I use Ninject to load my security module which performs this at startup.
  6. A default context used if a permission does not specify a SecurityContext, which simply contains the Principal information from Asp.Net security.
  7. A SecurityService that given a User, a SecurityContext and a Permission, determines all the Roles the User has in that SecurityContext and checks if the any of the Roles have the Permission being checked for.

At this point, here's an example flow in Asp.Net MVC:

  • Receive HttpRequest
  • Authenticate via Forms Authentication
  • Route to Controller Action
  • if (Permission.GetByName("CanDoSomething").IsAllowed()) { // proceed

-- [Inside Permission.IsAllowed] --

  • SecurityService receives SecurityContext
  • Find SecurityContextValidator for specified SecurityContext
  • Aggregate all Static and Dynamic roles from SecurityContextValidator
  • Iterate over roles and check if any of them have Permission being requested.
  • Return True or False!

To make things easier, I took it one step further and created a AbstractContextProtectedAttribute, which expects a SecurityContextFactory delegate that can create a SecurityContext (given a HTTPRequest, for example) and a Permission to check with the specified SecurityContext. Subclasses of this class can then be used to decorate controller actions.

Phew! So all that let's me now setup a table of Users, Roles, Permissions and map them to each other, define all the Permissions in the database. I wrote a pluggable SecurityPersistenceService that makes the security framework agnostic of the persistence strategy used - we unfortunately have everything from DataReaders, DataAdapters to Linq2Sql and EF. But at least we can write code like this:

[Protected("CanAccessX")] // Checks using default context
public class SomeController 
{
    [Protected("CanSeeY")] // Checks using default context
    public PartialViewResult GetY(<parameters>) 
    {
        var canSeeY_Variation1 = Permission.Get("CanSeeY_Variation1") ;
        var y_Variation1_Context = new Y_Variation1_Context { <build your context here> } ;
        if (canSeeY_Variation1.IsAllowed(y_variation1_Context))
        {
            <return variation 1 view>
        }

        // Y_Variation_2...etc
    }
}

And to make this work, at startup I register the appropriate validators:

public class MyNinjectModule
{
    public override void Load()
    {
        // Wire up a persistence service for the security framework
        // to use.
        SecurityService.SecurityPersistenceService = new MySecurityPersistenceServiceImplementation() ;

        // This is what allows the SecurityService to figure out what Validator to use
        // in a specified Context to get the User's Roles.          
        SecurityService.RegisterValidator<Y_Variation1_Context>(new Y_Variation1_ContextValidator(...)) ;
    }
}

I am now working on an addition to this framework that let's me perform these checks over enumerables of data, thus infusing all my domain objects with security infrastructure. The only clean way I know of doing this is using AOP. I used to work in Java and used AspectJ. Now I am considering PostSharp.

Hope this provides perspective for thinking about your problem.

Upvotes: 3

Adam Tuliper
Adam Tuliper

Reputation: 30152

The way I see your question, role based security would be the way to go.

If they are the organizer then they are an organizer role. However you need to abstract this a bit. Since an organizer role would then in theory be an organizer to all other events, you need a method that makes this determination and populates the roles upon request so user A is an organizer of event A, but not organizer of event B. This ideally needs to occur before the controller code is accessed so your choices are global.asax.cs or an auth filter.

Upvotes: 1

Ted Johnson
Ted Johnson

Reputation: 4343

The answer will change depending on the size, complexity, and projected growth of your application. While having security in the controller and then having different views works there are trade-offs. For example, code/mark-up replication could be a draw back. For complex applications, portlets might help, but that is heavy stuff. A view can change based on security and access roles, you would pass the parameter/data from the controller just like anything else, if it is not already available via the session. I would create a utility method for the views to use so that the logic/rules do not end up in the view.

Upvotes: 1

George Stocker
George Stocker

Reputation: 57877

Using Roles and custom Role Attributes seem like a good fit (though I can say that without code or a better understanding of your codebase, no one can give you the 'best' way) for your problem.

Upvotes: 0

Related Questions