Appearance
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.
| Part | Meaning |
|---|---|
| Tenant | The workspace. Rules and people never cross workspaces. |
| Actor | The username in the X-Actor header (anonymous when absent). |
| Resource | A free-text name that follows a convention (below). The platform does not check that the name exists. |
| Action | A 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
- 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).
- 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.
- 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.
| Step | Layer | Source | Decision |
|---|---|---|---|
| 1 | Temporary permission | Rows whose window contains now (start and end inclusive) for this actor, resource and action | Final. Denied if any matching row is a deny, otherwise allowed. It overrides a role. |
| 2 | User override | Standing rows for this actor, resource and action | Final. Denied if any matching row is a deny, otherwise allowed. |
| 3 | Role | Grants of the actor's roles and their ancestors for this resource and action | Denied if any matching row is a deny. Otherwise allowed. |
| 4 | Nothing matched | Allowed. |
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
| Rules | Person | Result and reason |
|---|---|---|
Role Employee: page:payroll-run / view deny | Alice holds only Employee | Denied, layer Role. |
| As above, plus user override for Alice: allow | Alice | Allowed, layer User override (step 2 ends the walk). |
| As above, plus temporary deny for Alice, valid this week | Alice | Denied, layer Temporary permission; the override is not read. |
Role Employee grants nothing for page:report-x | Alice | Allowed, layer Default. |
Role Company deny on employee / read scoped to org unit Finance | Bob, assigned to Finance Payroll (child of Finance) | Denied: the rule's org unit is an ancestor of Bob's unit. |
| Same rule | Carol, assigned to Sales | Allowed: 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 protect | Resource string | Actions asked | Authored on |
|---|---|---|---|
| Records of an entity through the generic entity endpoints | The entity name, for example equipment_loan | create, read, update, delete | Permission Sets, user overrides, temporary permissions (Studio lists only a few entity names; the API accepts any) |
| A page definition | page:<page name> | view | Screen Permissions |
| A menu node | menu:<menu name>:<node id> | view | Menu Permissions |
| A block type | widget:<block type> | visible, enabled, masked | Widget Permissions |
| A print template | print_template:<template name> | export, print | Print Permissions |
| A workflow | workflow:<workflow name> | submit, approve, reject, return, delegate, manage, migrate | Workflow Permissions |
| A report | report:<report name> | view, export, email, print (not consulted by any checkpoint yet) | Report Permissions |
| One document-management object | dms.cabinet:<id>, dms.folder:<id>, dms.document:<id>, dms.file:<id> | view, read, write, edit, delete, relate, execute, change_owner, change_permission | The 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.
| Question | Mechanism | Screen |
|---|---|---|
| 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
| Tool | What it does | Endpoint |
|---|---|---|
| Effective Permission Viewer, Explain now | Shows 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 & save | The 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-check | A 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
| Symptom | Likely cause | Check |
|---|---|---|
| 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 denies | A user override or an active temporary allow sits above the role | Explain; read the Layer column |
| Access denied although the role allows | Another role or an ancestor denies, or a user override or temporary deny exists | Explain; every deny row of the deciding layer is listed |
| A deny on an org unit has no effect for a person | Their org unit assignment is not below that unit | Check Applied?: "no - scope mismatch" |
| A new temporary permission does not work yet | The 60-second cache | Wait a minute, or check Explain, which is not cached |
| A page works for a person but the data does not load | Page and data are protected separately | Add entity, API or record rules |
Where next
- Permission screens reference
- Users and access
- Add app-owned roles and permissions
- Storage and access for documents
