Authorization
In this section, we cover the topics related to the access control performed by the framework.
Data Access Checks
The following table explains how data access permissions and restrictions are used by different framework mechanisms.
Entity Operations |
Entity Attributes |
Row-level JPQL Policy (1) |
Row-level READ Predicate Policy (2) |
Row-level CREATE/UPDATE/DELETE Predicate Policy |
|
|
Yes (3) |
No |
Yes |
Yes |
Yes |
|
No |
No |
No |
No |
No |
UI data-aware components |
Yes |
Yes |
- (4) |
- (4) |
- (4) |
REST API |
Yes |
Yes |
Yes |
Yes |
Yes |
REST API |
Yes |
Yes |
Yes |
Yes |
- (5) |
REST API |
Yes |
Yes |
- (6) |
- (6) |
- (6) |
Notes:
1) Row-level JPQL policy affects only the root entity.
// order is loaded only if it satisfies JPQL policies on the Order entity
Order order = dataManager.load(Order.class).id(orderId).one();
// related customer is loaded regardless of JPQL policies on the Customer entity
assert order.getCustomer() != null;
2) Row-level predicate policy affects the root entity and all linked entities in the loaded graph.
// order is loaded only if it satisfies constraints on the Order entity
Order order = dataManager.load(Order.class).id(orderId).one();
// related customer is not null only if it satisfies predicate policies on the Customer entity
if (order.getCustomer() != null) { /*...*/ }
3) Entity operation check in DataManager
is performed for the root entity only.
// loading Order
Order order = dataManager.load(Order.class).id(orderId).one();
// related customer is loaded even if the user has no permission to read the Customer entity
assert order.getCustomer() != null;
4) UI components do not check row-level policies themselves, but when data is loaded through standard mechanisms, the policies are applied by DataManager
. As a result, if an entity instance is filtered out by a row-level policy, the corresponding UI component is shown but it is empty. Also, for any action based on the ItemTrackingAction
class, you can specify a certain entity operation using the setConstraintEntityOp()
method. Then the action will be enabled only if the entity operation for the selected entity instance is allowed.
5) REST queries are read-only.
6) REST service method parameters and results are not checked for compliance to row-level policies. The service behavior with respect to row-level security is defined by how it loads and saves data, for example whether it uses DataManager
or UnconstrainedDataManager
.
Access Constraints
In this part, we briefly explain how Jmix authorization works. This information will be helpful if you need to check user permissions in your code or if you want to extend or replace the standard system of permissions based on roles and policies.
Framework mechanisms contain authorization points, where they check whether an operation or data is permitted. For each authorization point, there is an access context, which is a class implementing the AccessContext
interface and having attributes that describe the authorization subject.
Any module of the framework, an add-on, or the target application, can define and register a set of access constraints for a certain access context (that is for the authorization point). A constraint is a class implementing the AccessConstraint
interface with the applyTo(AccessContext)
method. In this method, a constraint implementation decides whether the authorization subject is allowed and updates the access context state with information about this decision.
At the authorization point, the mechanism being authorized applies the existing constraints using the AccessManager
bean. As a result, the mechanism has the information about authorization from all constraints in the context state and decides whether to continue or abort the operation with the subject.
Let’s illustrate this process with an example of checking rights to load an entity by DataManager
.
-
In the framework core, there is
CrudEntityContext
class that implementsAccessContext
and has the following attributes:-
entityClass
-DataManager
specifies here what entity is being loaded. Together with the context class, this value describes the authorization point. -
readPermitted
- access constraints populate this attribute, soDataManager
can decide whether to continue loading the entity.
-
-
In the security subsystem, there is
CrudEntityConstraint
class that implementsAccessConstraint
and its methods:-
getContextType()
returnsCrudEntityContext.class
to indicate that the constraint is designed for this context. -
applyTo()
sets theCrudEntityContext.readPermitted
attribute according to entity policies defined for the current user.
-
-
When
DataManager
loads an entity, it creates an instance ofCrudEntityContext
, sets theentityClass
attribute, and invokesAccessManager.applyConstraints()
. After that, it analyzes the value ofCrudEntityContext.readPermitted
attribute and either continues loading the entity or aborts the operation.
With this approach, authorization points are completely decoupled from the information required to make authorization decisions. In the example above, the authorization point is in the framework core, while the code that determines the authorization outcome is in the optional security module. Likewise, you can define an additional constraint for the same CrudEntityContext
in your application to affect the standard DataManager
authorization process.
Checking Permissions in Application Code
The above section describes how authorization works in the framework code. You may need to reproduce the framework’s authorization decisions in the application code to check what objects are available to the current user. To do this, you need to create an instance of the appropriate AccessContext
, pass it to the AccessManager.applyRegisteredConstraints()
method, and then analyze the context state. This technique is demonstrated in the examples below.
Example of checking if the current user is able to read Customer
entity:
@Autowired
private AccessManager accessManager;
@Autowired
private Metadata metadata;
public boolean checkCustomerReadPermitted() {
MetaClass metaClass = metadata.getClass(Customer.class);
CrudEntityContext accessContext = new CrudEntityContext(metaClass);
accessManager.applyRegisteredConstraints(accessContext);
return accessContext.isReadPermitted();
}
Example of getting all permitted screens:
@Autowired
private AccessManager accessManager;
@Autowired
private WindowConfig windowConfig;
public List<String> getPermittedScreens() {
return windowConfig.getWindows().stream()
.map(WindowInfo::getId)
.filter(screenId -> {
UiShowScreenContext accessContext = new UiShowScreenContext(screenId);
accessManager.applyRegisteredConstraints(accessContext);
return accessContext.isPermitted();
})
.collect(Collectors.toList());
}
See also an example of checking a specific policy.
Below are some common context classes for checking user permissions:
-
CrudEntityContext
- the context to check entity operations policy. -
EntityAttributeContext
- the context to check entity attributes policy. -
UiShowScreenContext
- the context to check screens policy. -
FlowuiShowViewContext
- the context to check Flow UI views policy. -
UiMenuContext
- the context to check menu policy. -
FlowuiMenuContext
- the context to check Flow UI menu policy. -
InMemoryCrudEntityContext
- the context to check predicate policy.