9. Data Access Control

So far you entered the application as admin and had full control over data and UI. In this final chapter, you will set up restricted access to the application for HR Managers and Employees.

Resource Role for Employees

Creating Resource Role

In the Jmix tool window, click New (add) → Resource Role:

employee role 1

In the New Resource Role dialog, enter Employee to Role name field and select UI in Security scope dropdown:

employee role 2

Click OK.

Studio will create and open an annotated interface:

package com.company.onboarding.security;

import io.jmix.security.role.annotation.ResourceRole;

@ResourceRole(name = "Employee", code = "employee", scope = "UI")
public interface EmployeeRole {
}
A role with the UI scope is applied to users only when they enter the system through the UI. If the same user logs in through the REST API, the role is not applied. It is recommended to create a different set of roles for the API, usually with fewer permissions.

Switch to the User Interface tab to define permissions to views. Select MyOnboardingView in the menu tree and select Allow checkboxes on the right:

employee role 3

After that, switch to the Entities tab and select the following permissions:

employee role 4

An employee needs to read the Step, User and UserStep entities to view them in UI and update User and UserStep to mark completed steps.

Switch back to the Text tab. You will see that Studio has generated a few methods with annotations corresponding to granted permissions:

@ResourceRole(name = "Employee", code = "employee", scope = "UI")
public interface EmployeeRole {
    @MenuPolicy(menuIds = "MyOnboardingView")
    @ViewPolicy(viewIds = "MyOnboardingView")
    void screens();

    @EntityAttributePolicy(entityClass = User.class,
            attributes = "*",
            action = EntityAttributePolicyAction.VIEW)
    @EntityPolicy(entityClass = User.class,
            actions = {EntityPolicyAction.READ, EntityPolicyAction.UPDATE})
    void user();

    @EntityAttributePolicy(entityClass = UserStep.class,
            attributes = "*",
            action = EntityAttributePolicyAction.VIEW)
    @EntityPolicy(entityClass = UserStep.class,
            actions = {EntityPolicyAction.READ, EntityPolicyAction.UPDATE})
    void userStep();

    @EntityAttributePolicy(entityClass = Step.class,
            attributes = "*",
            action = EntityAttributePolicyAction.VIEW)
    @EntityPolicy(entityClass = Step.class,
            actions = EntityPolicyAction.READ)
    void step();
}

Press Ctrl/Cmd+S and switch to the running application. Open SecurityResource roles view. You will see the new role in the list:

employee role 5

Assigning Role

Now let’s assign the role to a user. Open the Users list view and create a new user bob. Select the user and click Role assignments button:

assign role 1

In the Role assignments view, click Add button in the Resource roles panel.

In the Select resource roles dialog, select Employee and UI: minimal access roles:

assign role 2

Click Select. The selected roles will be shown in the Resource roles panel:

assign role 3

Click OK to save the role assignments.

The UI: minimal access role is required for the user to log in to the application UI. You can find it in your project and investigate its contents.

Log out using the button next to the current user name:

assign role 4

Log in as bob. You will see only My onboarding view in the menu:

assign role 5

Resource Role for HR Managers

In the Jmix tool window, click New (add) → Role.

In the New Role dialog, enter HR Manager to Role name field, set Role code to hr-manager and select UI in Security scope dropdown:

manager role 1

Click OK.

Studio will create and open the annotated interface defining the role:

package com.company.onboarding.security;

import io.jmix.security.role.annotation.ResourceRole;

@ResourceRole(name = "HR Manager", code = "hr-manager", scope = "UI")
public interface HRManagerRole {
}

Switch to the User Interface tab and allow User.list and User.detail views (use the search field on top to filter the tree):

manager role 2

Switch to the Entities tab and give read permission to Department and Step, and all permissions to User and UserStep:

manager role 3

Switch back to the Text tab and inspect the annotations generated by Studio:

@ResourceRole(name = "HR Manager", code = "hr-manager", scope = "UI")
public interface HRManagerRole {
    @MenuPolicy(menuIds = "User.list")
    @ViewPolicy(viewIds = {"User.detail", "User.list"})
    void screens();

    @EntityAttributePolicy(entityClass = Department.class,
            attributes = "*",
            action = EntityAttributePolicyAction.VIEW)
    @EntityPolicy(entityClass = Department.class,
            actions = EntityPolicyAction.READ)
    void department();

