0

Currently I am writing a web application in MVC 4. I am using a generic repository pattern. It works well. So I have something like the followings,

public class AddressRepository : IAddressRepository
    {
        private AISDbContext context = new AISDbContext();

        public IQueryable<Address> GetAddresses()
        {
            return context.Address;
        }

    }

But now I need to add something that filters the data more. Based on the logged in user's role, this data should be more filtered.

something like this..

public IQueryable<Address> GetAddresses()
{
   return context.Address.where(x=>x.haspermissions = CURENTUSER.Role);
 }

Now I could always add another function like this, but I want to try an be general. I want to know if I can just use the first bit of code and inherit from another class, that just applies the security trimming. This way I do not have to rewrite all my queries, I simply tell each class to inherit from the security trimmer. hope that makes sense..

Thanks

updated code

public class AddressRepository : SecureRepositoryBase<Address>, IAddressRepository
    {
        private AISDbContext context = new AISDbContext();

        public IQueryable<Address> GetAll()
        {
            return base.RetrieveSecure(context.Address, 1);           
        }
}

 public abstract class SecureRepositoryBase<T> where T : ISecuredEntity
    {
        public IQueryable<T> RetrieveSecure(IQueryable<T> entities, int currentUser)
        {
            return entities.Where(e => e.InspectorId == currentUser);         
        }
    }

 public interface ISecuredEntity
    {
        int? InspectorId { get; set; }
    }

 public class Address: ISecuredEntity
    {
        public int COESNo { get; set; }
        public int Postcode { get; set; }
        public int AuditAuthNo { get; set; }
        public bool? SelectedForAudit { get; set; }
        public int? RECId { get; set; }
        public string CustomerName { get; set; }
        public string CustomerAddress { get; set; }
        public int? CustomerSuburbId { get; set; }
        public int? InspectorId { get; set; }
        public DateTime? AuditDate { get; set; }
        public int? AuditType { get; set; }
        public int? UploadType { get; set; }
        public string COESImage { get; set; }
        public DateTime CreatedDate { get; set; }
        public int? CreatedBy { get; set; }
        public DateTime? ModifiedDate { get; set; }
        public int? ModifiedBy { get; set; }

        public virtual UserDetails Inspector { get; set; }
        public virtual Postcodes CustomerSuburb { get; set; }
        public virtual ResponsiblePerson RPerson { get; set; }
        public virtual UserProfile CreatedByUser { get; set; }
        public virtual UserProfile ModifiedByUser { get; set; }
    }
user2206329
  • 2,792
  • 10
  • 54
  • 81
  • I don't see the advantage of a generic repository, it usually makes things unnecessary more complicated. See http://stackoverflow.com/questions/1230571/advantage-of-creating-a-generic-repository-vs-specific-repository-for-each-obje for example. I'd just go with specific repositories where the intention is clearly expressed (Code maintainability!) – Alex Jun 14 '13 at 06:47
  • _"I don't see the advantage of a generic repository"_ - so let's just write CRUD operations for each model? – CodeCaster Jun 14 '13 at 07:24
  • @CodeCaster Of course not, but don't try to make it generic at all cost. – Alex Jun 14 '13 at 08:58
  • Somehow I agree with Alex: If you want to read restricted lists from the database - maybe you will need unrestricted lists some day for e.g administration - I would write GetAddressesRestricted(User user) in the repository and it's interface. I would avoid the CURRENTUSER in your example. – MoCapitan Jun 14 '13 at 09:27
  • Mocaptain - we'll the requirement at the moment is to call the same controller .. Which calls the action method.. Which returns the view.. And depending on who is logged in.. It should restrict the list returned.. So if an administration user logs in it should return the whole list.. Rather than writing separate methods for each users.. I thought I could restrict the way code caster has shown.. I am new to all of this so any and all feedback is appreciated – user2206329 Jun 14 '13 at 09:47
  • You might want to look into using global filters. This is a really good blog post that can get you started http://www.agile-code.com/blog/entity-framework-code-first-applying-global-filters/ – SOfanatic Jun 14 '13 at 13:15

3 Answers3

0

Create a base class that converts each query to one where the permissions are checked and let your repository classes inherit from that.

