Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

filtering data base on user premession or roles #117

Open
mwinner313 opened this issue Jul 22, 2017 · 8 comments
Open

filtering data base on user premession or roles #117

mwinner313 opened this issue Jul 22, 2017 · 8 comments

Comments

@mwinner313
Copy link

mwinner313 commented Jul 22, 2017

 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
Copy link
Collaborator

jcachat commented Jul 22, 2017

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
Copy link
Author

mwinner313 commented Jul 23, 2017

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
Copy link
Author

mwinner313 commented Jul 23, 2017

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
Copy link
Author

mwinner313 commented Jul 23, 2017

 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
Copy link
Author

mwinner313 commented Jul 25, 2017

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

@jcachat
Copy link
Collaborator

jcachat commented Jul 25, 2017

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
Copy link
Collaborator

jcachat commented Jul 26, 2017

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
Copy link

papyr commented Feb 25, 2021

Can you add support for filter with claim

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

3 participants