LINQ to SQLを使用したASP.NET MVC:リポジトリのエンティティへの承認を実装する方法
-
07-07-2019 - |
質問
ドメイン駆動設計を念頭に置いて、リポジトリにユーザー認証をどのように実装しますか?具体的には、ユーザーが提供したログインで表示できるデータをどのように制限しますか?
商品を保管するeコマースモールがあり、特定のストアマネージャーが一部の商品のみを管理しているとします。この場合、特定のログインですべての製品が表示されるわけではありません。
質問:
- レポですべての製品を選択し、フィルターを使用して、返される製品を制限しますか?
GetProducts(" keyword:boat")。restrictBy(" myusername")??
- ログインをリポジトリ内のcontrollercontextから読み取り、結果を受動的にフィルタリングしますか?
- ユーザーロールとアクセスできるエンティティとの関係をどのように保存しますか?エンティティキーとロールを多対多のテーブルに格納し、各ストアマネージャーがアクセスできる製品ごとに1つのレコードを保持しますか?
コード例またはコード例へのリンクは素晴らしいでしょう。ありがとう。
解決
私が取ったタックは、現在のユーザーと要求されているエンティティとの関係を調べるコントローラーアクションの属性を使用し、ルックアップの結果に基づいてアクションを許可または拒否することです。結合テーブルを経由するか直接的な関係があるかによって、いくつかの異なる属性があります。私の場合はデータコンテキストに対してリフレクションを使用しますが、リポジトリでは値が一致することを取得および確認します。以下のコードを含めます(コンパイルできないように汎用化する努力をしました)。これを拡張して、許可の概念も(結合テーブルに)含めることができます。
直接関係属性のコード。現在のユーザーがレコードの所有者であることを確認します(ルーティングパラメーターで指定された" id"属性がuserテーブルの現在のユーザーのidと一致します)。
[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();
}
}
}
これは、関連付け属性のコードです。つまり、ルーティングパラメーターのIDとユーザーテーブルのユーザーIDの間に結合テーブルに関連付けが存在します。 VS2008サンプルのSystem.Linq.Dynamicコードに依存していることに注意してください。
[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;
}
}
他のヒント
非常に同様の質問を S#arp Architectureニュースグループの Tom Cabanski は、 PostSharp 。これは私のニーズに対する実行可能なソリューションのように見えるので、私はそれをより詳細に調べることを計画しています。残念ながら、これはあなたの質問に対する完全な答えではありません。共有するコード例がありません。