3.2.14. Контроль доступа к атрибутам сущностей

Подсистема безопасности позволяет управлять доступом к атрибутам сущностей в соответствии с правами пользователя. Т.е. фреймворк может сделать атрибут read-only или скрыть его в зависимости от набора ролей, назначенных текущему пользователю. Однако, иногда может потребоваться изменять доступ к атрибуту в зависимости также от текущего состояния экземпляра сущности или связанных сущностей.

Механизм контроля доступа к атрибутам позволяет создавать правила того, какие атрибуты должны быть скрыты, нередактируемы или обязательны к заполнению для некоторого экземпляра сущности, и применять эти правила к компонентам Generic UI и в REST API.

Данный механизм работает следующим образом:

  • Когда DataManager загружает сущность, он находит бины, реализующие интерфейс SetupAttributeAccessHandler и вызывает их метод setupAccess(), передавая в них объект типа SetupAttributeAccessEvent. Данный объект содержит загруженный экземпляр в состоянии managed и три коллекции имен атрибутов: read-only, hidden и required (изначально они пустые).

  • Имплементации SetupAttributeAccessHandler анализируют состояние сущности и заполняют списки имен атрибутов соответствующим образом. Данные классы являются по сути контейнерами правил, задающих доступ к атрибутам экземпляров.

  • Описываемый механизм сохраняет имена атрибутов, заданные правилами, в самом экземпляре (в связанном объекте SecurityState).

  • На клиентском уровне, Generic UI и REST API используют объект SecurityState для управления доступом к атрибутам сущностей.

Для создания правил для некоторого типа сущностей, необходимо выполнить следующее:

  • Создайте Spring-бин в модуле core проекта и реализуйте в нем интерфейс SetupAttributeAccessHandler. Параметризуйте интерфейс типом обрабатываемой сущности. Бин должен иметь дефолтный singleton scope. Вам необходимо реализовать методы интерфейса:

    • supports(Class) - возвращает true если данный обработчик предназначен для работы с сущностями переданного класса.

    • setupAccess(SetupAttributeAccessEvent) - оперирует коллекциями атрибутов для настройки доступа. В данном методе необходимо заполнить коллекции скрываемых, только для чтения и обязательных атрибутов используя методы addHidden(), addReadOnly() и addRequired() объекта события. Экземпляр сущности, доступный через метод getEntity(), находится в состоянии managed, поэтому можно безопасно обращаться ко всем его атрибутам и атрибутам связанных сущностей.

Рассмотрим пример сущности Order, имеющей атрибуты customer и amount. Для ограничения доступа к атрибуту amount в зависимости от customer можно создать следующее правило:

package com.company.sample.core;

import com.company.sample.entity.Order;
import com.haulmont.cuba.core.app.SetupAttributeAccessHandler;
import com.haulmont.cuba.core.app.events.SetupAttributeAccessEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component("sample_OrderAttributeAccessHandler")
public class OrderAttributeAccessHandler implements SetupAttributeAccessHandler<Order> {

    @Override
    public boolean supports(Class clazz) {
        return Order.class.isAssignableFrom(clazz);
    }

    @Override
    public void setupAccess(SetupAttributeAccessEvent<Order> event) {
        Order order = event.getEntity();
        if (order.getCustomer() != null) {
            if ("PLATINUM".equals(order.getCustomer().getGrade().getCode())) {
                event.addHidden("amount");
            } else if ("GOLD".equals(order.getCustomer().getGrade().getCode())) {
                event.addReadOnly("amount");
            }
        }
    }
}
Контроль доступа к атрибутам в Generic UI

Фреймворк автоматически применяет ограничения доступа к экрану в момент между посылкой BeforeShowEvent и AfterShowEvent. Если вы не хотите применять ограничения для некоторого экрана, добавьте контроллеру экрана аннотацию @DisableAttributeAccessControl.

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

@UiController("sales_Order.edit")
@UiDescriptor("order-edit.xml")
@EditedEntityContainer("orderDc")
@LoadDataBeforeShow
public class OrderEdit extends StandardEditor<Order> {

    @Inject
    private AttributeAccessSupport attributeAccessSupport;

    @Subscribe(id = "orderDc", target = Target.DATA_CONTAINER)
    protected void onOrderDcItemPropertyChange(InstanceContainer.ItemPropertyChangeEvent<Order> event) {
        if ("customer".equals(event.getProperty())) {
            attributeAccessSupport.applyAttributeAccess(this, true, getEditedEntity());
        }
    }

}

Второй параметр метода applyAttributeAccess() - булевское значение, которое указывает, нужно ли сбрасывать ограничения доступа к компонентам в дефолтные настройки перед тем, как применить новые. Если передано true, возможные программные изменения в этих настройках будут потеряны. Когда данный метод вызывается автоматически перед открытием окна, передается false. Когда же вы вызываете данный метод в ответ на UI-события, передавайте true, иначе ограничения компонентов будут суммироваться, а не заменяться.

Ограничения доступа к атрибутам применяются только к компонентам, связанным с одним атрибутом сущности, например TextField или LookupField. Table и другие компоненты, реализующие интерфейс ListComponent, не затрагиваются. Поэтому если вы пишете правило, скрывающее атрибут для некоторых экземпляров, рекомендуется не показывать этот атрибут в таблицах совсем.