且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

从共享数据库的其他租户多租户应用prevent租户访问数据

更新时间:2023-02-20 20:26:38

恕我直言,在应用程序中的任何地方,你将有指出用户X有权管理或映射访问租客(S)A(,B) 。在你的业务层,你应该检查该用户曾经有权查看使用伪造的ID数据。你的情况,伪造我将属于另一个租户,用户不能访问,所以你会返回一个未授权/安全违反异常。

心连心

I’m working on a tenant application and i was wondering how i can block tenant access other tenant data.

First, let me expose some facts:

  1. The app is not free, 100% for sure the malicious user is a client.
  2. All the primary keys/identity are integers (Guid solve this problem but we can't change right now).
  3. The app use shared database and shared schema.
  4. All the tenants are business group wich own several shops.
  5. I'm use Forgery...

I have some remote data chosen by dropdown and its easy change the id's and acess data from other tenants, if you have a little knowledge you can f*ck other tenants data.

The first thing i think was check every remote field but this is kind annoying...

So i build a solution compatible with Code First Migrations using Model Convention and Composite Keys, few tested, working as expected.

Here's the solution:

Convention Class

public class TenantSharedDatabaseSharedSchemaConvention<T> : Convention where T : class
{
    public Expression<Func<T, object>> PrimaryKey { get; private set; }

    public Expression<Func<T, object>> TenantKey { get; private set; }

    public TenantSharedDatabaseSharedSchemaConvention(Expression<Func<T, object>> primaryKey, Expression<Func<T, object>> tenantKey)
    {
        this.PrimaryKey = primaryKey;
        this.TenantKey = tenantKey;

        base.Types<T>().Configure(m =>
        {
            var indexName = string.Format("IX_{0}_{1}", "Id", "CompanyId");

            m.Property(this.PrimaryKey).IsKey().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnOrder(0).HasColumnAnnotation("Index", new IndexAnnotation(new[] {
                new IndexAttribute(indexName, 0) { IsUnique = true }
            }));

            m.Property(this.TenantKey).IsKey().HasDatabaseGeneratedOption(DatabaseGeneratedOption.None).HasColumnOrder(1).HasColumnAnnotation("Index", new IndexAnnotation(new[] {
                new IndexAttribute(indexName, 1) { IsUnique = true }
            }));
        });
    }
}

Convetion Registration:

** On convention register i pass two properties, first the primary key and second is the tenant id.

modelBuilder.Conventions.Add(new TenantSharedDatabaseSharedSchemaConvention<BaseEntity>(m => m.Id, m => m.CompanyId));

Base Entity Model

public class BaseEntity
{
    public int Id { get; set; }

    public int CompanyId { get; set; }

    public Company Company { get; set; }
}

Order Entity (Example)

** Here i reference the currency and client with company and all work as expected...

public class Order : BaseEntity
{
    [Required]
    public int CurrencyId { get; set; }

    [ForeignKey("CompanyId, CurrencyId")]
    public virtual Currency Currency { get; set; }

    [Required]
    public int ClientId { get; set; }

    [ForeignKey("CompanyId, ClientId")]
    public virtual Client Client { get; set; }

    public string Description { get; set; }
}

  1. Is there any impact on performance?
  2. Is there any disadvantage compared to check every remote field?
  3. Someone have the same idea and/or problem and came with another solution?

IMHO, anywhere in the application, you will be having a mapping that states that user x is entitled to manage or access tenant(s) a(,b). In your businesses layer you should check it the user is ever entitled to see the data using the forged ID. In your case, the forged I'd will belong to another tenant that the user does not have access to, so you will return an unauthorized / security violation exception.

HTH