How to let a specific role edit a specific field only when a condition is met?

How can I restrict editing of a specific field based on user role and entity status?

For example, I want a user with the Leader role to not be able to edit the decision field in a Ticket entity if the status field is set to ONGOING.
In all other cases, the field should be editable.

What is the best way to implement this in Jmix?

Jmix version: 2.6.1
Jmix Studio Plugin Version: 2.6.4-251

To implement field-level editing restrictions based on both the user’s role and the entity status, you should combine role checking and component management in your view controller.

Let me describe the solution using the Onboarding example. Suppose that the task is to enable updating the user’s “Active” field only for “ExtendedUserManagementRole” and if the OnboardingStatus of the user is “Completed”.

First approach is to check if the user has a particular role.

The role:

@ResourceRole(name = "ExtendedUserManagementRole", code = ExtendedUserManagementRole.CODE, scope = "UI")  
public interface ExtendedUserManagementRole {  
    String CODE = "extended-user-management-role";  
}

User detail view controller:

public class UserDetailView extends StandardDetailView<User> {
	// ...

	@Autowired  
	private CurrentAuthentication currentAuthentication;  
	  
	@ViewComponent  
	private JmixCheckbox activeField;  
	@ViewComponent  
	private InstanceContainer<User> userDc;
	
    @Subscribe
    public void onReady(final ReadyEvent event) {
        // ...
        // Update field on view opening
        updateActiveField();
    }
    
    private void updateActiveField() {
        OnboardingStatus onboardingStatus = userDc.getItem().getOnboardingStatus();
        boolean editable = OnboardingStatus.COMPLETED.equals(onboardingStatus) && hasExtendedUserManagerRole();
        activeField.setReadOnly(!editable);
    }
    
    // Check if the current user has "extended-user-management-role"
	private boolean hasExtendedUserManagerRole() {  
	    return currentAuthentication.getAuthentication().getAuthorities().stream()  
	            .map(GrantedAuthority::getAuthority)  
	            .anyMatch(authority -> authority.equals("ROLE_extended-user-management-role"));  
	}

	// Update state when the entity state is changed
	@Subscribe(id = "userDc", target = Target.DATA_CONTAINER)
    public void onUserDcItemPropertyChange(final InstanceContainer.ItemPropertyChangeEvent<User> event) {
        if (event.getProperty().equals("onboardingStatus")) {
            updateActiveField();
        }
    }

Another approach is not tied to a particular role. Instead, we’ll check if the user has a specific policy. This is more flexible, because you can enable the policy in any role. For example, it can be in a base role which is not directly assigned to the user.

A role that enables the specific policy:

@ResourceRole(name = "ExtendedUserManagementRole", code = ExtendedUserManagementRole.CODE, scope = "UI")  
public interface ExtendedUserManagementRole {  
    String CODE = "extended-user-management-role";  
  
    @SpecificPolicy(resources = "onboarding.canUpdateUserActiveField")  
    void specific();  
}

The specific policy context to check in the app code:

package com.company.onboarding.security.specific;  
  
import io.jmix.core.accesscontext.SpecificOperationAccessContext;  
  
public class UpdatingUserActiveFieldContext extends SpecificOperationAccessContext {  
  
    public static final String NAME = "onboarding.canUpdateUserActiveField";  
  
    public UpdatingUserActiveFieldContext() {  
        super(NAME);  
    }  
}

User detail view controller:

public class UserDetailView extends StandardDetailView<User> {
	// ...

    @Autowired
    private AccessManager accessManager;
	@Autowired  
	private CurrentAuthentication currentAuthentication;  
	  
	@ViewComponent  
	private JmixCheckbox activeField;  	  
	@ViewComponent  
	private InstanceContainer<User> userDc;
	
    @Subscribe
    public void onReady(final ReadyEvent event) {
        // ...
        // Update field on view opening
        updateActiveField();
    }
    
    private void updateActiveField() {
        OnboardingStatus onboardingStatus = userDc.getItem().getOnboardingStatus();
        boolean editable = OnboardingStatus.COMPLETED.equals(onboardingStatus) && hasSpecificPermission();
        activeField.setReadOnly(!editable);
    }
    
    // Check if the current user has "onboarding.canUpdateUserActiveField" specific permission
    private boolean hasSpecificPermission() {
        UpdatingUserActiveFieldContext updatingUserActiveFieldContext = new UpdatingUserActiveFieldContext();
        accessManager.applyRegisteredConstraints(updatingUserActiveFieldContext);
        return updatingUserActiveFieldContext.isPermitted();
    }

	// Update state when the entity state is changed
	@Subscribe(id = "userDc", target = Target.DATA_CONTAINER)
    public void onUserDcItemPropertyChange(final InstanceContainer.ItemPropertyChangeEvent<User> event) {
        if (event.getProperty().equals("onboardingStatus")) {
            updateActiveField();
        }
    }

Regards,
Konstantin