Управление доступом в ASP.NET MVC в зависимости от входных параметров / уровень служб?

Всегда существует финализатор в IL - Система. Объект. Завершите (), существует в каждом классе, поэтому если Вы делаете пользовательский класс, он имеет финализатор, который Вы хотите подавить. Однако не все объекты помещаются на очередь завершения, таким образом, только технически необходимо должны быть подавить завершение при реализации собственного финализатора.

, Если Вы реализуете IDisposable для обертывания неуправляемых ресурсов, необходимо включать финализатор, и необходимо препятствовать тому, чтобы это работало, с тех пор в теории Вы уже делаете очистку, когда Dispose назван.

41
задан Iain Galloway 3 July 2015 в 11:40
поделиться

7 ответов

Во-первых, я думаю, вы уже наполовину поняли это, потому что заявили, что

, во-первых, мне нужна другая роль для каждого продукта, а во-вторых, я не буду знать, какую роль проверять, пока я не извлечу свой Продукт из репозитория

. Я видел так много попыток заставить безопасность на основе ролей делать то, для чего она никогда не предназначалась, но вы уже прошли эту точку, поэтому это круто :)

Альтернативой безопасности на основе ролей является безопасность на основе ACL, и я думаю, что это то, что вам здесь нужно.

Вам все равно нужно будет получить ACL для продукта, а затем проверить, есть ли у пользователя имеет право на разрешение на товар. Это настолько контекстно-зависимый и сложный для взаимодействия, что я думаю, что чисто декларативный подход является слишком негибким и слишком неявным (т.е.

Если текущий пользователь совпадает с Thread.CurrentPrincipal (что имеет место в ASP.NET MVC, IIRC), вы можете просто назначить указанные выше методы разрешений на:

bool canEdit = permission.IsGranted();

или

permission.Demand();

, потому что пользователь будет неявным . Для вдохновения вы можете взглянуть на System.Security.Permissions.PrincipalPermission.

29
ответ дан 27 November 2019 в 00:52
поделиться

Судя по тому, что вы описываете, вам нужна какая-то форма контроля доступа пользователей, а не разрешения на основе ролей. Если это так, то это необходимо реализовать во всей бизнес-логике. Ваш сценарий звучит так, как будто вы можете реализовать его на уровне обслуживания.

По сути, вы должны реализовать все функции в вашем ProductRepository с точки зрения текущего пользователя, а продукты помечены разрешениями для этого пользователя.

Это звучит сложнее, чем есть на самом деле. Прежде всего, вам понадобится интерфейс токена пользователя, который содержит информацию о пользователе в виде uid и списка ролей (если вы хотите использовать роли). Вы можете использовать IPrincipal или создать свой собственный в соответствии со строками

public interface IUserToken {
  public int Uid { get; }
  public bool IsInRole(string role);
}

Затем в вашем контроллере вы анализируете токен пользователя в конструкторе репозитория.

IProductRepository ProductRepository = new ProductRepository(User);  //using IPrincipal

Если вы ' повторно используя FormsAuthentication и настраиваемый IUserToken, вы можете создать оболочку вокруг IPrincipal, чтобы ваш ProductRepository создавался следующим образом:

IProductRepository ProductRepository = new ProductRepository(new IUserTokenWrapper(User));

Теперь все ваши функции IProductRepository должны обращаться к токену пользователя для проверки разрешений. Например:

public Product GetProductById(productId) {
  Product product = InternalGetProductById(UserToken.uid, productId);
  if (product == null) {
    throw new NotAuthorizedException();
  }
  product.CanEdit = (
    UserToken.IsInRole("admin") || //user is administrator
    UserToken.Uid == product.CreatedByID || //user is creator
    HasUserPermissionToEdit(UserToken.Uid, productId)  //other custom permissions
    );
}

Если вам интересно получить список всех продуктов, в вашем коде доступа к данным вы можете запросить на основе разрешения. В вашем случае левое соединение, чтобы увидеть, содержит ли таблица «многие ко многим» UserToken.Uid и productId. Если правая сторона соединения присутствует, вы знаете, что у пользователя есть разрешение на этот продукт, и затем вы можете установить логическое значение Product.CanEdit.

Используя этот метод, вы можете затем использовать следующее, если хотите, в вашем View ( где Модель - это ваш продукт).

16
ответ дан 27 November 2019 в 00:52
поделиться

Я склонен думать эта авторизация является частью вашей бизнес-логики (или, по крайней мере, вне логики вашего контроллера). Я согласен с Кевингесснером выше, в том, что проверка авторизации должна быть частью вызова для получения элемента. В его методе OnException вы можете показать страницу входа (или то, что вы настроили в web.config) примерно так:

if (...)
{
    Response.StatusCode = 401;
    Response.StatusDescription = "Unauthorized";
    HttpContext.Response.End();
}

И вместо создания UserRepository.

1
ответ дан 27 November 2019 в 00:52
поделиться

Answering my own question (eep!), Chapter 1 of Professional ASP.NET MVC 1.0 (the NerdDinner tutorial) recommends a similar solution to mine above:

public ActionResult Edit(int id)
{
  Dinner dinner = dinnerRepositor.GetDinner(id);
  if(!dinner.IsHostedBy(User.Identity.Name))
    return View("InvalidOwner");

  return View(new DinnerFormViewModel(dinner));
}

Asides from making me hungry for my dinner, this doesn't really add anything as the tutorial goes on to repeat the code implementing the business rule immediately in the matching POST Action Method, and in the Details view (actually in a child partial of the Details view)

Does that violate SRP? If the business rule changed (so that e.g. anyone who had RSVP'd could edit the dinner), you'd have to change both GET and POST methods, and the View (and the GET and POST methods and View for the Delete operation too, although that's technically a seperate business rule).

Is pulling the logic out into some kind of permissions arbitrator object (as I've done above) as good as it gets?

0
ответ дан 27 November 2019 в 00:52
поделиться

Вы в правильный путь, но вы можете инкапсулировать всю проверку разрешений в один метод, например GetProductForUser , который принимает продукт, пользователя и необходимые разрешения. Вызывая исключение, перехваченное обработчиком OnException контроллера, обработка выполняется в одном месте:

enum Permission
{
  Forbidden = 0,
  Access = 1,
  Admin = 2
}

public class ProductForbiddenException : Exception
{ }

public class ProductsController
{
  public Product GetProductForUser(int id, User u, Permission perm)
  {
    Product p = ProductRepository.GetProductById(id);
    if (ProductPermissionService.UserPermission(u, p) < perm)
    {
      throw new ProductForbiddenException();
    }
    return p;
  }

  public ActionResult Edit(int id)
  {
    User u = UserRepository.GetUserSomehowFromTheRequest();
    Product p = GetProductForUser(id, u, Permission.Admin);
    return View(p);
  }

  public ActionResult View(int id)
  {
    User u = UserRepository.GetUserSomehowFromTheRequest();
    Product p = GetProductForUser(id, u, Permission.Access);
    return View(p);
  }

  public override void OnException(ExceptionContext filterContext)
  {
    if (typeof(filterContext.Exception) == typeof(ProductForbiddenException))
    {
      // handle me!
    }
    base.OnException(filterContext);
  }
}

Вам просто нужно предоставить ProductPermissionService.UserPermission,

0
ответ дан 27 November 2019 в 00:52
поделиться

Решения для копирования и вставки через некоторое время действительно становятся утомительными, и их очень сложно поддерживать. Я бы, вероятно, выбрал настраиваемый атрибут, делающий то, что вам нужно. Вы можете использовать отличный .NET Reflector , чтобы увидеть, как реализован AuthorizeAttribute, и выполнить для него вашу собственную логику.

Он наследует FilterAttribute и реализует IAuthorizationFilter. В настоящий момент я не могу это проверить, но что-то вроде этого должно сработать.

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class ProductAuthorizeAttribute : FilterAttribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }

        object productId;
        if (!filterContext.RouteData.Values.TryGetValue("productId", out productId))
        {
            filterContext.Result = new HttpUnauthorizedResult();
            return;
        }

        // Fetch product and check for accessrights

        if (user.IsAuthorizedFor(productId))
        {
            HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache;
            cache.SetProxyMaxAge(new TimeSpan(0L));
            cache.AddValidationCallback(new HttpCacheValidateHandler(this.Validate), null);
        }
        else
            filterContext.Result = new HttpUnauthorizedResult();
    }

    private void Validate(HttpContext context, object data, ref HttpValidationStatus validationStatus)
    {
        // The original attribute performs some validation in here as well, not sure it is needed though
        validationStatus = HttpValidationStatus.Valid;
    }
}

Вы, вероятно, также можете сохранить полученный продукт / пользователя в filterContext.Controller.TempData, чтобы вы могли получить его в контроллере или сохранить это в каком-то кеше.

Edit: Я только что заметил часть о ссылке редактирования.

3
ответ дан 27 November 2019 в 00:52
поделиться

Я думаю, что это нереально и является нарушением разделения ответственности, ожидать чтобы код контроллера / модели управлял отображением представления. Код контроллера / модели может установить флаг в модели представления, который представление может использовать для определения того, что оно должно делать, но я не думаю, что вы должны ожидать, что один метод будет использоваться как контроллером / моделью, так и представлением. для управления доступом к модели и ее визуализацией.

Сказав, что вы можете подойти к этому любым из двух способов - оба будут включать модель представления, которая содержит некоторые аннотации, используемые представлением в дополнение к реальной модели. В первом случае вы можете использовать атрибут для управления доступом к действию. Это было бы моим предпочтением, но потребует декорирования каждого метода независимо - если только все действия в контроллере не имеют одинаковых атрибутов доступа.

Я разработал атрибут «роль или владелец» именно для этой цели. Он подтверждает, что пользователь выполняет определенную роль или является владельцем данных, создаваемых методом. Право собственности в моем случае контролируется наличием отношения внешнего ключа между пользователем и данными, о которых идет речь, то есть у вас есть таблица ProductOwner, и должна быть строка, содержащая пару продукт / владелец для продукта и текущий пользователь. Он отличается от обычного AuthorizeAttribute тем, что при сбое проверки владения или роли пользователь перенаправляется на страницу с ошибкой, а не на страницу входа. В этом случае каждый метод должен установить флаг в модели представления, который указывает, что модель можно редактировать.

В качестве альтернативы вы можете реализовать аналогичный код в методах ActionExecuting / ActionExecuted контроллера (или базового контроллера, чтобы он применялся согласованно ко всем контроллерам). В этом случае вам нужно будет написать некоторый код, чтобы определить, какое действие выполняется, чтобы вы знали, следует ли прерывать действие на основе права собственности на рассматриваемый продукт. Тот же метод установит флаг, чтобы указать, что модель можно редактировать. В этом случае вам, вероятно, понадобится иерархия моделей, чтобы вы могли преобразовать модель в редактируемую модель, чтобы вы могли установить свойство независимо от конкретного типа модели.

Этот вариант кажется мне более связанным, чем использование атрибута и, возможно, более сложный.

1
ответ дан 27 November 2019 в 00:52
поделиться
Другие вопросы по тегам:

Похожие вопросы: