# Object Permission Control
The platform uses role requirements (RoleRequirement) to control who can view, create, update and delete objects of a given type, and who can access a given form, action or menu. When the decision depends on the field values of the object itself, you can attach a piece of custom logic to a role requirement.
TIP
Since 1.0, Request Map based permission configuration and the enable roles field on forms have been removed; use the role requirements described on this page instead.
# Basic Permission Control
# Role Requirements
A role requirement (RoleRequirement) has the following fields:
| Property | Type | Description |
|---|---|---|
name | String | Name, unique within the tenant, cannot be changed after creation |
hasPermissionRoles | List<HierarchyRole> | The roles that satisfy the requirement; a user with any one of them satisfies it |
customLogic | DynamicLogic | Custom decision logic, can be empty, see Dynamic Permissions |
Decision rules:
- When
customLogicis set, the decision is based only on the result of the custom logic, andhasPermissionRolesno longer has any effect; - Otherwise, the platform checks whether the user has any of the roles in
hasPermissionRoles. Roles can be inherited; for example, the built-inROLE_ADMINincludesROLE_DEVELOPER, andROLE_DEVELOPERincludesROLE_USER, so a user withROLE_ADMINalso satisfies a role requirement that only requiresROLE_USER.
The platform has three built-in role requirements:
| Name | Roles that satisfy it |
|---|---|
USER | ROLE_USER |
DEVELOPER | ROLE_DEVELOPER |
ADMIN | ROLE_ADMIN |
# CRUD Permissions on Domain Classes
Each domain class (DomainClass) has four role requirement fields, controlling four operations respectively:
| Property | Controlled operation |
|---|---|
createRoleRequirement | Create |
readRoleRequirement | View |
updateRoleRequirement | Update |
deleteRoleRequirement | Delete |
WARNING
When no role requirement is configured for an operation, nobody (including administrators) can perform that operation. For example, if a domain class has no readRoleRequirement, querying it through /data/<domain class> returns 403 NoPermission.
These four fields are checked by the backend when the platform's data endpoints (/data/...) are called, and they also determine whether the create, edit and delete buttons are shown in the UI.
# Access Permissions for Forms, Actions and Menus
Forms (DynamicForm), object actions (DynamicAction) and menus (DynamicMenu) each have an accessRequirement field. The behavior when it is not configured differs:
| Object | accessRequirement configured | Not configured |
|---|---|---|
| Form | The form can be opened and read only if the requirement is satisfied | No additional restriction (login is still required, and the domain class's view permission still applies) |
| Menu | The menu is shown only if the requirement is satisfied | Form menus: shown (still subject to the requirement of the target form); group menus and link menus: not shown |
| Object action | The action is shown and can be executed only if the requirement is satisfied | Nobody can execute it |
# Configuring via Seed Data
In the current version the UI has no entry for editing role requirements, and the forms for domain classes, forms, actions and menus do not include these fields either. Configure them in the CSV files of a plugin or seed data:
RoleRequirement.csv: defines role requirements. The [:] in the column name hasPermissionRoles.name[:] means multiple roles are separated with an ASCII colon :, written as [ROLE_A:ROLE_B]; any one of the roles satisfies the requirement. Do not use a comma as in [ROLE_A,ROLE_B]: the comma is the CSV column separator and will break the row.
name(*),hasPermissionRoles.name[:],customLogic.name
EQUIPMENT_ADMIN,ROLE_EQUIPMENT_ADMIN,
REPORT_VIEWER,[ROLE_USER],
EQUIPMENT_MANAGER,[ROLE_EQUIPMENT_ADMIN:ROLE_ADMIN],
2
3
4
5
ROLE_EQUIPMENT_ADMIN is not a built-in platform role and must first be defined in HierarchyRole.csv (for example ROLE_EQUIPMENT_ADMIN,[ROLE_USER]); see Data Import for the syntax.
DomainClass*.csv: assigns role requirements to the four operations of a domain class.
shortName(*),extInfo,createRoleRequirement.name,readRoleRequirement.name,updateRoleRequirement.name,deleteRoleRequirement.name
EquipmentProfile,,EQUIPMENT_ADMIN,USER,EQUIPMENT_ADMIN,EQUIPMENT_ADMIN
2
3
DynamicForm*.csv, DynamicMenu*.csv, DynamicAction*.csv: use the accessRequirement.name column to specify the access requirement, for example
name(*),label,parent.name,icon,link,type,displaySequence,form.name,accessRequirement.name
For the general CSV syntax, see Data Import.
# Dynamic Permissions
When permissions need to be decided dynamically based on the object's field values, the current user and similar information, set customLogic on the role requirement.
# Injected Variables for Custom Logic
| Variable | Type | Description |
|---|---|---|
object | Object | The object being checked; depends on where it is used, see the notes below |
hasPermissionRoles | List<HierarchyRole> | The roles configured on this role requirement |
user | tech.muyan.api.security.MuyanAuthentication | The current user; user.authorities*.authority gives the list of role names |
The logic returns [result: true] to indicate the requirement is satisfied; returning [result: false] or no result means it is not satisfied.
Example: only developers can modify Webhooks, but nobody can modify the Webhook named "æĨæļčŽĸåéįĨ".
boolean isDeveloper = user.authorities*.authority.contains('ROLE_DEVELOPER')
return [result: isDeveloper && object?.name != 'æĨæļčŽĸåéįĨ']
2
Value of object:
| Where the role requirement is used | object |
|---|---|
accessRequirement of a form / action / menu | That form (DynamicForm) / action (DynamicAction) / menu (DynamicMenu) |
roleRequirement of a custom Controller | The current request (HttpServletRequest) |
| The four role requirements of a domain class | See the notes below; may be a specific object or null |
object in domain class role requirements
object is a specific object only when permissions are queried per object (that is, the Permission Query API POST /permissions/<domain class>/ below, which the frontend uses to decide whether to show the edit and delete buttons on each row). When the backend handles /data/... CRUD requests, it checks "whether this type of object can be operated on", and the object passed in is null.
In practice: with the example logic above used as the updateRoleRequirement of DynamicWebhook, the permission query API returned update: false for "æĨæļčŽĸåéįĨ" and the UI did not show the edit button; but calling PUT /api/data/DynamicWebhook/<id> directly still modified it successfully. So object-level decisions can currently only control the UI and cannot serve as a data security guarantee; the logic must also handle object == null, otherwise all CRUD requests will be rejected.
# Dynamic Create Permission
Besides attaching custom logic to createRoleRequirement, you can also create an object hook of type Create ability (CREATE) for a domain class to decide whether the current user can create objects of that type. Object hooks are maintained in Development > Object Hooks; fill in the new hook as follows:
| Field | Value |
|---|---|
| name | Name of the hook; required, cannot be changed after creation |
| Hook type | Create ability |
| Object type | The domain class this logic applies to |
| Core logic | The actual decision logic |
| Active | Must be turned on. It is off by default in the create form, and a hook saved with it off does not run |
| Variable | Type | Description |
|---|---|---|
objectType | Class<?> | The object type being operated on |
userContext | tech.muyan.api.security.MuyanAuthentication | The current user |
application | grails.core.GrailsApplication | The current grails application context |
log | Closure<?> | log closure for printing execution logs |
The logic must return a result key whose value is a Map containing a create key:
// Allow the user to create this object; create must be placed inside result
return [result: [create: true]] Return value format
create must be wrapped in result. When [create: true] is returned directly, the permission query API fails with Cannot invoke "java.util.Map.get(Object)" because "result" is null.
Notes:
- This hook only affects the result of the Permission Query API
GET /permissions/<domain class>/create, that is, whether the create button is shown in the UI; when an object is created throughPOST /data/<domain class>, the backend still checks onlycreateRoleRequirement. - When the hook returns
create: truebut the user does not satisfycreateRoleRequirement,createin the result is stilltrue, together witherror: 1and a message. The UI still shows the create button, but the submission is rejected by the backend. - When a domain class has multiple
CREATEhooks configured, all of them are ignored and the decision is based oncreateRoleRequirement.
# Dynamic Update and Delete Permissions
Object hooks of type Update/delete ability (UPDATE_DELETE) are currently not executed: the type can still be selected in the UI, but selecting it has no effect (in practice the permission query result was not affected and no execution records were produced). When update and delete permissions need to be decided per object, use custom logic on updateRoleRequirement / deleteRoleRequirement, and keep in mind the limitation on object described above.
# Permission Query API
The frontend uses the following endpoints to decide which buttons to show; you can also call them from your own pages or third-party systems (paths need the /api prefix; for authentication see Platform API):
# Whether objects of a type can be created
curl http://<server address>/api/permissions/DynamicWebhook/create \
-H 'Authorization: Bearer <access_token>'
# {"create":true}
# View, update and delete permissions for a set of objects
curl -X POST http://<server address>/api/permissions/DynamicWebhook/ \
-H 'Authorization: Bearer <access_token>' -H 'Content-Type: application/json' \
-d '{"ids":[1]}'
# {"1":{"view":true,"update":true,"delete":true}}
2
3
4
5
6
7
8
9
10