Skip to content

Permissions - how an access decision is made ​

Every guarded action in Spark ERP asks one question: may this person do this action on this thing? Opening a page, saving a record, approving a task, printing a document, calling an API route and managing a document all go through the same resolver, so the answer is the same on a screen, in a workflow and over the API. This page defines the question, the order in which rules are consulted, the resource-name conventions, the caching and the diagnostic tools. The screens that author the rules are documented in Permission screens reference.

The question ​

The resolver is called as allows(tenant, actor, resource, action) and returns allowed or denied.

PartMeaning
TenantThe workspace. Rules and people never cross workspaces.
ActorThe username in the X-Actor header (anonymous when absent).
ResourceA free-text name that follows a convention (below). The platform does not check that the name exists.
ActionA word such as view, read, update, submit, approve. Each checkpoint defines the actions it asks for.

Rules are stored as cells: (role or person, resource, action, effect, org unit). The effect is allow or deny. An org unit of none means the rule applies to the whole workspace.

How the actor's roles are found ​

  1. The actor's assignments in the role assignment table that are currently valid: effective-from not later than now and expiry later than now (a window can be set per assignment on the Users screen).
  2. Plus the roles of every active delegation in which the actor is the delegate: not revoked, now between its start and end, and either all roles of the delegator or the single role named in its scope. A delegate therefore evaluates as if holding those roles for the delegation window.
  3. Each role is then expanded with all of its ancestors through the parent chain. A role's active flag, effective-at and expiry-at are not consulted.

If the result is empty the actor has no roles, and the role layer imposes nothing.

Order of precedence ​

The resolver walks three layers and stops at the first layer that has a matching rule. A rule matches when its org unit is none, or is the actor's own org unit or an ancestor of it (see Organisation scope). Inside a layer a deny beats an allow.

StepLayerSourceDecision
1Temporary permissionRows whose window contains now (start and end inclusive) for this actor, resource and actionFinal. Denied if any matching row is a deny, otherwise allowed. It overrides a role.
2User overrideStanding rows for this actor, resource and actionFinal. Denied if any matching row is a deny, otherwise allowed.
3RoleGrants of the actor's roles and their ancestors for this resource and actionDenied if any matching row is a deny. Otherwise allowed.
4Nothing matchedAllowed.

An allow in the role layer does not add anything over the default, because no rule also means allowed. The role layer can only take access away. The consequence of the platform-wide default is that access is closed by writing a deny, not by omitting a grant.

Set rules before real use. A feature with no rules is open to every signed-in person. To restrict an item, add a deny for the broad roles that must not have it. A grant for the intended role is only needed to override a deny that its other roles inherit.

Because a deny wins within a layer and a parent's deny reaches its children, a person holding Employee (denied) and Manager (allowed) is denied. Overriding it takes a user override or a temporary permission for that person, which sit above the role layer.

Worked examples ​

RulesPersonResult and reason
Role Employee: page:payroll-run / view denyAlice holds only EmployeeDenied, layer Role.
As above, plus user override for Alice: allowAliceAllowed, layer User override (step 2 ends the walk).
As above, plus temporary deny for Alice, valid this weekAliceDenied, layer Temporary permission; the override is not read.
Role Employee grants nothing for page:report-xAliceAllowed, layer Default.
Role Company deny on employee / read scoped to org unit FinanceBob, assigned to Finance Payroll (child of Finance)Denied: the rule's org unit is an ancestor of Bob's unit.
Same ruleCarol, assigned to SalesAllowed: the rule does not reach her. The Effective Permission Viewer lists the row as "no - scope mismatch".

Organisation scope ​

An actor can be assigned to several org units. The set of units that match a rule is each assigned unit plus every ancestor of each, read live from the organisation hierarchy. A rule scoped to a parent unit therefore applies to every unit below it. A rule with no org unit always matches. The Studio matrix screens write tenant-wide rules; org-scoped rules are written through the REST endpoints, by attaching a permission set at a scope, or on the user override and temporary permission forms (field Scope).

Resource names ​

Resource names are plain text. A misspelt name creates a rule that never matches and nothing warns you; confirm every new rule with the Effective Permission Viewer.

What you protectResource stringActions askedAuthored on
Records of an entity through the generic entity endpointsThe entity name, for example equipment_loancreate, read, update, deletePermission Sets, user overrides, temporary permissions (Studio lists only a few entity names; the API accepts any)
A page definitionpage:<page name>viewScreen Permissions
A menu nodemenu:<menu name>:<node id>viewMenu Permissions
A block typewidget:<block type>visible, enabled, maskedWidget Permissions
A print templateprint_template:<template name>export, printPrint Permissions
A workflowworkflow:<workflow name>submit, approve, reject, return, delegate, manage, migrateWorkflow Permissions
A reportreport:<report name>view, export, email, print (not consulted by any checkpoint yet)Report Permissions
One document-management objectdms.cabinet:<id>, dms.folder:<id>, dms.document:<id>, dms.file:<id>view, read, write, edit, delete, relate, execute, change_owner, change_permissionThe object's Permissions dialog, Named ACL Templates

Placeholders in angle brackets stand for the real name. Other modules define their own resource strings; the strings above are the conventions used by the platform screens.

Per-record access ​

A caller can ask for a chain of resource names from most to least specific, for example dms.document:47 then dms.document. The resolver explains each name before the last one in turn and stops at the first name whose deciding layer is anything other than Default (that is, some rule exists for it); that decision is final. If none of the specific names has a rule it falls back to the ordinary allows on the last name. Document management uses this for per-document and per-folder rights, and the owner of an object always passes. Any module can use the same pattern, for example hcm.employee:123 then hcm.employee, with no second permission system.

