The tack that I've taken is to use attributes on the controller action that examines the relation between the current user and the entity being requested, then either allows or disallows the action based on the results of the look up. I have a couple of different attributes depending on whether it goes through a join table or has a direct relationship. It uses reflection against, in my case the data context, but in yours the repository(ies) to get and check that the values match. I'll include the code below (which I've made some efforts to genericize so it may not compile). Note you could extend this to include some notion of permission as well (in the join table).
Code for the direct relationship attribute. It verifies that the current user is the owner of the record (the specified "id" attribute in the routing parameters matches the id of the current user in the user table).
[AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false )]
public class RoleOrOwnerAuthorizationAttribute : AuthorizationAttribute
{
private IDataContextFactory ContextFactory { get; set; }
private string routeParameter = "id";
/// <summary>
/// The name of the routing parameter to use to identify the owner of the data (participant id) in question. Default is "id".
/// </summary>
public string RouteParameter
{
get { return this.routeParameter; }
set { this.routeParameter = value; }
}
public RoleOrOwnerAuthorizationAttribute()
: this( null )
{
}
// this is for unit testing support
public RoleOrOwnerAuthorizationAttribute( IDataContextFactory factory )
{
this.ContextFactory = factory ?? DefaultFactory();
}
public override void OnAuthorization( AuthorizationContext filterContext )
{
if (filterContext == null)
{
throw new ArgumentNullException( "filterContext" );
}
if (AuthorizeCore( filterContext.HttpContext ))
{
SetCachePolicy( filterContext );
}
else if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// auth failed, redirect to login page
filterContext.Result = new HttpUnauthorizedResult();
}
else if (filterContext.HttpContext.User.IsInRole( "SuperUser" ) || IsOwner( filterContext ))
{
SetCachePolicy( filterContext );
}
else
{
ViewDataDictionary viewData = new ViewDataDictionary();
viewData.Add( "Message", "You do not have sufficient privileges for this operation." );
filterContext.Result = new ViewResult { MasterName = this.MasterName, ViewName = this.ViewName, ViewData = viewData };
}
}
private bool IsOwner( AuthorizationContext filterContext )
{
using (var dc = this.ContextFactory.GetDataContextWrapper())
{
int id = -1;
if (filterContext.RouteData.Values.ContainsKey( this.RouteParameter ))
{
id = Convert.ToInt32( filterContext.RouteData.Values[this.RouteParameter] );
}
string userName = filterContext.HttpContext.User.Identity.Name;
return dc.Table<UserTable>().Where( p => p.UserName == userName && p.ParticipantID == id ).Any();
}
}
}
This is the code for the association attribute, i.e., there exists an association in a join table between the id in the routing parameter and the user's id from the user table. Note that there is a dependency on the System.Linq.Dynamic code from the VS2008 Samples.
[AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false )]
public class RoleOrOwnerAssociatedAuthorizationAttribute : MasterEventAuthorizationAttribute
{
private IDataContextFactory ContextFactory { get; set; }
public RoleOrOwnerAssociatedAuthorizationAttribute()
: this( null )
{
}
// this supports unit testing
public RoleOrOwnerAssociatedAuthorizationAttribute( IDataContextFactory factory )
{
this.ContextFactory = factory ?? new DefaultDataContextFactory();
}
/// <summary>
/// The table in which to find the current user by name.
/// </summary>
public string UserTable { get; set; }
/// <summary>
/// The name of the property in the UserTable that holds the user's name to match against
/// the current context's user name.
/// </summary>
public string UserNameProperty { get; set; }
/// <summary>
/// The property to select from the UserTable to match against the UserEntityProperty on the JoinTable
/// to determine membership.
/// </summary>
public string UserSelectionProperty { get; set; }
/// <summary>
/// The join table that links users and the entity table. An entry in this table indicates
/// an association between the user and the entity.
/// </summary>
public string JoinTable { get; set; }
/// <summary>
/// The property on the JoinTable used to hold the entity's key.
/// </summary>
public string EntityProperty { get; set; }
/// <summary>
/// The property on the JoinTable used to hold the user's key.
/// </summary>
public string UserEntityProperty { get; set; }
/// <summary>
/// The name of the route data parameter which holds the group's key being requested.
/// </summary>
public string RouteParameter { get; set; }
public override void OnAuthorization( AuthorizationContext filterContext )
{
using (var dc = this.ContextFactory.GetDataContextWrapper())
{
if (filterContext == null)
{
throw new ArgumentNullException( "filterContext" );
}
if (AuthorizeCore( filterContext.HttpContext ))
{
SetCachePolicy( filterContext );
}
else if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// auth failed, redirect to login page
filterContext.Result = new HttpUnauthorizedResult();
}
else if (filterContext.HttpContext.User.IsInRole( "SuperUser" )
|| IsRelated( filterContext, this.GetTable( dc, this.JoinTable ), this.GetTable( dc, this.UserTable ) ))
{
SetCachePolicy( filterContext );
}
else
{
ViewDataDictionary viewData = new ViewDataDictionary();
viewData.Add( "Message", "You do not have sufficient privileges for this operation." );
filterContext.Result = new ViewResult { MasterName = this.MasterName, ViewName = this.ViewName, ViewData = viewData };
}
}
}
protected bool IsRelated( AuthorizationContext filterContext, IQueryable joinTable, IQueryable userTable )
{
bool result = false;
try
{
int entityIdentifier = Convert.ToInt32( filterContext.RouteData.Values[this.RouteParameter] );
int userIdentifier = this.GetUserIdentifer( filterContext, userTable );
result = joinTable.Where( this.EntityProperty + "=@0 and " + this.UserEntityProperty + "=@1",
entityIdentifier,
userIdentifier )
.Count() > 0;
}
catch (NullReferenceException) { }
catch (ArgumentNullException) { }
return result;
}
private int GetUserIdentifer( AuthorizationContext filterContext, IQueryable userTable )
{
string userName = filterContext.HttpContext.User.Identity.Name;
var query = userTable.Where( this.UserNameProperty + "=@0", userName )
.Select( this.UserSelectionProperty );
int userIdentifer = -1;
foreach (var value in query)
{
userIdentifer = Convert.ToInt32( value );
break;
}
return userIdentifer;
}
private IQueryable GetTable( IDataContextWrapper dc, string name )
{
IQueryable result = null;
if (!string.IsNullOrEmpty( name ))
{
DataContext context = dc.GetContext<DefaultDataContext>();
PropertyInfo info = context.GetType().GetProperty( name );
if (info != null)
{
result = info.GetValue( context, null ) as IQueryable;
}
}
return result;
}
}