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.
Related
I'm trying to map a relation N:N for an entity which has some other information. In fact, to brief you better I have the following scenario:
A user can apply as many times as he wants for a exam and this application saves the final result. (That's why i didn't map the key with this to classes)
Looking for this over the internet I found some solutions regarding the creation of Id properties to save information about the Foreign Key besides the property itself. As I don't agree with this solution because I don't believe that we have to change our Model to satisfy ORM needs, I would like to know if you guys have another solution for me.
The following code is a piece of the classes I want to map. Currently, I didn't configure collections in the main classes and when I try to save the application I receive a key violation in the database because it tries to save the User/Exam in the database again.
Sorry if it is a silly question and thanks for your help.
public class User
{
public int Id { get; set; }
public string FullName { get; set; }
}
public class Exam
{
public int Id { get; set; }
public string Description { get; set; }
}
public class Application
{
public int Id { get; set; }
public virtual User User { get; set; }
public virtual Exam Exam { get; set; }
public int Result { get; set; }
}
public class Order
{
public int Id {get;set;}
[DisplayName("User")]
public long UserId { get; set; }
[ForeignKey("UserId")]
public virtual User User { get; set; }
public decimal Amount { get; set; }
}
With IEnumurable
public class User
{
public int Id{get;set;}
public virtual IEnumerable<Order> Orders { get; set; }
}
public User GetWithOrders()
{
var myUser=UserRepository.GetByEmail("email#email.com");
myUser.Orders=OrderRepository.GetByUserId(myUser.Id);
return myUser;
}
With ICollection
public class User
{
public int Id{get;set;}
public virtual ICollection<Order> Orders { get; set; }
}
public User GetWithOrders()
{
var myUser=UserRepository.GetByEmail("email#email.com");
return myUser;
}
I don't have lazy loading using IEnumerable for a navigation property. Therefore, I have to get the orders for this user with another query.
I have navigation with ICollection. So I can reach orders from user. This seems cool. But then I can add new orders to the user in the Controller without using service or repository.
It's kind of manipulating data on controller level. Is this anti-pattern?
But [with ICollection] I can add new order in Controller without using service or repository.
You mean you can do this (assuming there's a viewmodel for adding an order to a user and a SaveChanges() somewhere):
public class UserController
{
public ActionResult AddUserOrder(AddUserOrderModel addOrder)
{
User user = User.GetByEmail(addOrder.UserEmail);
user.Orders.Add(addOrder.Order);
User.SaveChanges();
}
}
And especially that you can do user.Orders.Add(...), then that's a side effect of exposing entity types from your service or repository layer.
If you want to avoid that, you'd have to define and expose a business object containing the members you want to expose:
public class UserBLL
{
public int Id { get; private set; }
public IEnumerable<Order> Orders { get { return _orders.AsEnumerable(); } }
private IEnumerable<Order> _orders;
public UserBLL(User user)
{
Id = user.Id;
_orders = user.Orders;
}
public void AddOrder(Order order)
{
_orders.Add(order);
}
}
There's not a real choice here. ICollection is needed by EF to control some of it's aspects like binding query results and lazy loading. By using IEnumerable, you're essentially turning all this functionality off, but along with it, EF's understanding of your underlying structure. When you generate migrations, EF will not generate any requisite underlying join tables for M2M relationships, foreign keys on related tables, etc.
Long and short, use ICollection. While you're correct that this allows you to add items by simply adding them to the collection on a related entity, sans-DAL, they still can't be saved without access to the context. If you've set up your DAL correctly, that's only available through the DAL, itself, so you still have to pass the entity back into your DAL pipeline to perpetuate any of these changes. In other words, don't worry about it.
I have a simple relation between a User model and a Role model.
public class User {
{
public User() {
Roles = new HahSet<Role>();
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
public class Role {
public Role()
{
Users = new HashSet<User>();
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public virtual ICollection<User> Users { get; set; }
}
On my development system, when querying user.Roles, I get the intended result of 3 Roles. When deployed to a test environment, the same query returns 0 Roles.
I have logged and monitored both environments. Both systems
Run the same code base and record the same logging statements
Execute the same SQL queries against an identical database (via both logging and SQL Profiler), so I can see it requesting the data from the database
Have the required database records
Are able to load User, but the test environment does not turn the Roles into objects on the user.Roles collection
Edit: Running the SQL queries manually on both development and testing databases return the expected results.
As far as I can, my environment and configs are identical.
My question is, what sort of environmental and/or configuration areas can I investigate to work out what is happening in the test environment?
To be on the safe side can you just verify the two objects.. They have to look something like this...
public class User {
{
public User() {
Roles = new HahSet<Role>();
}
[Key]
public int UserId { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
public class Role
{
[Key]
public int RoleId { get; set; }
[ForeignKey("User ")]
public int UserId { get; set; }
public User User { get; set; }
}
I have a couple of classes (for this example anyway) that use code first with the entity framework to connect to the database.
public class Customer
{
[Key]
public long CustomerId { get; set; }
public string CompanyName { get; set; }
...
public virtual List<Contact> Contacts { get; set; }
}
public class Contact
{
[Key]
public long ContactId { get; set; }
public string Forename { get; set; }
...
public long CustomerId { get; set; }
public virtual Customer Customer { get; set; }
}
When I hook these up in my context class directly to the db the foreign key relationships hook up fine and I can access the collection of contacts from within the customer class.
class RemoteServerContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Contact> Contacts { get; set; }
...
}
My problem is that these database tables are used by various different systems and are massive. In order to increase efficiency I have overridden the default behaviour to point at a view (and also a stored proc elsewhere) rather than directly at the table.
public IEnumerable<Customer> Customers ()
{
return Database.SqlQuery<Customer>("SELECT * FROM vw_CustomerList");
}
public IEnumerable<Contact> Contacts()
{
return Database.SqlQuery<Contact>("SELECT * FROM vw_ContactsList");
}
I have made sure that in each of the views I have included the foreign key fields: CustomerId and ContactId.
When I do this however the class joins appear to be lost - there's always a null when I drill into either of the objects where it should be pointing to the other one. I have tried to set up what the foreign key field should point to but this doesn't seem to help either.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Contact>().HasRequired(p => p.Customer)
.WithMany()
.HasForeignKey(k => k.CustomerId);
}
Is there a way to establish the connection when overriding the default behaviour?
There is no overriding in this case. If you removed
public DbSet<Customer> Customers { get; set; }
and replaced it with
public IEnumerable<Customer> Customers ()
{
return Database.SqlQuery<Customer>("SELECT * FROM vw_CustomerList");
}
you have completely changed the behavior. The first uses entities and full power of EF. The second is only helper to execute custom SQL. Second without first or without defining entity in OnModelCreating doesn't use Customer as mapped entity at all - it uses it as any normal class (only mapped entities can use features like lazy loading).
Because your Customer is now mapped to view you cannot use your former Customer class used with table. You must define mapping of Customer to a view by cheating EF:
modelBuilder.Entity<Customer>().ToTable("vw_ContactsList"); // EF code fist has no view mapping
Once you have this you can try again using:
public DbSet<Customer> Customers { get; set; }
Unless your view is updatable you will get exception each time you try to add, update or delete any customer in this set. After mapping relation between Customer and Contact mapped to views your navigation properties should hopefully work.
The problem with SqlQuery is the way how it works. It returns detached entities. Detached entities are not connected to the context and they will not lazy load its navigation properties. You must manually attach each Customer instance back to context and to do that you again need DbSet.
My simplified domain model looks something like this:
public abstract class Entity<IdK>
{
public virtual IdK Code { get; protected set; }
}
public class Contact : Entity
{
public virtual string Name { get; set; }
public virtual Company Company { get; set; }
}
public class Company : Entity
{
public virtual string Name { get; set; }
}
and I've defined a viewmodel:
public ContactViewModel()
{
public Guid Code { get; set; }
public int Version { get; set; }
public string Name { get; set; }
public string Company { get; set; }
public List<SelectListItem> Companies { get; set; }
}
to manage my contacts in a view.
Since I want the user to be able to choose from a list of companies I've added a list of SelectedListItem which will be rendered in my view like this:
<%=Html.ListBoxFor(m => m.Company, (List<System.Web.Mvc.SelectListItem>)Model.Companies)%>
Now, when the user submits my form I remap my viewmodel with my model before I save it.
I populate my Contact and use the id of the ContactViewModel.Company to create an object of type Company to associate with the property of the Contact class.
Since I don't want to fetch the whole company from the database I just fill the id.
When I persist my contact, though, I get an exception: "not-null property references a null or transient Domain.Contact.Company".
What is the best solution to manage lookups and persistence with MVC + Nhibernate?
Do you have any suggestions from your experience?
Unfortunately with NHibernate and lookups you can't just assign the ID property to a new instance of the Company object and then assign that Company object to the Contact.
Generally what I would do is in my repository, assuming that you can't change the Company information when saving a contact is something like this:
public Contact Save(Contact contact)
{
if(contact.Company.Id > 0)
contact.Company = Session.Load<Company>(contact.Company.Id);
Session.SaveOrUpdate(contact);
}
I generally find this allows you to encapsulate the logic of loading the Company and also allows you to keep it all wrapped up nicely in a single session.
Using Session.Load in this manner avoids hitting the database as described here
If you don't do this, what you're essentially saying to NHibernate is that you have a company object which you have assigned an ID and now want to save it with all the properties set to Null or empty string values or whatever and that is not what you want.
Alternatively you could create a Save specific Domain Object that looks like this:
public abstract class Entity<IdK>
{
public virtual IdK Code { get; protected set; }
}
public class SavableContact : Entity
{
public virtual string Name { get; set; }
public virtual IdK CompanyId { get; set; }
}
Which maps directly to the Contact table in your database so that when you Save this entity you can literally just map back the CompanyId from your view model and NHibernate will only save that value back and not care at all about the company objects.
It's a case of working out what works best for you. I personally prefer the first option as the extra bit of logic helps simplify the domain model, however if you're creating and exposing a public API then the second method might make more sense.