6.2.6.1. Ограничения

Ограничения (Constraints) позволяют реализовать row-level access control, т.е. контроль доступа на уровне экземпляров сущностей. В отличие от разрешений, которые накладываются на классы сущностей, ограничения накладываются на конкретные экземпляры. Ограничения можно накладывать на чтение, создание, модификацию и удаление сущностей, так что фреймворк будет отфильтровывать некоторые экземпляры или запрещать операции с экземплярами, соответствующими ограничениям. Кроме того, можно задать специальные ограничения, не привязанные к действиям CRUD.

Пользователь получает набор ограничений от всех групп начиная со своей и вверх по иерархии. Поэтому чем ниже пользователь в иерархии групп, тем больше у него ограничений.

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

Существует два типа проверки ограничений: проверка в базе данных и проверка в памяти.

  1. Для ограничений с проверкой в базе данных условия задаются с помощью фрагментов выражений на языке JPQL. Эти фрагменты подставляются в каждый запрос, выбирающий экземпляры данной сущности. Таким образом, сущности, не соответствующие условиям ограничения, отфильтровываются на уровне базы данных. Ограничение с проверкой в базе данных можно задать только на чтение сущностей, и они затрагивают только корневые сущности в загружаемых графах объектов.

  2. Для ограничений с проверкой в памяти условия задаются с помощью кода Java (во время разработки), или выражений на Groovy (во время работы приложения). Эти выражения выполняются для каждой сущности проверяемого графа объектов, и если какая-либо сущность не соответствует условиям - она отфильтровывается из графа объектов.

Задание ограничений во время разработки

Ограничения можно описывать в классе, расширяющем AnnotatedAccessGroupDefinition, который используется для определения группы доступа. Класс должен располагаться в модуле core. Ниже приведен пример группы доступа, определяющей несколько ограничений для сущностей Customer и Order:

@AccessGroup(name = "Sales", parent = RootGroup.class)
public class SalesGroup extends AnnotatedAccessGroupDefinition {

    @JpqlConstraint(target = Customer.class, where = "{E}.grade = 'B'") (1)
    @JpqlConstraint(target = Order.class, where = "{E}.customer.grade = 'B'") (2)
    @Override
    public ConstraintsContainer accessConstraints() {
        return super.accessConstraints();
    }

    @Constraint(operations = {EntityOp.CREATE, EntityOp.READ, EntityOp.UPDATE, EntityOp.DELETE}) (3)
    public boolean customerConstraints(Customer customer) {
        return Grade.BRONZE.equals(customer.getGrade());
    }

    @Constraint(operations = {EntityOp.CREATE, EntityOp.READ, EntityOp.UPDATE, EntityOp.DELETE}) (4)
    public boolean orderConstraints(Order order) {
        return order.getCustomer() != null && Grade.BRONZE.equals(order.getCustomer().getGrade());
    }

    @Constraint(operations = {EntityOp.UPDATE, EntityOp.DELETE}) (5)
    public boolean orderUpdateConstraints(Order order) {
        return order.getAmount().compareTo(new BigDecimal(100)) < 1;
    }
}
1 - загружать только покупателей, у которых атрибут grade равен B (соответствует значению перечисления Grade.BRONZE).
2 - загружать только заказы покупателей, у которых атрибут grade равен B.
3 - in-memory constraint that filters out customers with grade other than Grade.BRONZE from loaded object graphs.
4 - ограничение с проверкой в памяти, которое разрешает работать только с заказами для покупателей с grade == Grade.BRONZE.
5 - ограничение с проверкой в памяти, которое разрешает изменять и удалять только заказы с amount < 100.

Правила формирования ограничения на JPQL:

  • В качестве алиаса извлекаемой сущности необходимо использовать строку {E}. При выполнении запросов она будет заменена на реальный алиас, заданный в запросе.

  • В параметрах JPQL можно использовать следующие предопределенные константы:

    • session$userLogin − имя учетной записи текущего пользователя (в случае замещения − имя учетной записи замещаемого пользователя).

    • session$userId − ID текущего пользователя (в случае замещения − ID замещаемого пользователя).

    • session$userGroupId − ID группы текущего пользователя (в случае замещения − ID группы замещаемого пользователя).

    • session$XYZ − произвольный атрибут текущей пользовательской сессии, где XYZ − имя атрибута.

  • Содержимое атрибута where добавляется в выражение where запроса по условию and (И). Само слово where писать не нужно, оно будет добавлено автоматически, даже если исходный запрос его не содержал.

  • Содержимое атрибута join добавляется в выражение from запроса. Оно должно начинаться с запятой или слов join или left join.

Задание ограничений во время работы приложения

Для создания ограничения, выберите в экране Access Groups группу, на которую нужно наложить ограничение, и перейдите на вкладку Constraints. Экран редактирования ограничения содержит мастер Constraint Wizard, который помогает создавать простые JPQL и Groovy условия для атрибутов сущности. Если выбран тип операции Custom, то появляется обязательное поле Code, где нужно указать строку, по которой будет идентифицироваться данное ограничение.

Редактор JPQL в полях Join Clause и Where Clause поддерживает автодополнение имен сущностей и их атрибутов. Для вызова автодополнения нажмите Ctrl+Space. Если вызов произведен после точки, будет выведен список атрибутов сущности, соответствующей контексту, иначе - список всех сущностей модели данных.

В Groovy скрипт ограничения с проверкой в памяти необходимо использовать {E} в качестве переменной, содержащей проверяемый экземпляр сущности. Кроме того, в скрипт передается переменная userSession типа UserSession. В примере ниже приведен скрипт ограничения, проверяющий, что сущность была создана текущим пользователем:

{E}.createdBy == userSession.user.login

При нарушении ограничения пользователю показывается уведомление. Заголовок и текст уведомления для каждого ограничения можно локализовать: см. кнопку Localization на вкладке Constraints экрана Access Groups.

Проверка ограничений в коде приложения

Разработчик может проверить условия ограничений для конкретной сущности с помощью методов интерфейса Security:

  • isPermitted(Entity, ConstraintOperationType) - для проверки ограничений по типу операции.

  • isPermitted(Entity, String) - для проверки ограничений по коду ограничия.

Кроме того, существует возможность связать любое действие, унаследованное от класса ItemTrackingAction, c проверкой ограничений. Для этого в XML-элементе action следует задать атрибут constraintOperationType, либо использовать метод setConstraintOperationType() в контроллере экрана. Имейте в виду, что код ограничения будет выполняться на клиентском уровне, поэтому он не должен содержать обращения к классам среднего слоя.

Пример:

<table>
    ...
    <actions>
        <action id="create"/>
        <action id="edit" constraintOperationType="update"/>
        <action id="remove" constraintOperationType="delete"/>
    </actions>
</table>