Akses data dalam aplikasi multi-pengguna

Masalah membatasi akses ke data muncul hampir selalu ketika mengembangkan sistem multi-pengguna. Skenario utama adalah sebagai berikut:

  1. pembatasan akses data untuk pengguna yang diautentikasi
  2. pembatasan akses ke data untuk diautentikasi tetapi tidak memiliki hak pengguna yang diperlukan
  3. mencegah akses tidak sah melalui panggilan langsung ke API
  4. memfilter data dalam permintaan pencarian dan daftar elemen UI (tabel, daftar)
  5. mencegah perubahan data milik satu pengguna oleh pengguna lain

Skenario 1-3 dijelaskan dengan baik dan biasanya diselesaikan dengan menggunakan alat kerangka kerja bawaan, seperti otorisasi berbasis peran atau klaim . Tetapi situasi ketika pengguna yang berwenang dapat secara langsung mengakses data "tetangga" atau mengambil tindakan dalam akunnya terjadi setiap saat. Ini paling sering terjadi karena fakta bahwa programmer lupa untuk menambahkan cek yang diperlukan. Anda dapat mengandalkan tinjauan kode, atau Anda dapat mencegah situasi seperti itu dengan menerapkan aturan penyaringan data global. Mereka akan dibahas dalam artikel.

Daftar dan tabel


Pengontrol khas untuk menerima data dalam ASP.NET MVC mungkin terlihat seperti ini :

[HttpGet] public virtual IActionResult Get([FromQuery]T parameter) { var total = _dbContext .Set<TEntity>() .Where(/* some business rules */) .Count(); var items= _dbContext .Set<TEntity>() .Where(/* some business rules */) .ProjectTo<TDto>() .Skip(parameter.Skip) .Take(parameter.Take) .ToList(); return Ok(new {items, total}); } 

Dalam hal ini, semua tanggung jawab untuk memfilter data hanya ada pada programmer. Apakah dia akan ingat bahwa perlu untuk menambahkan kondisi di Where atau tidak?

Anda dapat memecahkan masalah menggunakan filter global . Namun, untuk membatasi akses, kami memerlukan informasi tentang pengguna saat ini, yang berarti bahwa konstruksi DbContext harus rumit untuk menginisialisasi bidang tertentu.

Jika ada banyak aturan, maka implementasi DbContext pasti akan harus belajar "terlalu banyak", yang akan melanggar prinsip tanggung jawab tunggal .

Arsitektur engah


Masalah dengan akses ke data dan copy-paste terjadi karena dalam contoh kami mengabaikan pembagian menjadi lapisan dan dari pengendali kami segera mencapai lapisan akses data, melewati lapisan logika bisnis. Pendekatan ini bahkan telah dijuluki " pengendali jelek dan bodoh ." Dalam artikel ini saya tidak ingin menyentuh masalah yang berkaitan dengan repositori, layanan, dan penataan logika bisnis. Filter global melakukan pekerjaan ini dengan baik, Anda hanya perlu menerapkannya pada abstraksi dari lapisan lain.

Tambahkan abstraksi


.NET sudah memiliki IQueryable untuk mengakses data. Ganti akses langsung ke DbContext dengan akses ke penyedia tersebut:

  public interface IQueryableProvider { IQueryable<T> Query<T>() where T: class; IQueryable Query(Type type); } 

Dan untuk mengakses data, kami akan membuat filter ini:

  public interface IPermissionFilter<T> { IQueryable<T> GetPermitted(IQueryable<T> queryable); } 

Kami menerapkan penyedia sedemikian rupa sehingga mencari semua filter yang dinyatakan dan secara otomatis menerapkannya:

  public class QueryableProvider: IQueryableProvider { //       private static Type[] Filters = typeof(PermissionFilter<>) .Assembly .GetTypes() .Where(x => x.GetInterfaces().Any(y => y.IsGenericType && y.GetGenericTypeDefinition() == typeof(IPermissionFilter<>))) .ToArray(); private readonly DbContext _dbContext; private readonly IIdentity _identity; public QueryableProvider(DbContext dbContext, IIdentity identity) { _dbContext = dbContext; _identity = identity; } private static MethodInfo QueryMethod = typeof(QueryableProvider) .GetMethods() .First(x => x.Name == "Query" && x.IsGenericMethod); private IQueryable<T> Filter<T>(IQueryable<T> queryable) => Filters //     .Where(x => x.GetGenericArguments().First() == typeof(T)) //         Queryable<T> .Aggregate(queryable, (c, n) => ((dynamic)Activator.CreateInstance(n, _dbContext, _identity)).GetPermitted(queryable)); public IQueryable<T> Query<T>() where T : class => Filter(_dbContext.Set<T>()); //  EF Core  Set(Type type),    :( public IQueryable Query(Type type) => (IQueryable)QueryMethod .MakeGenericMethod(type) .Invoke(_dbContext, new object[]{}); } 

Kode untuk mendapatkan dan membuat filter dalam contoh ini tidak optimal. Alih-alih Activator.CreateInstance lebih baik menggunakan Pohon Ekspresi yang dikompilasi . Beberapa kontainer IOC mendukung registrasi obat generik terbuka . Saya akan meninggalkan pertanyaan pengoptimalan di luar cakupan artikel ini.

Kami menyadari filter


Implementasi filter mungkin terlihat seperti ini:

  public class EntityPermissionFilter: PermissionFilter<Entity> { public EntityPermissionFilter(DbContext dbContext, IIdentity identity) : base(dbContext, identity) { } public override IQueryable<Practice> GetPermitted( IQueryable<Practice> queryable) { return DbContext .Set<Practice>() .WhereIf(User.OrganizationType == OrganizationType.Client, x => x.Manager.OrganizationId == User.OrganizationId) .WhereIf(User.OrganizationType == OrganizationType.StaffingAgency, x => x.Partners .Select(y => y.OrganizationId) .Contains(User.OrganizationId)); } } 

Kami memperbaiki kode pengontrol


  [HttpGet] public virtual IActionResult Get([FromQuery]T parameter) { var total = QueryableProvider .Query<TEntity>() .Where(/* some business rules */) .Count(); var items = QueryableProvider .Query<TEntity>() .Where(/* some business rules */) .ProjectTo<TDto>() .Skip(parameter.Skip) .Take(parameter.Take) .ToList(); return Ok(new {items, total}); } 

Tidak banyak perubahan sama sekali. Tetap melarang akses langsung ke DbContext dari pengontrol dan jika filter ditulis dengan benar, maka masalah akses data dapat dianggap tertutup. Filternya cukup kecil, jadi menutupinya dengan tes tidaklah sulit. Selain itu, filter yang sama ini dapat digunakan untuk menulis kode otorisasi yang mencegah akses tidak sah ke data "asing". Saya akan meninggalkan pertanyaan ini untuk artikel selanjutnya.

Source: https://habr.com/ru/post/id414897/


All Articles