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 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.
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;
}
}
来源:https://stackoverflow.com/questions/447464/asp-net-mvc-authorization-based-on-role-membership-or-data-relation-ownership