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:

  • When DataManager loads an entity, it locates all managed beans implementing the SetupAttributeAccessHandler interface and invokes their setupAccess() method passing the SetupAttributeAccessEvent object. This object contains the loaded instance in the managed state, and three collections of attribute names: read-only, hidden and required (they are initially empty).

  • The SetupAttributeAccessHandler implementations analyze the state of the entity and fill collections of attribute names in the event appropriately. These classes are in fact containers 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 and implement the SetupAttributeAccessHandler interface. Parameterize the interface with the type of handled entity. 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. 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.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.