Самостоятельное формирование разрешений и выдача экземпляров ключей

Чтобы построить универсальную систему прав доступа, вы можете самостоятельно формировать разрешения доступа, создавать экземпляры ключей и выдавать их пользователям. В этом случае можно контролировать права как на уровне элементов проекта, так и на уровне отдельных экземпляров сущностей (RLS).

Примечание: В приложении этот вариант может совмещаться с простым вариантом, использующим автоматическую выдачу прав (подробнее).

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

Ключи доступа, добавленные разработчиком

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

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

Подробнее о ключах доступа

Построение собственной системы прав

В собственной системе прав можно контролировать права как на уровне элементов проекта, так и на уровне отдельных экземпляров сущностей (RLS). Например, если чтение справочника разрешено, то дальше проверяется право на чтение конкретного элемента справочника. Если оно есть — значит, можно читать этот элемент. Если чтение запрещено — значит, этот элемент справочника читать нельзя. Если же чтение всего справочника запрещено, то права на его элементы не проверяются и чтение любого элемента запрещается.

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

Основные принципы построения системы ключей доступа

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

Управление доступом в проекте

Каждый вид элементов проекта, для которого поддерживается управление доступом, имеет фиксированный набор прав, предоставляемый . Например, Справочник имеет права Создание, Чтение, Изменение, Удаление.

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

Чтобы получить возможность вручную создавать разрешения для тех прав, которые вы хотите контролировать самостоятельно, выберите способ контроля доступа РазрешенияВычисляются или РазрешенияВычисляютсяДляКаждогоОбъекта, например:

Описание свойства КонтрольДоступа в YAML-файле элемента проекта:
КонтрольДоступа:
    Разрешения:
        Создание: РазрешеноАутентифицированным
        Чтение: РазрешенияВычисляются
        ПоУмолчанию: РазрешеноАдминистраторам
Эти два способа контроля доступа отличаются следующим:
  • РазрешенияВычисляются позволит вам контролировать права только на уровне всего элемента проекта.
  • РазрешенияВычисляютсяДляКаждогоОбъекта позволит вам контролировать права и на уровне всего элемента проекта, и на уровне отдельных его элементов (RLS).

При выборе разрешения РазрешенияВычисляютсяДляКаждогоОбъекта становится активной и обязательной для заполнения настройка РасчетРазрешенийПо, в которой перечисляются названия полей объекта, по которым происходит расчет разрешений доступа:

Пример заполнения настройки РасчетРазрешенийПо при расчете разрешений по полям Вид, Ответственный и Проект:
КонтрольДоступа:
    Разрешения:
        ПоУмолчанию: РазрешенияВычисляютсяДляКаждогоОбъекта
    РасчетРазрешенийПо: [ Вид, Ответственный, Проект ]
Внимание: В качестве данных для расчета разрешений доступа нельзя указывать поля следующих типов:
  • реквизиты-коллекции,
  • строки неограниченной длины,
  • числа, в которых более 20 знаков в целой части и более 10 в дробной.
Эти ограничения накладываются и на все пользовательские структуры, входящие в состав полей для расчета разрешений доступа.

Если для элемента проекта выбрано разрешение РазрешенияВычисляютсяДляКаждогоОбъекта, то порождается новый тип имя-элемента-проекта.ДанныеРасчетаРазрешений. Объект данного типа содержит все поля, перечисленные в настройке РасчетРазрешенийПо элемента проекта.