Mechanisms that answer a different question ​

The resolver covers the layers above. The following mechanisms are separate; each has its own screen and its own rules.

QuestionMechanismScreen
May this role change this field, or must it fill it?Field rules, applied when a record is saved. Roles only: user overrides, temporary permissions and org scope are not read.Field Permissions
Which rows of an entity may this role see?Row filter expressions. A role with no rule is unrestricted.Record Permissions
Who may approve an amount?A numeric ceiling per role and approval object.Approval Permissions
May this role call this route, how often and from where?Route rules checked on every /api/v1/** request.API Permissions
May this person use this resource under these conditions, with field masking, a schedule and versions?Attribute-based policies. Once a policy names a resource, everyone it does not allow is denied for it.Access Policies
May this actor use a privileged function such as managing secrets, backups, published APIs, webhooks or integrations?A stricter gate: no rule denies the action and either the actor holds Studio Staff, Tenant Owner or Tenant Admin, or an allow was explicitly granted to the actor. Everyday checks are allowed unless denied; these are denied unless granted.Roles, User Overrides
Which applications may this user enter?Application access grants.Application access

Two role tiers ​

Roles managed on the Roles screen are the workspace-wide tier. An application that ships its own admin-configurable roles (for example the HCM and CRM foundations) keeps a second set of role, permission and assignment tables in its own database schema, and its REST endpoints consult that tier. The tiers are independent: a grant in one does not appear in the other. See Add app-owned roles and permissions.

Caching ​

When the shared cache is enabled for the deployment, the result of each allows call is cached for 60 seconds. A change to a role, a role grant, a permission set or a user override raises a per-workspace generation number, which makes every earlier cached decision unreachable at once. A temporary permission does not raise the generation, so its start or expiry can take up to 60 seconds to show. Field rules and the explanation endpoint are not cached. Access policies keep their own list of active policies for 10 seconds.

Checking and explaining ​

ToolWhat it doesEndpoint
Effective Permission Viewer, Explain nowShows the decision, the deciding layer and every candidate row of that layer for any actor, resource and action, including rows ignored because their org scope does not reach the actor.GET /api/v1/permission-explain?actor=&resource=&action=
Simulate & saveThe same trace at a chosen instant, stored with who ran it. Only the instant varies; the actor's current roles and org units are replayed.POST /api/v1/permission-simulations, GET /api/v1/permission-simulations?actor=
Self-checkA screen asks about its own caller. One request carries many checks.POST /api/v1/permission-checks
http
POST /api/v1/permission-checks
X-Tenant-Id: 2
X-Actor: alice
Content-Type: application/json

{ "checks": [ { "resource": "menu:main:payroll", "action": "view" },
              { "resource": "widget:core.text-input", "action": "masked" } ] }
json
[ { "resource": "menu:main:payroll", "action": "view", "allowed": false, "decidingLayer": "role_permission" },
  { "resource": "widget:core.text-input", "action": "masked", "allowed": true, "decidingLayer": "default" } ]

The decidingLayer values are temporary_permission, user_permission, role_permission and default. Callers use default to tell "nobody configured this" from "a rule decided this"; the widget checkpoint treats a masked result decided by default as not masked. The self-check always answers about the caller, never about another person.

Audit and history ​

Changes to roles, role grants, permission sets and user overrides are written to the Permission Audit Log with before and after values. Roles, role grants, permission sets, user overrides, temporary permissions, field rules, API rules and approval limits also keep numbered versions that can be compared and restored. Record scope rules and named ACL templates have neither. Details and key formats are in Permission screens reference.

Who may change permissions ​

Every create, update, delete, set and clear of a rule is checked against the AUTHOR capability on artifact type role; restoring a version and publishing, activating, deactivating, archiving or deleting an access policy need PUBLISH. The check goes through a replaceable policy bean; the bundled default allows every actor. Reads, including the explanation endpoints, are open to any caller of the workspace. Studio itself requires the platform role Studio Staff (see Security Designer).

Limits ​

  • A checkpoint exists only where a feature calls the resolver. Report Permissions and the Masked action of Field Permissions are stored but not read.
  • Widget and menu checks run once per page host mount; a change shows at the next mount, and they hide controls without protecting the data behind them. Protect data with entity grants, field rules, record permissions and API rules.
  • Field rules block saving a changed or empty value; they do not strip a field from a read response.
  • The simulator cannot try a role or org unit the person does not have.
  • The role Active flag, effective-at and expiry-at are stored but not evaluated by the resolver.

Troubleshooting ​

SymptomLikely causeCheck
A grant "does nothing"The resource string is misspelt, or the item was never restricted (no rule means allowed)Explain the exact string; the deciding layer reads Default
Access allowed although a role deniesA user override or an active temporary allow sits above the roleExplain; read the Layer column
Access denied although the role allowsAnother role or an ancestor denies, or a user override or temporary deny existsExplain; every deny row of the deciding layer is listed
A deny on an org unit has no effect for a personTheir org unit assignment is not below that unitCheck Applied?: "no - scope mismatch"
A new temporary permission does not work yetThe 60-second cacheWait a minute, or check Explain, which is not cached
A page works for a person but the data does not loadPage and data are protected separatelyAdd entity, API or record rules

Where next ​