Something like this:

public interface ISecuredEntity
{
    IEnumerable<string> Permissions { get; }
}

public class Address : ISecuredEntity
{
    public IEnumerable<string> Permissions { get; set; }
}

public class AddressRepository : SecureRepositoryBase<Address>, IAddressRepository
{
    private AISDbContext context = new AISDbContext();

    public IQueryable<Address> GetAddresses()
    {
        return base.RetrieveSecure(context.Address, CURENTUSER);
    }
}

public abstract class SecureRepositoryBase<T>
    where T : ISecuredEntity
{
    public IQueryable<T> RetrieveSecure(IQueryable<T> entities, IUser currentUser)
    {
        return entities.Where(e => e.Permissions.Contains(currentUser.Role));
    }
}
CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • would you happen to have an example I could follow? – user2206329 Jun 14 '13 at 07:04
  • CodeCaster thanks for that. Just need to clarify, the address class is inheriting from ISecuredEntity. However what I want to do is, currently the Adress table has a field called roleId. So all I want to do is filter on that role id for the logged in user. The way you have defined the Address class it looks like I need to add into the permissions ineumerable. Sorry for the silly questions.. new to all of this stuff – user2206329 Jun 14 '13 at 10:29
  • @user2206329 then instead of the permissions, add the `int RoleID { get; }` to the interface (if one entity is owned by one role). – CodeCaster Jun 14 '13 at 10:34
  • i am getting the following error. Unable to cast the type 'AIS.Domain.Entities.Address' to type 'AIS.Domain.Entities.ISecuredEntity'. LINQ to Entities only supports casting EDM primitive or enumeration types. See above for my code update – user2206329 Jun 14 '13 at 11:44
  • @user2206329 please edit your question, not my answer. Also show the class definition of the class that contains the `public IQueryable
    GetAll()` method, which now just floats between your other code. This pattern should work.
    – CodeCaster Jun 14 '13 at 12:20
0

You might want to look into global filters. I'm including a brief example of how it would work:

public class AISDbContext : DbContext
{
    public void ApplyFilters(IList<IFilter<AISDbContext>> filters)
    {
        foreach(var filter in filters)
        {
            filter.DbContext = this;
            filter.Apply();
        }
    }
}

public interface IFilter<T> where T : DbContext
{
    T DbContext {get; set;}
    void Apply();
}

public class AdminRoleFilter : IFilter<AISDbContext>
{
    public AISDbContext _dbContext {get; set;}
    public void Apply()
    {
        _dbContext.Address = new FilteredDbSet(_dbContext, d => d.haspermissions = "Admin");
    }
}

The details of the FilteredDbSet are really extensive and can be found here.

Then the implementation of your DbContext in your AddressRepository would look like this:

var context = new AISDbContext();
context.ApplyFilters(new List<IFilter<AISDbContext>>()
    {
        new AdminRoleFilter()
    });

public List<Address> GetAddresses()
{
    return context.Address.ToList();
}
SOfanatic
  • 5,523
  • 5
  • 36
  • 57
0

MVC repository pattern, how to security trim?

Repository should not take care of access control / security trimming. It merely provides methods to query for domain objects.

In the following code

function GetAddresses() as IQueryable(of Address)
function GetAddressesInspectedBy(Inspector as Inspector) as IQueryable(of Address)

The method GetAddresses will return all addresses available.

The method GetAddressesInspectedBy will return all addresses inspected by the concrete inspector.

These two methods are clearly distinct and coexist with each other in repository. Both methods should be available, but what method is to be called depends on business logic / access control outside repository.

But now I need to add something that filters the data more. Based on the logged in user's role, this data should be more filtered.

Data returned to the user should not be based on user's role! Instead it should be based on user's request! And whether the request may be executed or not depends on user's role.

For example, administrator role user may be allowed to request each of the following

  1. all addresses,
  2. addresses by concrete inspector,
  3. addresses inspected by himself.

Each of the three request types returns different results, independent on administrator role!

While inspector role user may be allowed to request only

  1. addresses inspected only by himself.

Application receives request, checks whether user is allowed to do this request and calls repository methods corresponding to the request.

Lightman
  • 1,078
  • 11
  • 22