zzzprojects / EntityFramework.DynamicFilters

Global filtering for Entity Framework.
https://entityframework-dynamicfilters.net/
MIT License
501 stars 86 forks source link

filtering data base on user premession or roles #117

Open mwinner313 opened 7 years ago

mwinner313 commented 7 years ago
 public class RestrictedItemGlobalFilter : GlobalFilter
     {
        public IIdentityManager IdentityManager { get; set; }
        public override void ApplyFilter(DbModelBuilder modelBuilder)
        {
            modelBuilder.Filter("RestrictItemsBaseOnAccsess", 
                (IAccessRestrictedItem x, List<string> roles) => x.AccessType == AccessType.Public
                         || roles.Any(x.AllowedRolesPremissionJsonString.Contains),
                 GetIdentityroles);
        }

        private List<string> GetIdentityroles()
        {
            return IdentityManager.GetCurrentIdentityRoles();
        }

    }

ex message:


Test method VarinaCmsV2.Data.Tests.AppDbContextPRoxyTests.ShouldFilterBaseOnRestrictedAccses threw exception: 
System.Data.Entity.Core.EntityCommandCompilationException: An error occurred while preparing the command definition. See the inner exception for details. ---> System.ArgumentException: DbExpressionBinding requires an input expression with a collection ResultType.
Parameter name: input
    at System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder.DbExpressionBuilder.BindAs(DbExpression input, String varName)
   at System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder.DbExpressionBuilder.Bind(DbExpression input)
   at EntityFramework.DynamicFilters.LambdaToDbExpressionVisitor.MapAnyOrAllExpression(MethodCallExpression node)
   at EntityFramework.DynamicFilters.LambdaToDbExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)
   at EntityFramework.DynamicFilters.LambdaToDbExpressionVisitor.VisitBinary(BinaryExpression node)
   at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression`1 node)
   at System.Linq.Expressions.Expression`1.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at EntityFramework.DynamicFilters.LambdaToDbExpressionVisitor.Convert(DynamicFilterDefinition filter, DbExpressionBinding binding, DbContext dbContext, DataSpace dataSpace)
   at EntityFramework.DynamicFilters.DynamicFilterQueryVisitorCSpace.BuildFilterExpressionWithDynamicFilters(IEnumerable`1 filterList, DbExpressionBinding binding, DbExpression predicate)
   at EntityFramework.DynamicFilters.DynamicFilterQueryVisitorCSpace.Visit(DbScanExpression expression)
   at System.Data.Entity.Core.Common.CommandTrees.DbScanExpression.Accept[TResultType](DbExpressionVisitor`1 visitor)
   at System.Data.Entity.Core.Common.CommandTrees.DefaultExpressionVisitor.VisitExpression(DbExpression expression)
   at System.Data.Entity.Core.Common.CommandTrees.DefaultExpressionVisitor.VisitExpressionBinding(DbExpressionBinding binding)
   at System.Data.Entity.Core.Common.CommandTrees.DefaultExpressionVisitor.VisitExpressionBindingEnterScope(DbExpressionBinding binding)
   at System.Data.Entity.Core.Common.CommandTrees.DefaultExpressionVisitor.Visit(DbProjectExpression expression)
   at EntityFramework.DynamicFilters.DynamicFilterQueryVisitorCSpace.Visit(DbProjectExpression expression)
   at System.Data.Entity.Core.Common.CommandTrees.DbProjectExpression.Accept[TResultType](DbExpressionVisitor`1 visitor)
   at EntityFramework.DynamicFilters.DynamicFilterInterceptor.TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
   at System.Data.Entity.Infrastructure.Interception.DbCommandTreeDispatcher.<Created>b__0(IDbCommandTreeInterceptor i, DbCommandTreeInterceptionContext c)
   at System.Data.Entity.Infrastructure.Interception.InternalDispatcher`1.Dispatch[TInterceptionContext,TResult](TResult result, TInterceptionContext interceptionContext, Action`2 intercept)
   at System.Data.Entity.Infrastructure.Interception.DbCommandTreeDispatcher.Created(DbCommandTree commandTree, DbInterceptionContext interceptionContext)
   at System.Data.Entity.Core.Common.DbProviderServices.CreateCommandDefinition(DbCommandTree commandTree, DbInterceptionContext interceptionContext)
   at System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlanFactory.CreateCommandDefinition(ObjectContext context, DbQueryCommandTree tree)
 --- End of inner exception stack trace ---
    at System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlanFactory.CreateCommandDefinition(ObjectContext context, DbQueryCommandTree tree)
   at System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlanFactory.Prepare(ObjectContext context, DbQueryCommandTree tree, Type elementType, MergeOption mergeOption, Boolean streaming, Span span, IEnumerable`1 compiledQueryParameters, AliasGenerator aliasGenerator)
   at System.Data.Entity.Core.Objects.EntitySqlQueryState.GetExecutionPlan(Nullable`1 forMergeOption)
   at System.Data.Entity.Core.Objects.ObjectQuery`1.<>c__DisplayClass7.<GetResults>b__6()
   at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)
   at System.Data.Entity.Core.Objects.ObjectQuery`1.<>c__DisplayClass7.<GetResults>b__5()
   at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute[TResult](Func`1 operation)
   at System.Data.Entity.Core.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
   at System.Data.Entity.Core.Objects.ObjectQuery`1.<System.Collections.Generic.IEnumerable<T>.GetEnumerator>b__0()
   at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at VarinaCmsV2.Data.Tests.AppDbContextPRoxyTests.ShouldFilterBaseOnRestrictedAccses() in E:\programming\projects\Source\VarinaCmsV2\VarinaCmsV2.Data.Tests\AppDbContextPRoxyTests.cs:line 39

and thank u alot for last answer

jcachat commented 7 years ago

First of all, I'm not sure what the GlobalFilter class or ApplyFilter method are. So I'm going to assume GlobalFilter is derived from DbContext and the ApplyFilter method is called from inside the OnModelCreating() method.

I also don't know the type of the 'AllowedRolesPremissionJsonString' property. From the name of it, it sounds like it's a string. But you are referencing a ".Contains" property. If it is a string, then that will be a problem since you are passing a reference to the Contains method when the .Any() is going to be expecting a string.

Also, you can't access a private method to retrieve the value for the "roles" parameter. When the expression value is needed, it will fail to evaluate that because it will not have a reference to the "GlobalFilters" instance. The proper way to handle that is to reference your DbContext derived class as described here: https://github.com/jcachat/EntityFramework.DynamicFilters#parameter-expressions.

So based on my assumptions above, I THINK your filter should look like this:

modelBuilder.Filter("RestrictItemsBaseOnAccsess", 
                (IAccessRestrictedItem x, List<string> roles) => x.AccessType == AccessType.Public
                         || roles.Any(r => r == x.AllowedRolesPremissionJsonString),
                 (GlobalFilter ctx) => ctx.IdentityManager.GetCurrentIdentityRoles());
mwinner313 commented 7 years ago

GlobalFilter is a base class that i will inject a list from it to my dbcontext from its constructor


 public class AppDbContext : IdentityDbContext<User, Role, Guid, UserLogin, UserRole, UserClaim>, IUnitOfWork
    {
        private readonly List<GlobalFilter> _globalFilters;
        public AppDbContext(List<GlobalFilter> globalFilters = null) : base("name=DefaultConnection")
        {
            _globalFilters = globalFilters;
           Database.SetInitializer(new MigrateDatabaseToLatestVersion<AppDbContext, Configuration>());

        }
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Add<MapProtectedFieldConvention>();
            modelBuilder.Configurations.AddFromAssembly(typeof(User).Assembly);
            modelBuilder.Ignore<BaseEntity>();
            FindAndRegisterEntitiesFromAssembly(typeof(BaseEntity).Assembly, typeof(DbEntity), modelBuilder);
            _globalFilters?.ForEach(x => x.ApplyFilter(modelBuilder));
            base.OnModelCreating(modelBuilder);
        }
    }

And any drived class applies its own logic and AllowedRolesPremissionJsonString property is a string field that stores json data like this

  public ObservableCollection<RolePremission> AllowedRolesPermissions
        {
            get
            {
                var additionalPermissions =
                       JsonConvert.DeserializeObject<ObservableCollection<RolePremission>>(AllowedRolesPremissionJsonString ?? string.Empty)
                       ?? new ObservableCollection<RolePremission>();
                additionalPermissions.CollectionChanged += SaveToPremissinBackingField;
                return additionalPermissions;
            }
            set { AllowedRolesPremissionJsonString = JsonConvert.SerializeObject(value); }
        }
        private void SaveToPremissinBackingField(object sender, NotifyCollectionChangedEventArgs e)
        {
            var premissions = sender as ObservableCollection<RolePremission>;
            if (premissions == null) throw new InvalidOperationException("Cant Observe Entitiy AllowedPremissions For DataPresistence");
            this.AllowedRolesPermissions = premissions;
        }

thanks .

mwinner313 commented 7 years ago

any way i need a way to decouple my db context from system requirement logics as the application grows dbcontext filters conut grows too and i tried to do some thing like entitytype configuratuion class does, decoupling Entiry filter configuration, i wish there was some thing like this

SomeEntiry: EntiryFilterConfiguration<interface or class>
{
      public SomeEntiry()
      {
           HasFilter(...)
       }
}

i am not good in english sorry about Misspellings and grammar thank you again.

mwinner313 commented 7 years ago
 modelBuilder.Filter("RestrictItemsBaseOnAccsess", 
                (IAccessRestrictedItem x, List<string> roles) => x.AccessType == AccessType.Public
                         || roles.Any(r=>r== x.AllowedRolesPremissionJsonString),
              (AppDbContext ctx)=> ctx.CurrentUserRoles);

i wrote this for sake of testing but the same problem exists : DbExpressionBinding requires an input expression with a collection ResultType. r== x.AllowedRolesPremissionJsonString is not applicable to my code but i wrote that for test i think problem comes from (roles.Any)

example of AllowedRolesPremissionJsonString value :: [ {"RoleName":"admin","AccessPremission":2}, {"RoleName":"author","AccessPremission":2} ] // 2 means just can see, comes from an enum because of that i used contains method and also it test something else

 modelBuilder.Filter("RestrictItemsBaseOnAccsess", 
                (IAccessRestrictedItem x, List<string> roles) => x.AccessType == AccessType.Public
                         || x.AllowedRolesPremissionJsonString.Contains("hello"),
                 GetIdentityroles);

and what i got is ::


Test method VarinaCmsV2.Data.Tests.AppDbContextPRoxyTests.ShouldFilterBaseOnRestrictedAccses threw exception: 
System.Data.Entity.Core.EntityCommandCompilationException: An error occurred while preparing the command definition. See the inner exception for details. ---> System.NotSupportedException: Unsupported object type used in Contains() - type = PropertyExpression
    at EntityFramework.DynamicFilters.LambdaToDbExpressionVisitor.MapContainsExpression(MethodCallExpression node)
   at EntityFramework.DynamicFilters.LambdaToDbExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)
   at EntityFramework.DynamicFilters.LambdaToDbExpressionVisitor.VisitBinary(BinaryExpression node)
   at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression`1 node)
   at System.Linq.Expressions.Expression`1.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at EntityFramework.DynamicFilters.LambdaToDbExpressionVisitor.Convert(DynamicFilterDefinition filter, DbExpressionBinding binding, DbContext dbContext, DataSpace dataSpace)
   at EntityFramework.DynamicFilters.DynamicFilterQueryVisitorCSpace.BuildFilterExpressionWithDynamicFilters(IEnumerable`1 filterList, DbExpressionBinding binding, DbExpression predicate)
   at EntityFramework.DynamicFilters.DynamicFilterQueryVisitorCSpace.Visit(DbScanExpression expression)
   at System.Data.Entity.Core.Common.CommandTrees.DbScanExpression.Accept[TResultType](DbExpressionVisitor`1 visitor)
   at System.Data.Entity.Core.Common.CommandTrees.DefaultExpressionVisitor.VisitExpression(DbExpression expression)
   at System.Data.Entity.Core.Common.CommandTrees.DefaultExpressionVisitor.VisitExpressionBinding(DbExpressionBinding binding)
   at System.Data.Entity.Core.Common.CommandTrees.DefaultExpressionVisitor.VisitExpressionBindingEnterScope(DbExpressionBinding binding)
   at System.Data.Entity.Core.Common.CommandTrees.DefaultExpressionVisitor.Visit(DbProjectExpression expression)
   at EntityFramework.DynamicFilters.DynamicFilterQueryVisitorCSpace.Visit(DbProjectExpression expression)
   at System.Data.Entity.Core.Common.CommandTrees.DbProjectExpression.Accept[TResultType](DbExpressionVisitor`1 visitor)
   at EntityFramework.DynamicFilters.DynamicFilterInterceptor.TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
   at System.Data.Entity.Infrastructure.Interception.DbCommandTreeDispatcher.<Created>b__0(IDbCommandTreeInterceptor i, DbCommandTreeInterceptionContext c)
   at System.Data.Entity.Infrastructure.Interception.InternalDispatcher`1.Dispatch[TInterceptionContext,TResult](TResult result, TInterceptionContext interceptionContext, Action`2 intercept)
   at System.Data.Entity.Infrastructure.Interception.DbCommandTreeDispatcher.Created(DbCommandTree commandTree, DbInterceptionContext interceptionContext)
   at System.Data.Entity.Core.Common.DbProviderServices.CreateCommandDefinition(DbCommandTree commandTree, DbInterceptionContext interceptionContext)
   at System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlanFactory.CreateCommandDefinition(ObjectContext context, DbQueryCommandTree tree)
 --- End of inner exception stack trace ---
    at System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlanFactory.CreateCommandDefinition(ObjectContext context, DbQueryCommandTree tree)
   at System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlanFactory.Prepare(ObjectContext context, DbQueryCommandTree tree, Type elementType, MergeOption mergeOption, Boolean streaming, Span span, IEnumerable`1 compiledQueryParameters, AliasGenerator aliasGenerator)
   at System.Data.Entity.Core.Objects.EntitySqlQueryState.GetExecutionPlan(Nullable`1 forMergeOption)
   at System.Data.Entity.Core.Objects.ObjectQuery`1.<>c__DisplayClass7.<GetResults>b__6()
   at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)
   at System.Data.Entity.Core.Objects.ObjectQuery`1.<>c__DisplayClass7.<GetResults>b__5()
   at System.Data.Entity.SqlServer.DefaultSqlExecutionStrategy.Execute[TResult](Func`1 operation)
   at System.Data.Entity.Core.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
   at System.Data.Entity.Core.Objects.ObjectQuery`1.<System.Collections.Generic.IEnumerable<T>.GetEnumerator>b__0()
   at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at VarinaCmsV2.Data.Tests.AppDbContextPRoxyTests.ShouldFilterBaseOnRestrictedAccses() in E:\programming\projects\Source\VarinaCmsV2\VarinaCmsV2.Data.Tests\AppDbContextPRoxyTests.cs:line 38
mwinner313 commented 7 years ago

any idea ?? i still have that problem yeah i know its alot of explanations :D

jcachat commented 7 years ago

I don't know what's wrong with the .Any() clause. That is supported. Look in the unit tests at the ChildCollectionFiltersTests.cs. There are a couple of tests there for .Any(). Maybe experiment with those and try to plug in your specific entities (as much as possible) to see if you can reproduce it there.

string.Contains() is not supported at the moment. That only works on an IEnumerable. That is something I can fix but that will only fix your .Contains("Hello") attempt.

Also, I think I see what you were trying to do in your original filter. Where you have this clause:

roles.Any(x.AllowedRolesPremissionJsonString.Contains)

I don't think that syntax can be supported and might be why it's complaining about needing a collection result type.

But once I add support for stirng.Contains() in the filter linq, this might work:

roles.Any(r => x.AllowedRolesPremissionJsonString.Contains(r))

I'll try to get that done in the next day or 2.

jcachat commented 7 years ago

I just pushed out an update (v 2.10.0) that adds support for string.Contains().

But, after another look, I don't think this is going to work at all. If you create the filter like this:

roles.Any(r => x.AllowedRolesPremissionJsonString.Contains(r))

that will create a subquery that attempts to evaluate this portion of that query:

r => x.AllowedRolesPremissionJsonString.Contains(r)

The problem there is that it's accessing the outer table ('x' in the linq query) which would not be valid sql since the inner sql query will not have any reference to the outer "x" entity set.

I also don't know if a valid query can be created against the parameter "roles" - it may only work on an entity collection.

papyr commented 3 years ago

Can you add support for filter with claim