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?