views:

1250

answers:

1

I'd like to extend the AuthorizeAttribute in ASP.NET MVC so that it supports the concept of a user's authorization being based on their role membership OR "ownership" of the data in question. I'm using LINQ2SQL for data access. There is a similar question at http://stackoverflow.com/questions/390930/asp-net-mvc-authorization-using-roles.

What I'm thinking is adding EntityProperty, UserProperty, RouteParameter, and JoinTableType parameters to my extended AuthorizeAttribute class. The first two would be the names of the properties in the join table to check. The RouteParameter would be the name of the route parameter to extract for the value of the EntityProperty to match. I'd obtain the user id from the user's table using the current user name. The JoinTableType parameter would be the type of the table in the datacontext that contains the Entity and UserProperties that the route parameter value and user id must match.

The basic idea is, in pseudocode:

 if authorizecore result is true
    user is granted access based on role
 else if user is not authenticated
    redirect to logon
 else if user is related to request
    user is granted access based on relation
 else
    user is not authorized, redirect to not authorized error view

The is related test would look like:

 result = false
 find the matching user from user name
 find the entity property value in route data
 if user exists and entity property value exists
    get table from context matching join table type
    if table exists
       find row in table matching user id and entity property value
       if row exists
          result = true
       endif
    endif
 endif


 return result

My question is how do I use the type and property names in constructing the LINQ query? Or am I going to have to do all this with object and reflection. I'm really searching for ideas on how to make this easier so other suggestions would be appreciated as well. I'd prefer to use the attribute rather than embed the checking directly in the action to keep this consistent with how I handle my other actions.

+1  A: 

I was able to use the Dynamic Linq extensions from the VS2008 samples to do this in a pretty reasonable fashion. Here's the code representing the second pseudocode sample from above. It passes my initial unit test, but I'll need to make it more robust.

Usage:

[RoleOrMemberAuthorization( UserTable = "Participants",
                            UserNameProperty = "UserName",
                            UserSelectionProperty = "ParticipantID",
                            JoinTable = "GroupLeaders",
                            EntityProperty = "GroupID",
                            UserEntityProperty = "ParticipantID",
                            RouteParameter = "id",
                            Roles = "SuperUser, ViewGroups" )]

Called as:

else if (IsRelated( filterContext,
                    this.GetTable( dc, this.JoinTable ), 
                    this.GetTable( dc, this.UserTable ) ))
{
    SetCachePolicy( filterContext );
}

Relevant source:

protected bool IsRelated( AuthorizationContext filterContext,
                          IQueryable joinTable,
                          IQueryable userTable )
{
    bool result = false;
    try
    {
        object entityIdentifier = filterContext.RouteData
                                               .Values[this.RouteParameter];
        object userIdentifier = this.GetUserIdentifer( filterContext, userTable );
        if (userIdentifier != null && entityIdentifier != null)
        {
            result = joinTable.Where( this.EntityProperty + "=@0 and "
                                      + this.UserEntityProperty + "=@1",
                                      entityIdentifier,
                                      userIdentifier )
                              .Count() > 0;
        }
    }
    catch (NullReferenceException) { }
    return result;
}

private object GetUserIdentifer( AuthorizationContext filterContext,
                                 IQueryable userTable )
{
    string userName = filterContext.HttpContext.User.Identity.Name;

    var query = userTable.Where( this.UserNameProperty + "=@0", userName )
                         .Select( this.UserSelectionProperty );

    object userIdentifer = null;
    foreach (var value in query)
    {
        userIdentifer = value;
        break;
    }
    return userIdentifer;
}

private IQueryable GetTable( DataContext context, string name )
{
    PropertyInfo info = context.GetType().GetProperty( name );
    if (info != null)
    {
        return info.GetValue( context, null ) as IQueryable;
    }
    else
    {
        return null;
    }
}
tvanfosson