В коде объекты данного типа можно создать двумя способами:
  • в качестве параметра указать сам объект, для которого должен быть порожден объект типа ДанныеРасчетаРазрешений:
    метод КонструкторИзОбъекта()
        // Создаем новый объект справочника Задачи
        // и заполняем его реквизиты
        пер НоваяЗадача = новый Задачи.Объект()
        НоваяЗадача.Наименование = "Наименование задачи"  // Не используется для расчета разрешений
        НоваяЗадача.Вид = "Сложная задача"                // Используется для расчета разрешений
        НоваяЗадача.Ответственный = "Иванов Дмитрий"      // Используется для расчета разрешений
        НоваяЗадача.НомерПроекта = 15                     // Используется для расчета разрешений
    
        // Создаем экземпляр типа ДанныеРасчетаРазрешений,
        // в качестве параметра конструктора передаем объект НоваяЗадача
        пер ДанныеРасчетаРазрешений = новый Задачи.ДанныеРасчетаРазрешений(НоваяЗадача)
    
        // Экземпляр типа ДанныеРасчетаРазрешений содержит поля,
        // которые использовались для расчета разрешений
        пер РеквизитВидЗадачи = ДанныеРасчетаРазрешений.Вид // "Сложная задача"
        пер РеквизитОтветственный = ДанныеРасчетаРазрешений.Ответственный // "Иванов Дмитрий"
        пер РеквизитНомерПроекта = ДанныеРасчетаРазрешений.НомерПроекта // 15
    ;
  • в качестве параметров явно указать все значения полей для расчета разрешений доступа:
    метод КонструкторИзАтрибутов()
        // Создаем экземпляр типа ДанныеРасчетаРазрешений,
        // в качестве аргументов передаем поля,
        // указанные в настройке РасчетРазрешенийПо
        пер ДанныеРасчетаРазрешений = новый Задачи.ДанныеРасчетаРазрешений(
            Вид = "Сложная задача",
            Ответственный = "Иванов Дмитрий",
            НомерПроекта = 15
        )
    
        // Экземпляр типа ДанныеРасчетаРазрешений содержит поля,
        // которые использовались для расчета разрешений
        // и передавались как аргументы конструктора
        пер РеквизитВидЗадачи = ДанныеРасчетаРазрешений.Вид // "Сложная задача"
        пер РеквизитОтветственный = ДанныеРасчетаРазрешений.Ответственный // "Иванов Дмитрий"
        пер РеквизитНомерПроекта = ДанныеРасчетаРазрешений.НомерПроекта // 15
    ;

Вычисление разрешений доступа

Для того чтобы вы могли создать разрешения доступа, у типов встроенного языка, порождаемых элементом проекта, есть события ВычислитьРазрешенияДоступа и ВычислитьРазрешенияДоступаДляОбъектов. Эти события нужно обрабатывать в модулях этих типов, например: в модуле справочника, в модуле документа, в модуле HTTP-сервиса, в модуле SOAP-сервиса, в модуле регистра сведений, в модуле плана обмена.

Предположим, вы добавили в приложение собственный ключ доступа с именем КлючиГруппСотрудников. Этот ключ имеет один параметр Группа, позволяющий указать элемент перечисления ГруппыСотрудников. Это перечисление имеет два элемента: Менеджеры и Руководители. Таким образом, вы сможете иметь два экземпляра ключа доступа, с помощью которых вы разделите всех пользователей на две группы.

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

@Обработчик
метод ВычислитьРазрешенияДоступа(): Массив<РазрешениеДоступа>

    пер Разрешения: Массив<РазрешениеДоступа>
    
    // Получим экземпляры ключей доступа
    пер КлючМенеджеров = новый КлючиГруппСотрудников.Объект(ГруппыСотрудников.Менеджеры)
    пер КлючРуководителей = новый КлючиГруппСотрудников.Объект(ГруппыСотрудников.Руководители)

    // Создадим разрешение доступа
    Разрешения.Добавить(новый РазрешениеДоступа([КлючМенеджеров, КлючРуководителей],
                                                [Сущность.Право.Чтение]))

    возврат Разрешения
;

Здесь чтение справочника в целом разрешается всем пользователям: и тем, кто имеет экземпляр КлючРуководителей, и тем, кто имеет экземпляр КлючМенеджеров.

Событие ВычислитьРазрешенияДоступаДляОбъектов предназначено для создания разрешений доступа для отдельного элемента справочника. Обработчик события в модуле справочника может выглядеть, например, следующим образом:

