views:

65

answers:

3

I am starting to dabble with ASP.Net MVC. One question I have is on best practices for protecting user data. For example in the scenario of Sales people, they should only be able to view their own data.

e.g.

SalesData/Edit/14

It is very easy to change the "14" to view other data which they may/or may not have access to.

At this point, I am thinking in my controllers to check for who is logged in, and checking if they have access to the "id" that is getting requested. The problem I see with this, is that this will be application wide, and I am looking for best practices on how to approach this. Should I be looking at CustomControllers? Filters? or what? Any articles/references for how to tackle this would be appreciated.

+1  A: 

Set up your methods for retrieving data from your database repository in such a way that you can pass the UserID of the currently logged in person as a parameter. You can then use a permissions table to filter the data to only that data for which the user has access.

The permissions table would have two fields: UserID and ContentID. Once this is set up, it's fairly straightforward to set up CRUD screens so that someone with administrative privileges can set content permissions.

Robert Harvey
Good suggestion. Re: permissions table, what would ContentID be? Lets say I have Orders, OrderItems, SalesSlips, etc... many entities. What would contentId be? and how would I know what that ID maps too?
zzz
In that case, you need another field in the permissions table that is a TableID. It would also be good to have a table containing the table names and table IDs.
Robert Harvey
Or, you can just move up to an entity that the user does have access to, that incorporates the records in question. For example, if the salesperson has access to a specific order, he also has access to all of the OrderItems on that order.
Robert Harvey
A: 

I use IPrincipal and Authorize(Roles='...') attribute to limit access to actions. IPrincipal is then injected into service layer and user IIdentity is used to filter data.

Example: Users create tasks. Every user can see his tasks. GetTask(int taskId) method first filters by CreatedBy field using identifier from IIdentity and then takes task with specified id. If user doesn't have access to data, method will not return any rows.

LukLed
+1  A: 

The problem I see with this, is that this will be application wide,

Then you need common service that handles it. Suprisingly, I would call it IAuthorisationService.

and I am looking for best practices on how to approach this. Should I be looking at CustomControllers? Filters? or what?

Whichever way you choose you should use common IAuthorisationService above.
From my experience I can tell that it is easier to inject the service into controller and use it on every action:

/* Interfaces */
public interface IAuthorisationService {
 bool CanEdit(YourItem item);
}

public interface ICurrentUserProvider {
 YourUserEntity GetCurrentUser();
}

/* Implementations */
public class HttpUserProvider : ICurrentUserProvider {
 public YourUserEntity GetCurrentUser() {
  return HttpContext.Current.User.Principal as YourUserEntity;
 }
}

public calss RolesAuthorisationService : IAuthorisationService {
 ICurrentUserProvider userProvider
 public RolesAuthorisationService(ICurrentUserProvider userProvider) {
  this.userProvider = userProvider;
 }

 public bool CanEdit(YourItem item) {
  var u = userProvider.GetCurrentUser();
  if (u == null)
   return false;
  return item.Owner == u && u.IsInRole("EditYourItem");
 }
}

/* Controller */

public class MyController: Controller {
 IAuthorisationService authorisation;

 public MyController(IAuthorisationService authorisation) {
  this.authorisation = authorisation;
 }

 public ActionResult Edit(int id) {
  var item = GetTheItembyIdSomehow();
  if (!authorisation.CanEdit(item))
   return new HttpUnauthorizedResult();

  // Can do this
 }
}

Then you can use ControllerFactory to inject the required dependencies automatically into the controllers:

class DependencyInjectionContainer : WindsorContainer {
    public DependencyInjectionContainer() {
        RegisterDependencies();
    }

    private void RegisterDependencies() {

        // Services
        Register(
            AllTypes.Of<IDiscoverableService>()
                .FromAssembly(typeof(IDiscoverableService).Assembly)
                .Configure(c => c.LifeStyle.Transient)
                .WithService.FromInterface()
            );

        // Controllers
  Register(
   AllTypes.Of<IController>()
    .FromAssembly(typeof(DependencyInjectionContainer).Assembly)
    .Configure(c => c.LifeStyle.Transient)
   );
    }
}

class WindsorControllerFactory : DefaultControllerFactory, IDisposable {
    private readonly IWindsorContainer container;

    public WindsorControllerFactory() {
        container = new DependencyInjectionContainer();
    }

    protected override IController GetControllerInstance(Type controllerType) {
        if (controllerType == null)
            return base.GetControllerInstance(controllerType);
        return (IController) container.Resolve(controllerType);
    }

    public void Dispose() {
        container.Dispose();
    }
}
Dmytrii Nagirniak
Looking at your code, it appears that you are only specifying roles such as "edit," and not permissions for specific items.
Robert Harvey
Ohh. Yes. Sorry. I'll update it.
Dmytrii Nagirniak