# 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 customLogic is set, the decision is based only on the result of the custom logic, and hasPermissionRoles no 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-in ROLE_ADMIN includes ROLE_DEVELOPER, and ROLE_DEVELOPER includes ROLE_USER, so a user with ROLE_ADMIN also satisfies a role requirement that only requires ROLE_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],
1
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
1
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
1

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 != 'æŽĨæ”ļčŽĸ单通įŸĨ']
1
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]]
1

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 through POST /data/<domain class>, the backend still checks only createRoleRequirement.
  • When the hook returns create: true but the user does not satisfy createRoleRequirement, create in the result is still true, together with error: 1 and a message. The UI still shows the create button, but the submission is rejected by the backend.
  • When a domain class has multiple CREATE hooks configured, all of them are ignored and the decision is based on createRoleRequirement.

# 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}}
1
2
3
4
5
6
7
8
9
10
Last Updated: 9/24/2026, 2:27:35 PM