    @EntityAttributePolicy(entityClass = Step.class,
            attributes = "*",
            action = EntityAttributePolicyAction.VIEW)
    @EntityPolicy(entityClass = Step.class,
            actions = EntityPolicyAction.READ)
    void step();

    @EntityAttributePolicy(entityClass = User.class,
            attributes = "*",
            action = EntityAttributePolicyAction.MODIFY)
    @EntityPolicy(entityClass = User.class,
            actions = EntityPolicyAction.ALL)
    void user();

    @EntityAttributePolicy(entityClass = UserStep.class,
            attributes = "*",
            action = EntityAttributePolicyAction.MODIFY)
    @EntityPolicy(entityClass = UserStep.class,
            actions = EntityPolicyAction.ALL)
    void userStep();
}

Press Ctrl/Cmd+S and switch to the running application. Log in as admin. Open SecurityResource roles view and make sure the new HR Manager role is in the list.

Create a new user, say alice.

Assign the HR Mnager and UI: minimal access roles to alice as you did in the previous section.

Log in as alice. You will see the Users view and will be able to manage users and their onboarding steps:

manager role 4

Row-level Role for HR Managers

Currently, HR managers can create users, assign any department to a user, and see users of all departments.

In this section, you will introduce a row-level role which restricts access to departments and other users for an HR manager. They will be able to see and assign only their own department (the one where they are set in the hrManager attribute).

In the Jmix tool window, click New (add) → Row-level Role:

rl role 1

In the New Row-level Role dialog, enter:

  • Role name: HR manager’s departments and users

  • Role code: hr-manager-rl

  • Class: com.company.onboarding.security.HrManagerRlRole

rl role 2

Click OK.

Studio will create and open an annotated interface:

package com.company.onboarding.security;

import io.jmix.security.role.annotation.RowLevelRole;

@RowLevelRole(name = "HR manager's departments and users",
        code = "hr-manager-rl")
public interface HrManagerRlRole {
}

Click Add PolicyJPQL Policy in the top actions panel:

rl role 3

In the Add JPQL Policy dialog, enter:

  • Entity: Department

  • Where clause: {E}.hrManager.id = :current_user_id

rl role 3 1

Click OK.

Click Add PolicyJPQL Policy again and enter:

  • Entity: User

  • Where clause: {E}.department.hrManager.id = :current_user_id

Click OK.

The HrManagerRlRole interface will have the following code:

package com.company.onboarding.security;

import com.company.onboarding.entity.Department;
import com.company.onboarding.entity.User;
import io.jmix.security.role.annotation.JpqlRowLevelPolicy;
import io.jmix.security.role.annotation.RowLevelRole;

@RowLevelRole( (1)
        name = "HR manager's departments and users",
        code = "hr-manager-rl")
public interface HrManagerRlRole {

    @JpqlRowLevelPolicy( (2)
            entityClass = Department.class, (3)
            where = "{E}.hrManager.id = :current_user_id") (4)
    void department();

    @JpqlRowLevelPolicy(
            entityClass = User.class,
            where = "{E}.department.hrManager.id = :current_user_id")
    void user();
}
1 @RowLevelRole annotation indicates that the interface defines a row-level role.
2 @JpqlRowLevelPolicy defines a policy to be applied on the database level when reading the entity.
3 The entity class for which the policy is applied.
4 The where clause to be added for each JPQL select statement for this entity. {E} is used instead of the entity alias in the query. :current_user_id is a predefined parameter set by the framework to the id of the currently logged-in user.

Press Ctrl/Cmd+S and switch to the running application. Log in as admin. Open SecurityRow-level roles view and make sure the new HR manager’s departments and users role is in the list.

Open Role assignments view for alice and add the role to the Row-level roles table:

rl role 4

Click OK to save the role assignments.

Set alice as HR Manager for a Department:

rl role 5

Log in as alice.

In the Users list view, you will see only users of her department:

rl role 6

And alice can assign only this department to a user:

rl role 7

Summary

In this section, you have created HR Managers and Employees roles to restrict access to the application for different groups of users.

You have learned that:

  • A resource role gives users permissions to open views and to work with particular entities.

  • A row-level role, in contrast, restricts user’s ability to read particular entity instances for an entity permitted by a resource role.

  • Roles are assigned to users at runtime using the Role assignment view available from the User.detail view.

  • The UI: minimal access role scaffolded in the project is required for a user to log in to the application UI.