Role matrix
The complete account of what each role in this wiki allows and what it does not, layer by layer. This page is the one source for permission values; every other page links here rather than restating them.
Scope
| Layer | Grants | Stored in | Evaluated by |
|---|---|---|---|
| 1 DokuWiki ACL | numbers 0–255 per page or namespace | conf/acl.auth.php | DokuWiki core |
| 2 Session assurance | AUTHENTICATED, MAIL_OTP, TOTP, MFA_ANY | conf/wkidentity-policy.json | wkidentity |
| 3 Project role | none, reader, contributor, maintainer, admin | groups in the user manager | wkdocore |
| 4 Component tiers | differs per component | ACL or a policy file of its own | the component itself |
The layers are asked in this order, and each may only refuse. A role on layer 3 cannot grant what layer 1 has denied.
Layer 1: the DokuWiki ACL
The base ladder. It applies to every page and every namespace and is the only layer that works without any component of this house.
The ladder
| Value | Name | Allows | Does not allow |
|---|---|---|---|
| 0 | No access | nothing; the page behaves as though it did not exist | any read, including attachments and feeds |
| 1 | Read | view the page, history and diffs, export, subscribe | any change, including creating a subpage |
| 2 | Edit | additionally: change existing pages, drafts, revert | creating new pages, uploading files |
| 4 | Create | additionally: create new pages in the namespace | uploading files, deleting files |
| 8 | Upload | additionally: put files into the media area | deleting uploaded files again |
| 16 | Delete | additionally: delete files in the media area | configuration, user manager, extensions |
| 255 | Administration | everything, bypassing every ACL rule | — |
How a value is arrived at
DokuWiki evaluates the most specific rule, not the highest. In order:
- A rule for this account on this page.
- A rule for a group of this account on this page — where several apply, the highest wins.
- The same two steps in the next namespace up, all the way to the root.
- Nothing matches: no access.
An account rule beats every group rule at the same level, even a lower one. That is how a single account is kept out of an area its group has open.
The groups of this wiki
| Group | Who is in it | Meaning |
|---|---|---|
@ALL | every visitor, signed in or not | the base value; at 0 the area is not public |
@user | every signed-in account | “being signed in is enough” |
@admin | the superuser group ($conf['superuser']) | bypasses layer 1 entirely |
@wvds | editorial contributors | write access on the language and content namespaces |
@wvdse | technical maintenance of the source mounts | full access to the mounted repositories |
@dwdo_<project>_readers | readers of a project | see layer 3 |
@dwdo_<project>_contributors | contributors of a project | see layer 3 |
@dwdo_<project>_maintainers | maintainers of a project | see layer 3 |
Layer 2: session assurance
This layer does not ask who you are but how well this one session has shown it. It is the most common reason for being signed in and turned away all the same.
| Level | Means | Reached by | Not enough for |
|---|---|---|---|
AUTHENTICATED | signed in, password is enough | ordinary sign-in | areas that require a factor |
MAIL_OTP | one-time code by e-mail confirmed | entering the code from the e-mail | areas that explicitly require TOTP |
TOTP | authenticator app confirmed | six-digit code from the app | — |
MFA_ANY | some second factor confirmed | MAIL_OTP or TOTP | — |
What a requirement can be bound to
A requirement is bound to a scope. Eight kinds exist:
namespace— every page under a prefix.plugin,module,view— a component, a module within it, a single view.command,adminAction— a command, or an administrative action.group,user— bound to the requester rather than to the target: “this group always needs TOTP”.
Where several apply, the highest holds. A session that has proved MAIL_OTP satisfies MFA_ANY and MAIL_OTP, but not TOTP.
The three older lists
| List | Effect | Blocks? |
|---|---|---|
required_users | the profile page and a site banner nag until enrolment | no, a prompt only |
email_users | may use the e-mail code even where it is generally off | no, only widens |
required_namespaces | read and write are refused until a factor is enrolled | yes |
email_users can only add a method, never take one away. A missing or unreadable policy file can therefore never lock anybody out.
Layer 3: the project role
Inside a project a ladder of its own applies. It decides which hubs appear in the navigation and who may change a project's membership.
| Role | Level | Allows | Does not allow |
|---|---|---|---|
none | 0 | nothing | any project area |
reader | 10 | view project areas and hubs | change content, see members |
contributor | 20 | additionally: create and change the project's content | manage members, change settings |
maintainer | 30 | additionally: manage members, project settings | appoint maintainers, global configuration |
admin | 40 | everything | — |
admin is the global superuser of the wiki, not a project-scoped role. There is no project-scoped administration right: the highest role obtainable within a project is maintainer. Maintainers are appointed by the global administration.
Where the role comes from
The group overlay first, then the ACL. First match wins:
- Superuser →
admin. - Member of
@dwdo_<project>_maintainers→maintainer. - Member of
@dwdo_<project>_contributors→contributor. - Member of
@dwdo_<project>_readers→reader. - Otherwise: translate the ACL value on the project root — from 16 →
maintainer, from 2 →contributor, from 1 →reader, elsenone.
The prefix dwdo_ is configurable (roleprefix). The role is always determined at the project root, never at the page you happen to be viewing — it is a property of the project, not of the request.
What the navigation hides
Hubs your role does not reach are hidden from the navigation rail. That is a display decision, not a boundary: the target page checks its own permission. A missing menu entry therefore means “there is nothing here for you”, not “something is hidden here”.
Layer 4: component tiers
Some components additionally run a ladder of their own, because their subject is not a wiki page.
Mounted source repositories
Four rungs of one ladder, expressed in the same numbers as layer 1 — so there is no second permission store, only one ACL line per group on the mount point.
| Capability | ACL value | Allows | Does not allow |
|---|---|---|---|
view | 1 | browse and view inside the wiki | clone, download |
fetch | 2 | additionally: clone and download an archive | push changes of your own |
propose | 4 | additionally: push under your own ref prefix | write to other people's refs |
write | 8 | additionally: push anywhere the repository allows | administer mount points |
New mount points come with four presets:
| Tier | Who sees it | Who writes |
|---|---|---|
private | nobody but the technical maintenance | technical maintenance |
readonly | every signed-in account (fetch) | technical maintenance |
public | everybody, signed in or not (fetch) | technical maintenance |
team | the project's readers (view) | contributors fetch, maintainers write |
team is the only tier whose groups depend on the project. It is the right choice as soon as several closed projects sit side by side: the other three know only “nobody”, “everybody signed in” and “everybody”.
SQLite Data Studio
A capability matrix rather than a ladder, because the six capabilities really are independent.
| Capability | Allows |
|---|---|
use | open the Studio at all |
query | run reading queries |
write | change data |
design | change the schema |
export | take results out |
admin | manage connections and the matrix itself |
Resolution, first match wins:
- Superuser → always allowed.
- With
dbadmin_readonlyat 1,writeanddesignare refused — even where the matrix grants them. - A rule for the connection beats a global one; within one scope an account rule beats every group rule. Group rules at the same scope are unioned.
- No matching rule → refused.
Actions that require a factor
Several components additionally require a session assurance per layer 2 for individual actions, whatever permission the account holds.
| Component | Action | Requires |
|---|---|---|
| Vault | read | AUTHENTICATED |
| Vault | create, rotate, delete, rekey | MFA_ANY |
| SQLite Data Studio | code-write, connection-admin, db-destructive | MFA_ANY |
| Resources | create, update | AUTHENTICATED |
| Resources | delete | MFA_ANY |
| Azure DevOps bridge | secret-write, remove | MFA_ANY |
| Source mounts | token-issue | AUTHENTICATED |
| Source mounts | mount-admin | MFA_ANY |
| Blog | content-admin | MFA_ANY |
Who maintains what
| Layer | Screen | Who may go there |
|---|---|---|
| 1 ACL | ?do=admin → Access control | superuser |
| 1 an account's groups | ?do=admin → User manager | superuser |
| 2 session assurance | ?do=admin → Identity | superuser |
| 3 project members | project page → Members | the project's maintainers |
| 4 mount tiers | ?do=admin → Source mounts | superuser |
| 4 capability matrix | ?do=admin → SQLite Data Studio → Permissions | whoever holds admin in the Studio |
A project's membership screen is the only one that needs no global administration. That is why it is the destination an access request about a project is routed to — see Where a request goes.
The contract for components
Your own component hangs its refusal on the same infrastructure instead of writing a sentence of its own. Two calls are enough.
A refusal about a missing permission:
$req = plugin_load('helper', 'wkrequest'); if (is_object($req) && method_exists($req, 'accessPanel')) { echo $req->accessPanel([ 'resourceLabel' => $title, 'required' => $req->permissionLabel(AUTH_READ), 'current' => $req->currentPermissionLabel($pageId), 'draft' => [ 'type' => 'ACCESS_REQUEST', 'owner' => 'myplugin', 'handler' => 'myplugin:settings', 'resource' => $pageId, ], 'alternatives' => [['label' => $otherRoute, 'href' => $url]], ]); }
A refusal about a missing second factor:
echo $req->stepUpPanel([ 'requirement' => 'MFA_ANY', 'origin' => 'myplugin:delete', 'back' => $backUrl, ]);
Sign-in, “my open requests” and the way back are appended without the call site having to remember them; each reports itself unavailable in the situation it does not fit and is then left out.
So that a request reaches the right place, the component registers its administrative screen:
$controller->register_hook('WKREQUEST_REGISTER_HANDLERS', 'BEFORE', $this, 'registerRequestHandlers'); // ... $event->data['handlers'][] = [ 'owner' => 'myplugin', 'id' => 'settings', 'label' => $this->getLang('menu'), 'url' => wl('', ['do' => 'admin', 'page' => 'myplugin'], true, '&'), 'types' => ['ACCESS_REQUEST', 'ADMIN_ACTION_REQUEST'], ];
Where a component knows the closed area better than the core does, it sharpens the panel instead of drawing a second one beside it:
$controller->register_hook('WKREQUEST_REFINE_DENIED', 'BEFORE', $this, 'refineDeniedRequest');
$event->data then carries id, resourceLabel, required, current, facts, alternatives and draft. Returned values are taken back one by one; an unusable value is discarded and the generic answer stands.
Known gaps
Places where the model today does less than the rest of this page suggests. They are listed because a gap nobody names looks like a mistake by the reader.
| Gap | Consequence | Workaround |
|---|---|---|
| Messages after an attempted action are still one-line notices, not panels. This affects the Git view, pipelines, work items and the source view among others. | After a failed click it says “you may not do that” with no route to a request. | Reload the page: the closed page carries the panel. |
| Only a request that names a project finds the people responsible on its own. Everything else goes to the globally configured address — and none is set on this installation. | A request with no project reference notifies nobody. It is stored and visible in the inbox, but nobody learns of it. | Set an address under notify_mail, or open the inbox regularly. |
@wvds and @wvdse appear in the ACL rules but are currently assigned to no account. | Rules that open something only to these groups open it to nobody. | Check the assignment in the user manager. |
| The project index that lists your role per project exists in German only so far. | Anybody using the interface in another language finds their roles one at a time, on each project page. | Project index (in German) |
Related topics
- Roles and permissions — entry point and signpost.
- How to: request access — the route from refusal to request.
- Why am I not allowed to do this? — symptoms and their causes.