Фильтр данных с использованием выражения на основе роли - PullRequest
0 голосов
/ 02 февраля 2020

У меня есть приложение с несколькими ролями пользователя (Admin, Dealer, Manager). Со следующей структурой базы данных

public class Country
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Shop> Shops { get; set; }
}
public class Shop
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int CountryId { get; set; }
    public virtual Country Country { get; set; }
    public virtual ICollection<Machine> Machines { get; set; }
}
public class Machine
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int ShopId { get; set; }
    public virtual Shop Shop { get; set; }
}

Каждый пользователь может видеть записи машины, указанные c только для его роли. Администратор может видеть все машины, дилер - только машины из своей страны и менеджер для своего магазина. Мне нужно убедиться, что пользователи не видят данные, которые им не нужны, когда список компьютеров или отдельная сущность извлекаются из базы данных. Опции, которые я пробовал:

  1. Использовать глобальный фильтр структуры сущности, передавая выражение на основе роли в HasQueryFilter во время инициализации DbContext. Но, очевидно, первое выражение кэшируется во время одного вызова OnModelCreating. Поэтому этот подход не будет работать для разных выражений, основанных на роли для одной и той же сущности.
public class DatabaseContext : DbContext
{
    private Expression<Func<Machine, bool>> machineFilter;
    public DatabaseContext(DbContextOptions<DatabaseContext> options, Expression<Func<Machine, bool>> machineFilter) 
        : base(options)
    {
        this.machineFilter = machineFilter;
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Machine>().HasQueryFilter(machineFilter);
    }

    public virtual DbSet<Country> Countries { get; set; }
    public virtual DbSet<Shop> Shops { get; set; }
    public virtual DbSet<Machine> Machines { get; set; }
}
Использовать шаблон спецификации. Этот подход работает, но тогда мне нужно go пройти через все службы, где используется объект этого datacontext, и убедиться, что спецификация применяется в старом и новом коде. И я не уверен, насколько корректен этот подход для моего случая.
public class MachineService
{
    private readonly DataContext _context;

    public List<Machine> GetAll(ISpecification specification)
    {
        return _context.Machines.Where(specification.Predicate).ToList();
    }
}

public class MachineSpecification : ISpecification<Machine>
{
    public Expression<Func<Machine, bool>> Predicate { get; }

    public MachineSpecification(Role role, int? countryId, int? shopId)
    {
        switch(role)
        {
            case Role.Admin: 
                Predicate = c => true;                    
                break;
            case Role.Dealer:
                Predicate = c => c.Shop.CountryId == countryId;
                break;
            case Role.Manager:
                Predicate = c => c.ShopId == shopId;
                break;
            default: throw new NoPermissionException();
        }
    }
}

Каков подходящий подход или шаблон для решения проблемы доступа к данным на основе роли? Я не хочу делать эти проверки в контроллерах. Есть ли способ решить это на уровне DataContext?

...