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

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

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

Данный механизм работает следующим образом (см. также изменения в версии 6.8.1 ниже):

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

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

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

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

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

  • Создайте управляемый бин в модуле core проекта. Бин должен иметь дефолтный singleton scope.

  • Создайте метод, принимающий единственный параметр типа SetupAttributeAccessEvent. Метод должен иметь аннотацию org.springframework.context.event.EventListener. Тип SetupAttributeAccessEvent является generic и должен быть параметризован типом сущности.

  • В данном методе необходимо заполнить коллекции скрываемых, только для чтения и обязательных атрибутов используя методы 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.events.SetupAttributeAccessEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component("sample_AttributeAccessRules")
public class AttributeAccessRules {

    @EventListener
    public void orderAccess(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");
            }
        }
    }
}
Изменения в версии 6.8.1

В связи с проблемами, найденными в случае имплементации обработчиков в виде Spring application event listeners, данный механизм был изменен в версии 6.8.1 платформы. Правила, описанные выше, по прежнему действуют по умолчанию, но в дополнение был введен альтернативный способ объявления обработчиков. Данный способ включается с помощью свойства приложения cuba.useSpringApplicationEventsToSetupAttributeAccess, и будет основным и единственным способом начиная с версии 6.9. Поэтому мы рекомендуем изменить ваши обработчики в соответствии с правилами, описанными далее.

Обработчик должен быть управляемый бином модуля core проекта и реализовывать интерфейс SetupAttributeAccessHandler. Бин должен иметь дефолтный singleton scope. Вам необходимо реализовать методы интерфейса:

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

  • setupAccess(SetupAttributeAccessEvent) - оперирует коллекциями атрибутов для настройки доступа.

Например:

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

Фреймворк автоматически применяет ограничения доступа к экрану перед вызовом метода ready() его контроллера. Если вы не хотите этого для некоторого экрана, переопределите метод isAttributeAccessControlEnabled() его контроллера и верните из него false.

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

package com.company.sample.web.order;

import com.company.sample.entity.Order;
import com.haulmont.cuba.gui.AttributeAccessSupport;
import com.haulmont.cuba.gui.components.AbstractEditor;
import com.haulmont.cuba.gui.data.Datasource;

import javax.inject.Inject;
import java.util.Map;

public class OrderEdit extends AbstractEditor<Order> {

    @Inject
    private Datasource<Order> orderDs;

    @Inject
    private AttributeAccessSupport attributeAccessSupport;

    @Override
    public void init(Map<String, Object> params) {
        orderDs.addItemPropertyChangeListener(e -> {
            if ("customer".equals(e.getProperty())) {
                attributeAccessSupport.applyAttributeAccess(this, true, getItem());
            }
        });
    }
}

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

Warning

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