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:
- The app is not free, 100% for sure the malicious user is a client.
- All the primary keys/identity are integers (Guid solve this problem but we can't change right now).
- The app use shared database and shared schema.
- All the tenants are business group wich own several shops.
- 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; }
}
- Is there any impact on performance?
- Is there any disadvantage compared to check every remote field?
- 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