none
EF 6.1.1, Interceptors and visitors RRS feed

  • Question

  • I'm trying to implement authorization by interceptors in Entity Framework 6.1.1.

    This is my code:

    public class DemoDbConfiguration : DbConfiguration
    {
    	public DemoDbConfiguration()
    	{
    		AddInterceptor(new AuthorizationInterceptor());
    	}
    }
    public class DemoContext : DbContext
    {
    	public DbSet<Demo> Demos { get; set; }
    	public DbSet<Auth> Auths { get; set; }
    }
    public class Demo
    {
    	public int Id { get; set; }
    	public string Name { get; set; }
    	public int DomainId { get; set; }
    	[ForeignKey("DomainId")]
    	public Domain Domain { get; set; }
    }
    public class Domain
    {
    	public int Id { get; set; }
    	public string Name { get; set; }
    }
    public class Auth
    {
    	[Key, Column(Order = 0)]
    	public int DomainId { get; set; }
    	[Key, Column(Order = 1)]
    	public int IdentityId { get; set; }
    	[Key, Column(Order = 2)]
    	public int FunctionKey { get; set; }
    	public bool? Value { get; set; }
    }
    public class AuthorizationInterceptor : IDbCommandTreeInterceptor
    {
    	public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
    	{
    		if (interceptionContext.OriginalResult.DataSpace == DataSpace.SSpace)
    		{
    			var queryCommand = interceptionContext.Result as DbQueryCommandTree;
    			if (queryCommand != null)
    			{
    				var newQuery = queryCommand.Query.Accept(new AuthorizationQueryVisitor());
    				interceptionContext.Result = new DbQueryCommandTree(
    					queryCommand.MetadataWorkspace,
    					queryCommand.DataSpace,
    					newQuery);
    			}
    
    		}
    	}
    }
    public class AuthorizationQueryVisitor : DefaultExpressionVisitor
    {
    	public override DbExpression Visit(DbScanExpression main)
    	{
    		//TODO - Use annotations to determine if ElementType is Authorizable
    		if (main.Target.ElementType.Name == "Demo")
    		{
    			var mainBinding = main.BindAs("main");
    			var auth = main.Target.EntityContainer.GetEntitySetByName("Auth", false).Scan();
    			var authBinding = auth.BindAs("auth");
    			var join = mainBinding.InnerJoin(
    				authBinding,
    				mainBinding.Variable.Property("DomainId").Equal(
    					authBinding.Variable.Property("DomainId")));
    			var selectPairs = main.Target.ElementType.Members
    				.Select(mem =>
    					new KeyValuePair<string, DbExpression>(
    						mem.Name,
    						mainBinding.Variable.Property(mem.Name)));
    			var select = join.Select(ex => DbExpressionBuilder.NewRow(selectPairs));
    			return select;
    			//TODO - Implement filter (WHERE clause)
    		} else return base.Visit(main);
    	}
    }

    I want to reproduce this SQL statement:

    SELECT d.Id, d.Name, d.DomainId
    FROM Demoes as d
    INNER JOIN Auths as a
    ON d.DomainId = a.DomainId
    WHERE FunctionKey = 1 AND IdentityId = 1 AND Value = 1

    WHERE clause is not implemented yet and the constants must be replaced by proper values.

    I'm getting this error:

    ArgumentException: The referenced variable 'main' is not defined in the current scope.

    How i can define the 'main' and 'auth'' variables?

    What is wrong there?

    Is this possible to implement with this approach?

    Thanks.






    • Edited by Oscar Jim Thursday, June 26, 2014 1:47 PM
    Thursday, June 26, 2014 1:18 PM

All replies

  • Hello,

    For reproducing this SQL statement, I think using the linq2entities is enough as:

    var result = (from demo in db.Demos
    
                                  join auth in db.Auths on demo.DomainId equals auth.DomainId
    
                                  where auth.FunctionKey == 1 && auth.IdentityId == 1 && auth.Value == true
    
                                  select demo).ToList();
    

    As you said you want to implement authorization by interceptors, I don’t understand why you need to do it.

    Regards.


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Friday, June 27, 2014 7:28 AM
    Moderator
  • Hello Fred:

    Yes, your linq sentence is equivalent to my sql code but i'm trying to implement this functionality with interceptors and visitors. 

    I can modify all methods in all controllers for implement the authorization filters but i want implement it in a general manner for abstract and centralize the implementation on entire system at one time.

    I think that this code is compliant since latest stable version (6.1.1) of Entity Framework. I was inspired with this session at Build 2014 by Rowan Miller: 
    http://channel9.msdn.com/Events/TechEd/NorthAmerica/2014/DEV-B417#fbid=

    Rowan show us how implement soft delete functionality using interceptors and visitors. All entities in our solution can be marked with SoftDeleteAttribute and automatically the delete sentences wil be converted into update sentences to mark database rows as deleted but not deleted definitively from database.

    I want to implement similar functionality but for authorization purpose.

    Regards.



    • Edited by Oscar Jim Friday, June 27, 2014 9:05 AM
    Friday, June 27, 2014 8:56 AM
  • Hi,

    For if your idea is feasible, you can add a comment at below the video.

    Monday, June 30, 2014 7:01 AM
  • Hello Oscar,

    Were you able to accomplish what you were trying?. I have the same requirement...

    Regards,

    Yosel

    Wednesday, December 31, 2014 8:24 PM
  • No, sorry Yosel :(

    I have decided implement this out of interception system, but i'm building the expression on the fly anyway.

    This is the code to check if a field of an entity, is contained in a values array:

            private Expression<Func<TEntity, bool>> BuildForeignKeysContainsPredicate<T>(List<T> foreignKeys, PropertyInfo property)
            {
                Expression<Func<TEntity, bool>> result = default(Expression<Func<TEntity, bool>>);
    
                try
                {
                    ParameterExpression entityParameter = Expression.Parameter(typeof(TEntity));
                    ConstantExpression foreignKeysParameter = Expression.Constant(foreignKeys, typeof(List<T>));
                    MemberExpression memberExpression = Expression.Property(entityParameter, property);
                    Expression convertExpression = Expression.Convert(memberExpression, typeof(T));
                    MethodCallExpression containsExpression = Expression.Call(foreignKeysParameter
                        , "Contains", new Type[] { }, convertExpression);
                    result = Expression.Lambda<Func<TEntity, bool>>(containsExpression, entityParameter);
                    if (typeof (INullable).IsAssignableFrom(property.PropertyType) ||
                        (property.PropertyType.IsGenericType &&
                         property.PropertyType.GetGenericTypeDefinition() == typeof (Nullable<>)))
                    {
                        var arg = Expression.Parameter(typeof (TEntity));
                        var prop = Expression.Property(arg, property);
                        result = Expression.Lambda<Func<TEntity, bool>>(
                            Expression.NotEqual(
                                prop,
                                Expression.Constant(null, prop.Type)),
                            arg)
                            .And(result);
                    }
                } catch (Exception ex)
                {
                    throw ex;
                }
    
                return result;
            }

    I try to explain you this method:
    The method gets an array of values to check and a PropertyInfo that represent the field (column in SQL) in which we make the "Contains" check.
    Example:
    BuildForeignKeysContainsPredicate(new[]{1,2},typeof(Entity).GetProperty("Id"));

    On this call, the method returns an expression that checks if field "Id" have value "1" or "2"
    In addition, if type of property defined by PropertyInfo parameter is nullable, the result expression contains a "AND" clausule that exclude all null values

    I suppose that this is not the same that the original topic, but i hope help you.

    Happy new year!

    Friday, January 2, 2015 10:50 AM