Skip to content

Roles and Permissions

Ed Mozley edited this page Jul 14, 2026 · 5 revisions

Roles & Permissions

How FreeITSM lets a non-administrator manage part of a module's settings β€” without handing them the whole System module, and without handing them all of that module's settings either. This is a second, finer layer of access control sitting on top of module access, so it's called "Layer 2".

Companion pages: Module Access Control (Layer 1), Admin Access Control (the is_admin System gate whose guard pattern this reuses), and Security.

Managed under System β†’ Roles (administrators only). To wire a module or add a permission, see the Roles β€” Developer Guide.


Three layers, composed

FreeITSM answers three questions about a person and a module, not one:

Question Answered by Page
Can they use it? Module access β€” individual + team grants Module Access Control
Can they administer this part of it? A Role granting that settings tab's capability this page
Can they run the System module? is_admin Admin Access Control

They stack. Module access gets someone into Asset Management to do their job; a role is what additionally lets them, say, maintain the locations list. is_admin is a superset of everything.

This is deliberately not an action-level permission system for everyday work. There is no tick-box for "reply to a ticket" or "take a course". Layer 2 governs a module's settings surface and nothing else. The day-to-day job stays governed by module access.

The unit of permission is a settings tab

This is the design decision everything else follows from.

A single "manage this module's settings" permission sounds tidy, and it is useless. Look at what sits side by side on one tab bar:

Asset Management tab What it actually is
Asset types A lookup list
Asset statuses A lookup list
vCenter Hostname, username and password for your hypervisor
Intune Azure tenant id, client id and client secret

One capability covering both means you can never say "Priya maintains the asset types and locations, but doesn't get the hypervisor credentials." Tickets is worse: its Mailboxes tab holds OAuth credentials and inbound mail routing β€” whoever holds it can redirect or read the company's email β€” and it sits two tabs from Ticket types, which is a list of words.

So each settings tab gets its own capability. Around 70 across the product when the roll-out completes.

The umbrella keeps it usable

Every module also declares a <module>.manage umbrella, which satisfies every capability in that module. So the ordinary "Asset Administrator" role is one tick, not seven.

The umbrella also makes roles stable over time: add a new tab next year and umbrella-holders pick it up automatically, while a role that ticked individual tabs does not. That's the safe direction β€” a new administrative surface is never silently granted to someone who was given a narrow permission.

Some tabs aren't administration

