0

It appears I am trying to achieve the impossible: from within a (computed) property of my (entity framework) entity I am trying to check whether the logged on user has a certain claim. To be more precise, it does not work when trying to use this property from within a Linq DB query.

The Linq query is nothing special:

var query = DbContext.Organizations.Where(x => !x.DeleteDate.HasValue);

The data retrieval is triggered by an automapper projection:

var dto = await query.ProjectToListAsync<MyDto>(Mapper.ConfigurationProvider);

I have added this property in my Organization entity:

[Computed]
public bool CanCancel => !CancelDate.HasValue 
                             && identity.Claims.Any(c => c.Type == permission.ToString());  // identity is of type ClaimsIdentity, permission is an enum from our project

(The Computed attribute comes from the DelegateCompiler library which allows the decompilation of a delegate or a method body to its lambda representation. If you mark a method of your entity with this, Automapper can use it in a query during a projection. We use this technique a lot in our project, it is very useful!)

Unfortunately it does not look like ClaimsIdentity.Claims can be decompiled. Running the code throws the error:

Unable to create a constant value of type 'System.Security.Claims.Claim'. Only primitive types or enumeration types are supported in this context.

I suppose it has something to do with the internals of this Claims property:

public virtual IEnumerable<Claim> Claims
{
  get
  {
    for (int i = 0; i < this.m_instanceClaims.Count; ++i)
      yield return this.m_instanceClaims[i];
    if (this.m_externalClaims != null)
    {
      for (int j = 0; j < this.m_externalClaims.Count; ++j)
      {
        if (this.m_externalClaims[j] != null)
        {
          foreach (Claim claim in this.m_externalClaims[j])
            yield return claim;
        }
      }
    }
  }
}

But I fail to see how I could solve this. Does anyone have an idea?

Thanks in advance!

johey
  • 1,139
  • 1
  • 9
  • 25
  • I tried to avoid the call to the `ClaimsIdentity.Claims` by directly accessing the (private) m_instanceClaims property (via a `MemberExpression`) but this caused another problem ("LINQ to Entities Does not Support Invoke"). I tried to bypass this problem via LinqKit's AsExpandable (cf. https://stackoverflow.com/a/11313924/6996150) but that does not seem to make any difference (same error). For now I give up. I think it's possible but I ran out of time. :-| – johey Nov 26 '18 at 11:36

1 Answers1

0

Okay, I didn't find a perfect solution for this problem, but I was able to workaround it, by:

a) creating a static property that checks the claim:

private static bool CanCancel => identity.HasPermission(AppPermission.CanCancel);

(HasPermission is an extension method that uses the ClaimsIdentity's HasClaim method to check that the identity has the given permission)

b) accessing that properties from my computed property:

[Computed] 
public bool IsCancelable => !CancelDate.HasValue && CanCancel;

As this property no longer contains an input variable (e.g. the permission to check), Linq to Entities can now correctly translate it to a query.

johey
  • 1,139
  • 1
  • 9
  • 25