Я пытаюсь защитить свои маршруты MVC от ряда пользователей, которые соответствуют ряду критериев. Так как MVC, кажется, использует атрибуты вполне немного, и Steven Sanderson использует один для расширяемости безопасности в его про книге MVC, я начал возглавлять вниз этот маршрут, но я хотел бы определить правило контекстуально на основе действия, к которому я применяю его.
Некоторые действия для сотрудников только, некоторые не.
Некоторые действия для company1 только, некоторые не.
Таким образом, я думал этот тип об использовании...
[DisableAccess(BlockUsersWhere = u => u.Company != "Acme")]
public ActionResult AcmeOnlyAction()
{
...
}
[DisableAccess(BlockUsersWhere = u => u.IsEmployee == false)]
public ActionResult EmployeeOnlyAction()
{
...
}
Выглядит довольно чистым мне и действительно довольно легок реализовать, но я получаю следующую ошибку компилятора:
'BlockUsersWhere' не является допустимым именованным аргументом атрибута, потому что это не допустимый тип параметра атрибута
По-видимому, Вы не можете использовать Func в качестве аргумента атрибута. Какие-либо другие предложения для обхождения этой проблемы или чего-то еще, что обеспечивает простое использование, которое мы полюбили в наших проектах MVC?
Предложение Некроса будет работать, однако вам придется вызывать его помощник SecurityGuard
в теле каждого метода действия.
Если вы по-прежнему хотите использовать декларативный подход на основе атрибутов (преимущество которого состоит в том, что вы можете применить атрибут ко всему Контроллеру), вы можете написать свой собственный AuthorizeAttribute
public class CustomAuthorizeAttribute : AuthorizeAttribute {
public bool EmployeeOnly { get; set; }
private string _company;
public string Company {
get { return _company; }
set { _company = value; }
}
protected override bool AuthorizeCore(HttpContextBase httpContext) {
return base.AuthorizeCore(httpContext) && MyAuthorizationCheck(httpContext);
}
private bool MyAuthorizationCheck(HttpContextBase httpContext) {
IPrincipal user = httpContext.User;
if (EmployeeOnly && !VerifyUserIsEmployee(user)) {
return false;
}
if (!String.IsNullOrEmpty(Company) && !VerifyUserIsInCompany(user)) {
return false;
}
return true;
}
private bool VerifyUserIsInCompany(IPrincipal user) {
// your check here
}
private bool VerifyUserIsEmployee(IPrincipal user) {
// your check here
}
}
Тогда вы должны его использовать. следующим образом
[CustomAuthorize(Company = "Acme")]
public ActionResult AcmeOnlyAction()
{
...
}
[CustomAuthorize(EmployeeOnly = true)]
public ActionResult EmployeeOnlyAction()
{
...
}
Поскольку в параметрах атрибутов можно использовать только константы, типы или инициализаторы массивов, они, вероятно, не будут работать или, по крайней мере, не будут такими гибкими.
В качестве альтернативы вы можете использовать что-то подобное, что я придумал при решении этой проблемы.
Это API:
public static class SecurityGuard
{
private const string ExceptionText = "Permission denied.";
public static bool Require(Action<ISecurityExpression> action)
{
var expression = new SecurityExpressionBuilder();
action.Invoke(expression);
return expression.Eval();
}
public static bool RequireOne(Action<ISecurityExpression> action)
{
var expression = new SecurityExpressionBuilder();
action.Invoke(expression);
return expression.EvalAny();
}
public static void ExcpetionIf(Action<ISecurityExpression> action)
{
var expression = new SecurityExpressionBuilder();
action.Invoke(expression);
if(expression.Eval())
{
throw new SecurityException(ExceptionText);
}
}
}
public interface ISecurityExpression
{
ISecurityExpression UserWorksForCompany(string company);
ISecurityExpression IsTrue(bool expression);
}
Затем создайте построитель выражений:
public class SecurityExpressionBuilder : ISecurityExpression
{
private readonly List<SecurityExpression> _expressions;
public SecurityExpressionBuilder()
{
_expressions = new List<SecurityExpression>();
}
public ISecurityExpression UserWorksForCompany(string company)
{
var expression = new CompanySecurityExpression(company);
_expressions.Add(expression);
return this;
}
public ISecurityExpression IsTrue(bool expr)
{
var expression = new BooleanSecurityExpression(expr);
_expressions.Add(expression);
return this;
}
public bool Eval()
{
return _expressions.All(e => e.Eval());
}
public bool EvalAny()
{
return _expressions.Any(e => e.Eval());
}
}
Реализуйте выражения безопасности:
internal abstract class SecurityExpression
{
public abstract bool Eval();
}
internal class BooleanSecurityExpression : SecurityExpression
{
private readonly bool _result;
public BooleanSecurityExpression(bool expression)
{
_result = expression;
}
public override bool Eval()
{
return _result;
}
}
internal class CompanySecurityExpression : SecurityExpression
{
private readonly string _company;
public CompanySecurityExpression(string company)
{
_company = company;
}
public override bool Eval()
{
return (WhereverYouGetUser).Company == company;
}
}
Вы можете добавить столько пользовательских выражений, сколько вам нужно. Инфраструктура немного сложна, но тогда использование действительно простое:
public ActionResult AcmeOnlyAction()
{
SecurityGuard.ExceptionIf(s => s.UserWorksForCompany("Acme"));
}
Вы также можете связать выражение и использовать его в качестве условия для примера (используя SecurityGuard.Require ()
).
Прошу прощения за длинный пост, надеюсь, это поможет.