ASP.NET MVC с LINQ to SQL:Как реализовать авторизацию для объектов в репозитории?
-
07-07-2019 - |
Вопрос
Учитывая доменно-ориентированный дизайн, как бы вы реализовали авторизацию пользователей в репозитории?В частности, как бы вы ограничили данные, которые вы можете видеть при входе в систему, предоставленном пользователем?
Допустим, у нас есть торговый центр электронной коммерции, в котором хранятся товары, но менеджер магазина обслуживает только некоторые товары.В этом случае не все продукты должны быть видны при любом логине.
Вопросы:
- Вы бы выбрали все продукты в репозитории, а затем использовали фильтр, чтобы ограничить возвращаемые продукты?
Like GetProducts("keyword: boat").restrictBy("myusername")?
- Вы бы прочитали логин из контекста контроллера в репозитории и пассивно отфильтровали результаты?
- Как бы вы сохранили связь между ролью пользователя и объектами, к которым она может иметь доступ?Вы бы просто сохранили ключ сущности и роль в таблице «многие-ко-многим», имея по одной записи для каждого продукта, к которой мог бы получить доступ каждый менеджер магазина?
Примеры кода или ссылки на примеры кода были бы фантастическими.Спасибо.
Решение
Мой подход заключается в использовании атрибутов действия контроллера, которое проверяет связь между текущим пользователем и запрашиваемой сущностью, а затем либо разрешает, либо запрещает действие на основе результатов поиска.У меня есть несколько разных атрибутов в зависимости от того, проходит ли он через таблицу соединений или имеет прямую связь.Он использует отражение, в моем случае, контекста данных, но в вашем репозитории, чтобы получить и проверить соответствие значений.Я приведу ниже код (который я приложил некоторые усилия, чтобы обобщить, чтобы он не мог скомпилироваться).Обратите внимание, что вы можете расширить это, включив в него также некоторое понятие разрешения (в таблице соединений).
Код атрибута прямой связи.Он проверяет, что текущий пользователь является владельцем записи (указанный атрибут «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();
}
}
}
Это код атрибута ассоциации, т. е. в таблице соединения существует связь между идентификатором в параметре маршрутизации и идентификатором пользователя из пользовательской таблицы.Обратите внимание, что существует зависимость от кода System.Linq.Dynamic из Образцы VS2008.
[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 для которого Том Кабански предложил структуру АОП, например ПостШарп.Это похоже на работоспособное решение для моих нужд, поэтому я планирую рассмотреть его более подробно.К сожалению, это не полный ответ на ваш вопрос, поскольку у меня нет примеров кода, которыми я мог бы поделиться.