The Left panel tab (on seven modules' settings pages) is a per-analyst display preference β€” where your sidebar sits. It isn't administration, it declares no capability, and everyone with the module sees it. There's nothing there to grant.

Sensitive capabilities are badged

Any capability that reaches credentials, email, or money is marked sensitive and badged in the Roles picker. assets.vcenter and assets.intune are sensitive; assets.types is not. The badge is a prompt to think, not a second gate.

The model

A capability is an atomic permission, keyed <module>.<tab> (e.g. assets.vcenter). A role is a named bundle of capabilities, assigned to analysts and/or whole teams. An analyst's effective capabilities are the union of the roles assigned to them directly and the roles assigned to any team they belong to β€” the same individual-plus-team, combined-at-one-choke-point shape as module and company access.

Table Purpose
rbac_roles A named role (name, description, is_active)
rbac_role_capabilities The capability keys a role grants
rbac_analyst_roles Roles assigned to an analyst
rbac_team_roles Roles assigned to a team (every member inherits)

Valid capabilities live in code, not the database. Each module declares its settings tabs β€” and therefore its capabilities β€” in a single file, <module>/settings/manifest.php. Everything else is derived from that one declaration: the tab bar you see, the tick-boxes on System β†’ Roles, and which stored settings each tab may write.

So the database can never hold a capability the app doesn't define, the Roles picker cannot drift from the code (it is generated from it), and retiring a capability retires it everywhere with no data migration.

Earlier this was four separate lists that had to agree, with an automatic check to catch them drifting apart. Needing that check was the problem, so they were collapsed into one. Detail in the Developer Guide.

The two core rules

1. Deny by default

With no role, a non-administrator has no access to any module's settings. This is the opposite of Layer 1's "absence = everything" default β€” and it's the right default here, because a settings screen is administration, not someone's job.

2. System administrators bypass the whole layer

An analyst with is_admin = 1 implicitly holds every capability. This is what makes deny-by-default safe to switch on:

  • Every administrator β€” including you β€” keeps everything on upgrade. The tightening can never lock you out.
  • The new control only ever adds reach for the non-admins you choose.

Note

Because admins bypass Layer 2, you never assign roles to your administrators. Promoting someone to admin on System β†’ Analysts gives them everything regardless of roles.


How it's enforced

The whole point of this layer is that it is not a UI convenience. Three mechanisms, all server-side, all failing closed.

1. The page: not rendered, not hidden

A settings tab you lack the capability for is never emitted into the HTML. It is not display:none, and it is not disabled β€” the markup does not exist. There is nothing to un-hide in devtools, and nothing to re-enable.

The tab bar is rendered from the module's manifest, filtered by what you hold. Someone granted only assets.locations receives a page containing exactly two tabs. Typing the URL of the vCenter tab does nothing, because there is no vCenter tab on their page.

2. The endpoint: a hard guard

Hiding a control is worthless if the endpoint behind it still answers. Every settings write endpoint carries its own guard:

Guard Use on A lacking non-admin gets
requireCapability(Cap::X) a settings page 302 redirect to the launcher
requireCapabilityJson(Cap::X) a settings write API 403 { success:false, … } and exit

Both are authoritative β€” they re-check the database on every request, so an analyst whose role was revoked a second ago is stopped even on a stale session β€” and both fail closed: if anything throws, access is denied.

Layer 1's original mistake was hiding launcher cards without enforcing, so a "restricted" analyst could type a URL and walk in. Layer 2 does not repeat it.

3. Shared settings: the permission follows the key

Some settings tabs have no endpoint of their own. Asset Management's Warranty, vCenter and Intune tabs, Tickets' General tab, and three System areas all save through one generic key/value endpoint (api/settings/save_system_settings.php).

A guard at the top of that file would be meaningless β€” it's the same file for every caller, with five different audiences. So authorisation attaches to the setting key, not the file: includes/settings_keys.php records who owns each key, every key in a request is authorised before any of them are written (so a partly-permitted save is refused whole rather than half-applied), and a key that no module claims is refused outright.

Warning

This was a real hole, fixed in #829. That endpoint previously checked only that you were a logged-in analyst, then wrote whatever keys you posted. Any analyst β€” including one whose module access was a single unrelated module β€” could overwrite the vCenter and Intune credentials, every AI provider API key, the SSO configuration, or the security policy: setting max_failed_logins to 9999 switches off brute-force lockout entirely. Reads were never exposed (secrets are masked on the way out), so it was tampering rather than disclosure. If you are on an older release, upgrade.

What is deliberately not guarded

Reads. get_asset_types.php is called by the settings page and by the everyday asset list, which needs type names to render its filters. Gate it and the module breaks for everyone who isn't a manager. Capabilities guard writes; operational reads stay on plain module access.

Warning

With one exception: a read that returns credentials is not an operational read.

get_mailboxes.php masked the OAuth client secret into a new field for display β€” and then shipped the original plaintext alongside it, to every logged-in analyst (#849). It was a "read". The rule is: ask what the response contains, not just what the endpoint does.

4. And the one thing none of that can catch

Type-safe permissions catch a misspelled check. Nothing catches a check somebody simply forgot to write β€” no language feature saves you from a line that isn't there.

So there is an audit: System β†’ Debug tools β†’ D005 (Endpoint permission coverage). It reads every endpoint in the product and reports what actually guards each one, ranked by how much damage the gap allows. Run it before a release.

It is not theoretical. Every one of these was a real hole, and every one was found by hand, by accident, while looking for something else β€” which is precisely why the tool now exists:

What was open Who could do it
The shared settings endpoint (#829) Any analyst could rewrite the vCenter and Intune credentials, every AI API key, the SSO config β€” and set max_failed_logins to 9999, switching off brute-force lockout
Six Intune endpoints (#830) Any analyst could trigger a full sync of the Intune tenant
All 49 RFP Builder endpoints (#833) Any analyst could read, edit or delete any RFP, or overwrite its AI key β€” while the pages were correctly gated
An audit-log endpoint (#834) Anyone not even logged in could forge audit entries attributed to any analyst
The LMS settings page (#836) A learner could open the course-authoring settings by typing the URL

The management screen (System β†’ Roles)

Administrators only.

  • Add a role β€” a name and description.
  • Edit a role β€” tick its capabilities (grouped by module, umbrella first, sensitive ones badged), and choose the analysts and teams that hold it. All saved as one unit.
  • A role with no capabilities grants nothing. An inactive role grants nothing either β€” so you can switch one off without deleting it or un-assigning everyone.

The picker is generated from the code's capability registry, so a capability the code defines always appears, and one it doesn't can never be saved.

Safe upgrade

  • Nothing changes for administrators β€” they bypass the layer.
  • Non-admins lose settings access they may incidentally have had. Before a module is wired in, its settings are reachable by anyone with the module; once wired, a non-admin needs a role. That is the intended tightening, it's called out in the changelog per module, and operational access is untouched β€” a non-admin can still do their actual job the moment the upgrade lands.

Roll-out status β€” βœ… COMPLETE

All 16 modules. 76 capabilities. 143 capability-guarded endpoints.

Module Capabilities
Tickets 14 β€” departments, ticket types, ticket origins, statuses, priorities, SLA, rota, rota locations, email templates, general, CSAT, mailboxes πŸ”’, messaging πŸ”’, reply cleanup πŸ”’
Asset Management 7 β€” types, statuses, locations, suppliers, warranty, vCenter πŸ”’, Intune πŸ”’
Contracts 7 β€” supplier types/statuses, contract statuses, payment schedules, contract terms, RFP departments, RFP AI πŸ”’
Change Management 5 β€” fields, statuses, priorities, types, impacts
Tasks 5 β€” statuses, priorities, calendar, card, tags
Knowledge 4 β€” email πŸ”’, AI πŸ”’, embeddings πŸ”’, recycle bin
CMDB 3 β€” classes, relationship types, AI πŸ”’
Problem Management 3 β€” statuses, priorities, AI πŸ”’
Service Status 3 β€” services, statuses, impacts
Morning Checks 2 β€” checks, statuses
Forms 2 β€” layout, AI πŸ”’
Workflow 2 β€” AI πŸ”’, formats
Calendar 1 β€” categories
Process Mapper 1 β€” step types
Software 1 β€” API keys πŸ”’
LMS 1 β€” lms.manage (module access lets you take assigned training; the capability lets you manage it)

Every module also has a <module>.manage umbrella. πŸ”’ = sensitive: reaches credentials, email, money or the audit trail.

The System module is deliberately administrator-only

It has ~20 areas, not tabs, and every one is administration by definition β€” companies, analysts, teams, SSO, the security policy, branding, database verification. There is no operational half to separate out, which is the whole premise of this layer. Splitting it would create permissions only administrators would ever hold β€” which is what is_admin already means.

Related pages

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally