5.2.14. Entity Attribute Access Control

The security subsystem allows you to set up access to entity attributes according to user permissions. That is the framework can automatically make an attribute read-only or hidden depending on a set of roles assigned to the current user. But sometimes you may want to change the access to attributes dynamically depending also on the current state of the entity or its linked entities.

The attribute access control mechanism allows you to create rules of what attributes should be hidden, read-only or required for a particular entity instance, and apply these rules automatically to Generic UI components and REST API.

The mechanism works as follows (see also changes in version 6.8.1 below):

  • When DataManager loads an entity, it sends a Spring application event of the SetupAttributeAccessEvent type. The event object contains the loaded instance in the managed state, and three collections of attribute names: read-only, hidden and required (they are initially empty).

  • You create an event listener that analyzes the state of the entity and fill collections of attribute names in the event appropriately. This event listener is in fact a container for the rules that define the attribute access for a given instance.

  • The mechanism saves the attribute names, defined by your rules, in the entity instance itself (in a linked SecurityState object).

  • On the client tier, Generic UI and REST API use the SecurityState object to control the access to entity attributes.

In order to create a rule for particular entity type, do the following:

  • Create a managed bean in the core module of your project. The bean must have the default singleton scope.

  • Create a method accepting one parameter of the SetupAttributeAccessEvent type. The method must be marked with the org.springframework.context.event.EventListener annotation. The SetupAttributeAccessEvent type is generic and it must be parametrized with the entity type.

  • In the method, you should fill the collections of read-only, hidden and required attributes using the addHidden(), addReadOnly() and addRequired() methods of the event. The entity instance, which is available via the getEntity() method, is in the managed state, so you can safely access its attributes and attributes of its linked entities.

For example, provided that Order entity has customer and amount attributes, you could create the following rule for restricting access to the amount attribute depending on the 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");
            }
        }
    }
}
Changes in version 6.8.1

Due to the problems encountered when access control handlers were implemented as Spring application event listeners, the mechanism was changed in the platform 6.8.1. The above rules are still in place by default, but the alternative way of implementing handlers has been introduced. The new way is turned on by the cuba.useSpringApplicationEventsToSetupAttributeAccess application property and will be the default and only way for version 6.9+. So we recommend you refactoring your handlers to conform to the new rules explained below.

The handler must be a managed bean defined in the core module of your project and implementing the SetupAttributeAccessHandler interface. The bean must have the default singleton scope. You have to implement the interface methods:

  • supports(Class) returns true if the handler is designed to work with the given entity class.

  • setupAccess(SetupAttributeAccessEvent) manipulates with the collections of attributes to set up the access.

For example:

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");
            }
        }
    }
}
Attribute Access Control in Generic UI

The framework automatically applies attribute access restrictions to a screen right before invoking the ready() method of the screen controller. If you don’t want this for a particular screen, override its isAttributeAccessControlEnabled() method and return false from it.

You may want to recompute and apply the restrictions while the screen is opened, in response of user actions. You can do it using the AttributeAccessSupport bean, passing the current screen and the entity which state has changed. For example:

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());
            }
        });
    }
}

The second parameter of the applyAttributeAccess() method is a boolean value which specifies whether to reset components access to default before applying new restrictions. If it’s true, programmatic changes to the components state (if any) will be lost. When the method is invoked automatically on screen opening, the value of this parameter is false. But when invoking the method in response of UI events, set it to true, otherwise the restrictions on components will be summed and not replaced.

Warning

Attribute access restrictions are applied only to the components bound to single entity attributes, like TextField or LookupField. Table and other components implementing the ListComponent interface are not affected. So if you write a rule that can hide an attribute for some entity instances, we recommend not showing this attribute in tables at all.