@Обработчик
метод ВычислитьРазрешенияДоступаДляОбъектов(Элементы: ЧитаемыйМассив<Поставщики.ДанныеРасчетаРазрешений>):
                                            ЧитаемоеСоответствие<Поставщики.ДанныеРасчетаРазрешений, 
                                            Массив<РазрешениеДоступа>>

    пер Результат = <Поставщики.ДанныеРасчетаРазрешений, Массив<РазрешениеДоступа>>{:}

    // Получим экземпляры ключей доступа
    пер КлючМенеджеров = новый КлючиГруппСотрудников.Объект(ГруппыСотрудников.Менеджеры)
    пер КлючРуководителей = новый КлючиГруппСотрудников.Объект(ГруппыСотрудников.Руководители)
 
    для Элемент из Элементы
        пер Разрешения = <РазрешениеДоступа>[]
        // Проанализируем свойства элемента справочника, в зависимости от чего 
        // либо разрешим менеджерам его чтение, либо нет
        если Элемент.ВажноеСвойство
            Разрешения.Добавить(новый РазрешениеДоступа([КлючРуководителей], 
                                                        [Сущность.Право.Чтение]))
        иначе
            Разрешения.Добавить(новый РазрешениеДоступа([КлючМенеджеров, КлючРуководителей],
                                                        [Сущность.Право.Чтение]))
        ;
        Результат.Вставить(Элемент, Разрешения)
    ;
    возврат Результат
;

Здесь, если элемент справочника имеет ВажноеСвойство == Истина, то чтение этого элемента разрешается только тем, кто имеет экземпляр КлючРуководителей. В противном случае элемент справочника могут читать и те, кто имеет экземпляр КлючРуководителей, и те, кто имеет экземпляр КлючМенеджеров.

В зависимости от выбранного способа контроля доступа в элементе проекта, вам нужны определенные обработчики:
  • если вы выбрали РазрешенияВычисляются, то у вас должен быть только обработчик события ВычислитьРазрешенияДоступа;
  • если вы выбрали РазрешенияВычисляютсяДляКаждогоОбъекта, то вам нужны обработчики обоих событий.

После того как вы добавили в проект новый справочник, нужно запустить приложение и записать для этого справочника разрешения доступа. Однако событие ВычислитьРазрешенияДоступа не вызывается автоматически. Для его вызова используйте метод ПересчитатьРазрешенияДоступа() (например, для справочника: имя-справочника.ПересчитатьРазрешенияДоступа()). Такой вызов можно разместить в модуле проекта.

Подробнее о пересчете разрешений и экземпляров ключей

Обработчик события ВычислитьРазрешенияДоступаДляОбъектов в модуле сущности вызывается в следующих случаях:
  • при создании нового объекта или записи регистра, если для любого из прав сущности настроено вычисление разрешений для каждого объекта;
  • при изменении существующего объекта или записи регистра, если были изменены значения реквизитов, которые используются для расчета разрешений;
  • при явном пересчете разрешений доступа объектов или записей регистра при вызове метода имя-сущности.ПересчитатьРазрешенияДоступаДляОбъектов().
Таким образом, после добавления справочника и записи его элементов в приложении будут нужные разрешения как для самого справочника в целом, так и для его элементов.

Выдача ключей доступа

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

// ...
// Получим экземпляры ключей доступа
пер КлючМенеджеров = новый КлючиГруппСотрудников.Объект(ГруппыСотрудников.Менеджеры)
пер КлючРуководителей = новый КлючиГруппСотрудников.Объект(ГруппыСотрудников.Руководители)
// ...

В этот момент у менеджера этого ключа доступа вызывается событие ПроверитьНаличиеКлючейДоступа, в обработчике которого вы формируете соответствие экземпляра ключа пользователям. Например:

