Question

As a simplified example I have users, products and customers. Users are allowed access to certain products and to certain customers.

I'm using an edmx-file to map my SQL Server to my code and get the data using linq. A typical query might look something like this:

from prod in ctx.Products
join userProduct in ctx.UserProduct
on prod.Id equals userProduct.ProductId

join user in ctx.UserProfile
on userProduct.UserId equals user.Id

where user.UserName == username  // <-- username is a method parameter
select new Product
{
    Id = prod.Id,
    DisplayText = prod.UserFriendlyText
}

Every time I need data from the database I must join towards the access rights table to EXCLUDE data the user does not have access to. This means that if someone (and it will happen eventually) forget to join towards the access table a user will see too much. Is there a way to INCLUDE data instead so that if I forget the access tables nothing is shown?

I've also been thinking about separating the different customers into different databases as their data will never be related to each other and it will be a small disaster if I leak data between customers. Leaking products between users from the same customer is bad but not as critical.

If it matters I'm in a C# MVC4 CQRS architecture with eventual consistency between the read and write side.

I've checked stack overflow for similar questions but all I could find was this unanswered one:

Était-ce utile?

La solution

How about using the Repository pattern, and forcing your Dev's to use it to make calls to the Database? This will promote code reuse and improve the maintainability of the app.

Because a method will be called from the repository you can control the code that interacts with the database, and force consistency, that way you can make sure that the access table is always used, and used as you wish.

Autres conseils

I have a similar problem in my database. 90% of my entities are "organisation dependent". My approach uses a generic base repository with methods like this:

    public virtual T Find(int id)
    {
        T e = Context.Set<T>().Find(id);

        var od = e as OrganisationDependent;
        if (od != null && od.OrganisationID != CurrentOrganisationID)
            return null;

        if (e == null)
            return null;

        return e;
    }

The "All" method was a particular issue. Solved by How to conditionally filter IQueryable

    private static readonly PropertyInfo _OrganisationIDProperty = ReflectionAPI.GetProperty<OrganisationDependent, int>(o => o.OrganisationID);

    private static Expression<Func<TOrg, bool>> FilterByOrganization<TOrg>(int organizationId)
    {
        //The FilterByOrganisation method uses the LINQ Expressions API to generate an expression that will filter on organisation id
        //This avoids having to cast the set using .AsEnumerable().Cast<OrganisationDependent>().Where(x => x.OrganisationID == CurrentOrganisationID).AsQueryable().Cast<T>();
        //https://stackoverflow.com/questions/20052827/how-to-conditionally-filter-iqueryable-by-type-using-generic-repository-pattern
        var item = Expression.Parameter(typeof(TOrg), "item");
        var propertyValue = Expression.Property(item, _OrganisationIDProperty);
        var body = Expression.Equal(propertyValue, Expression.Constant(organizationId));
        return Expression.Lambda<Func<TOrg, bool>>(body, item);
    }

    public virtual IQueryable<T> All
    {
        get
        {
            if (typeof(T).IsSubclassOf(typeof(OrganisationDependent)))
                return Context.Set<T>().Where(FilterByOrganization<T>(CurrentOrganisationID));

            return Context.Set<T>();
        }
    }

This closes off most of the places that a user could access someone else's data. But it doesn't filter navigational properties. So I have to add code to all navigation properties on non-organisation dependent entities to do that.

I don't want to separate my data into different database, but one day I will find out if it's feasible to create views filtered by organisation in different schemas - with the same name and structure as my tables, then switch schema according to user.....oh and I want to automatically create them for each new organisation and autmatically migrate them using code-first too....

And you could vote to Allow filtering for Include extension method here

If you're using a CQRS style architecture you can think about having one or more viewmodels per user that contains the products/customers that they have access to.

If you see yourself having to implement logic on the query side of CQRS that is a strong indication that you are doing something wrong.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top