Безопасность в ADO.NET Data Services

Тема безопасности в распределенных приложениях является очень актуальной, особенно когда сервис работает в глобальной сети. Давайте рассмотрим вопросы безопасности в рамках технологии ADO.NET Data Services.

В рамках ADO.NET Data Services механизмы безопасности можно рассматривать на нескольких уровнях.

Во-первых, т.к. сервисы ADO.NET Data Services работают в рамках hosting-среды ASP.NET, они очень тесно интегрируются с существующей инфраструктурой ASP.NET. Это означает, что мы можем использовать существующие механизмы аутентификации ASP.NET, Membership API, Card Space и т.д. Что интересно, мы можем использовать все существующие наработки в этой области, например собственные механизмы аутентификации, реализованные в рамках HTTP-модулей.

Во-вторых, существует разграничение видимости на уровне объектных коллекций. Существуют варианты полного доступа (чтения/записи), чтения и выполнения запросов, только чтения одиночных сущностей, записи одиночных сущностей и др. Для того, чтобы настроить видимость на уровне контейнеров необходимо в методе InitializeService воспользоваться объектом config. У этого объекта есть два метода – SetEntitySetAccessRule и SetServiceOperationAccessRule. Эти методы идентичны, за тем исключением, что первый задает видимость для объектных коллекций, а второй – сервисных операций.

public class NorthwindService : DataService<NorthwindEntities>
{
    public static void InitializeService(IDataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("*", EntitySetRights.All);
        config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
    }

    // ..

Первым параметром передается имя контейнера или операции в виде строки. Если необходимо задать настройки для всех контейнеров/операций, то используется символ *. Тем не менее, в работающих проектах таким синтаксисом я бы пользоваться не стал. Вторым параметром этим методам передается значение enum-а, который указаывает, значение видимости. Например, если мы укажем значение None, то при работе с сервисом заданную коллекцию мы просто не увидим, как будто ее нету :)

Ну и последний, самый мощный механизм – это перехватички (interceptors). Перехватчики – это специальные методы, которые исполняются на каждую отдаваемую клиенту сущность.

Например, у меня есть коллекция, которая возвращает набор заказчиков. Я могу создать следующий перехватчик:

[QueryInterceptor("Customers")]
public Func<Customers, bool> OnQueryCustomers()
{
   return c => c.ContactName.Contains(" ") == false;
}

Как видно, этот метод возвращает лямбда-выражение, которое исполняется при отдаче клиенту каждой сущности. Это лямбда-выражение возвращет булевское значение, если оно равно true, то данная сущность отдаете клиенту, если false, то клиент ничего об этой сущности не узнает :) В данном примере мы выдаем только те сущности Customer, которые не содержат пробелов. Можно переделать этот пример следующим образом:

[QueryInterceptor("Customers")]
public Func<Customers, bool> OnQueryCustomers()
{
   return c => c.ContactName == HttpContext.Current.User.Identity.Name;
}

В этом случае мы используем HttpContext и получаем имя текущего пользователя. Затем если имя текущего пользователя и поле ContactName совпадают, то эту сущность мы клиенту отдаем. Таким образом, каждый пользователь будет работать со своим набором данных, несмотря на то, что данные для всех пользователей хранятся в одной таблице.

Перехватичик существуют не только для операций выборки, но и для операций модификации. В этом случае перехватчик представляет собой метод, который генерирует исключение, в случае, если операция недопустима.

[ChangeInterceptor("Customers")]
public void OnChangeCategories(Customers c, UpdateOperations mode)
{
   if ((mode == UpdateOperations.Add) || (mode == UpdateOperations.Change))
   {
      if (c.ContactTitle.Contains(" ") == true)
      {
       throw new DataServiceException(400, "Contact title consist of a single word.")
      }
   }
}

Видно, что в перехватчик принимает два параметра – изменяемую сущность и тип операции. В случае ошибки генерируется исключение DataServiceException.

Точно также можно использовать инфраструктуру безопасности при модификации данных.

[ChangeInterceptor("Customers")]
public void OnChangeCategories(Customers c, UpdateOperations mode)
{
   if (mode == UpdateOperations.Delete)
   {
      if (c.ContactName == HttpContext.Current.User.Identity.Name)
      {
         throw new DataServiceException(400, "You can't delete it.")
      }
   }
}

Важная особенность перехватчиков, что эти методы помечаются атрибутами QueryInterceptor или ChangeInterceptor, в параметрах которых мы указываем для какой сущности предназначены эти перехватчики.