@Обработчик
метод ПроверитьНаличиеКлючейДоступа(Ключи: ЧитаемыйМассив<КлючиГруппСотрудников.Объект>, 
                                    ПользователиДляПроверки: ЧитаемыйМассив<Пользователи.Объект>): 
                                    ЧитаемоеСоответствие<КлючиГруппСотрудников.Объект, 
                                    ЧитаемыйМассив<Пользователи.Ссылка>>

    знч Результат = <КлючиГруппСотрудников.Объект, Массив<Пользователи.Ссылка>>{:}

    // Получаем пользователей каждой из групп сотрудников
    знч ГруппыСотрудников = Ключи.Преобразовать(Ключ -> Ключ.Группа)
    знч Пользователи = ПользователиДляПроверки.Преобразовать(Пользователь -> Пользователь.Ссылка)
    знч ПользователиПоГруппам: Соответствие<ГруппыСотрудников, Массив<Пользователи.Ссылка>> 
        = ПолучитьПользователейПоГруппам(ГруппыСотрудников, Пользователи)

    для ЭкземплярКлюча из Ключи
        знч ПользователиГруппы = ПользователиПоГруппам.ПолучитьИлиУмолчание(ЭкземплярКлюча.Группа)
        если ПользователиГруппы != Неопределено
            Результат.Вставить(ЭкземплярКлюча, ПользователиГруппы)
        ;
    ;

    возврат Результат
;
Событие ПроверитьНаличиеКлючейДоступа вызывается в следующих случаях:
  • при выполнении обработчика ВычислитьРазрешенияДоступаДляОбъектов, если при этом были созданы новые экземпляры ключей доступа;
  • при подключении к приложению нового пользователя;
  • при явном пересчете определенных ключей доступа пользователей при вызове метода имя-ключа-доступа.ПересчитатьКлючи(<Пользователи>);
  • при явном пересчете всех ключей доступа пользователей при вызове метода Пользователи.ПересчитатьКлючиДоступа(<Пользователи>).
Обработчик события ПроверитьНаличиеКлючейДоступа в качестве параметров принимает массивы ключей и пользователей. На размеры этих массивов влияют:
  • событие вызова обработчика:
    • если обработчик вызван при создании нового экземпляра ключа (в момент записи объекта), то массив ключей будет содержать только созданный экземпляр ключа, а массив пользователей — всех подключенных к приложению пользователей;
    • если обработчик вызван при подключении нового пользователя или при явном пересчете ключей пользователя, то массив ключей будет содержать все существующие экземпляры ключей, а массив пользователей — только этого пользователя;
  • типы параметров ключа доступа и их количество:
    • количество возможных экземпляров ключей с параметрами ссылочных типов будет превосходить это количество для ключей с параметрами типов перечислений с ограниченным количеством значений;
    • чем больше параметров содержит ключ, тем больше возможных комбинаций их значений и тем больше количество возможных экземпляров ключа доступа.

Выдача прав на системные действия

Помимо формирования разрешений на доступ к отдельным элементам проекта, позволяет вам выдавать пользователям права на системные действия. Для этого используется базовый тип СистемноеПраво. От него унаследовано перечисление ВстроенноеПраво, содержащее встроенные права на системные действия, в частности право на выполнение запросов в консоли запросов.

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

@Обработчик
метод ВычислитьСистемныеРазрешенияДоступа(): ЧитаемыйМассив<РазрешениеДоступа>
    возврат [
        новый РазрешениеДоступа(
            [новый КлючДоступаДляАутентифицированных.Объект()],
            [ВстроенноеПраво.КонсольЗапросов])
    ]
;
Для вызова обработчика и пересчета всех разрешений доступа используйте метод ПересчитатьСистемныеРазрешенияДоступа() типа Пользователи.
Чтобы проверить наличие права на выполнение системных действий у пользователя, вызовите метод ЕстьПраво или ПроверитьПраво типа КонтрольДоступа, например:
// Проверка наличия у пользователей права на использование консоли запросов.
// Проверка осуществляется в текущем контексте прав доступа
метод ДоступнаКонсольЗапросов(): Булево
    возврат КонтрольДоступа.ЕстьПраво(ВстроенноеПраво.КонсольЗапросов)
;