Skip to content

fix(rbac): grant authenticated read on every openbuild schema (#76) - #81

Merged
rubenvdlinde merged 1 commit into
developmentfrom
wip/schema-read-authenticated
Aug 1, 2026
Merged

fix(rbac): grant authenticated read on every openbuild schema (#76)#81
rubenvdlinde merged 1 commit into
developmentfrom
wip/schema-read-authenticated

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Fixes #76.

Root cause

Every openbuild schema declared a non-empty authorization block with no read key:

"authorization": { "create": ["admin"], "update": ["admin"], "delete": ["admin"] }

OpenRegister treats that as fail-closed. MagicRbacHandler::buildRbacConditionsSql() bypasses filtering only for an empty block; a populated block with no read rule produces $rules = [] and falls through to the owner condition alone — so every non-admin caller matched nothing and saw zero rows.

That is not an OR bug. It is deliberate, and commented as such at MagicRbacHandler:1031.

It is the whole of #76: an owner could grant a colleague editor or viewer on an app, the grant landed and read back correctly, and they still saw an empty list — because OR filtered the objects out one layer below openbuild's own permission check.

The earlier diagnosis on this issue (a create gate on the read path) was wrong. Corrected with evidence in ConductionNL/openregister#2252 — the function carrying that gate turned out to have no caller at all.

The fix

"read": ["authenticated"] on all 15 schemas — 6 in openbuild_register.json and 9 across register.d/ fragments. The fragments were missed on the first pass and would have left business rules, automations, component blocks and the agent workspace owner-only.

authenticated requires $userId !== null (MagicRbacHandler:414), so anonymous callers are not granted.

This is intentionally the coarse layer. appinfo/routes.php already documents that OR's schema read rule is a group ACL rather than a row filter, and that the per-app permissions block is enforced by /api/applications. Both layers are verified below.

Verification — live, after a FORCED re-import

A normal import advances the version without applying the change; this used POST /api/settings/load.

caller OR object API openbuild /api/applications
admin 21 21
rbac-editor (editor on pw-verchain) 0 → 21 1
rbac-viewer (viewer on pw-verchain) 0 → 21 1
rbac-outsider (no grant) 0 → 21 0
anonymous 0 401

Both layers behave as designed: OR grants the coarse authenticated read, openbuild's endpoint filters it down by app role, and anonymous is excluded at both.

All 15 schemas re-read from the live API to confirm the authorization actually applied, rather than trusting the JSON.

🤖 Generated with Claude Code

Every openbuild schema declared

    "authorization": { "create": ["admin"], "update": ["admin"], "delete": ["admin"] }

— non-empty, and with no `read` key. OpenRegister treats that as fail-closed:
`MagicRbacHandler::buildRbacConditionsSql()` bypasses filtering only for an
EMPTY block; a populated block with no `read` rule falls through to the owner
condition alone, so every non-admin caller saw zero rows. Not a bug in OR —
deliberate, and commented as such at MagicRbacHandler:1031.

That is the whole of #76. An owner could grant a colleague editor or viewer on
an app and they still saw an empty list, because OR filtered the objects out
one layer below openbuild's own permission check.

Adds `"read": ["authenticated"]` to all 15 schemas — 6 in the monolith and 9
across the register.d fragments, which were missed by the first pass and would
have left business rules, automations, component blocks and the agent
workspace owner-only.

`authenticated` requires $userId !== null (MagicRbacHandler:414), so anonymous
callers are NOT granted. This is intentionally the coarse layer: appinfo/routes.php
already documents that OR's schema read rule is a group ACL, not a row filter,
and that the per-app `permissions` block is enforced by /api/applications. Both
layers verified live.

Measured on the disposable instance after a FORCED re-import:

  caller          OR object API   openbuild /api/applications
  admin                      21   21
  rbac-editor          0 -> 21    1  (granted editor on pw-verchain)
  rbac-viewer          0 -> 21    1  (granted viewer on pw-verchain)
  rbac-outsider        0 -> 21    0  (no grant)
  anonymous                   0   401

Diagnosis trail: ConductionNL/openregister#2252.
@rubenvdlinde
rubenvdlinde merged commit b6dd2ae into development Aug 1, 2026
18 checks passed
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openbuild @ 17a893f

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 100/100
npm ✅ 659/659
PHPUnit
Newman ⏭️
Playwright ⏭️

Coverage: 89.4% (17/19 statements)


Quality workflow — 2026-08-01 21:24 UTC

Download the full PDF report from the workflow artifacts.

rubenvdlinde added a commit that referenced this pull request Aug 2, 2026
…ns can use the app (#87)

Picks up ConductionNL/nextcloud-vue#576: a 401/403 from /api/setup/status now
empties the unmet-steps lists, so a non-admin is no longer shown the
first-time-setup wizard.

Why this bump matters more than the version delta suggests: after #81 gave
non-admins OpenRegister-level read on the schemas, an editor still could not
use OpenBuild. They landed on "Welcome to OpenBuild / Set up this app" with
.openbuild-schema-list count 0 — the setup endpoints are admin-only, they
answer 403, and useSetupStatus read that as "setup unfinished".

Also corrects a drift found on the way: node_modules held 2.1.0-vue3.7 while
package.json and the lockfile both said 2.1.0-vue3.13. Nobody had run an
install since that bump, so local builds were linking a library three versions
behind the pin. This commit was built from a clean `npm ci`.

New: tests/e2e/non-admin-access.spec.ts, a regression suite for the OUTCOME
rather than any one layer. Two consecutive fixes for this looked green while
the app stayed broken for the user — openbuild#76's grant, then a nc-vue fix
that short-circuited `completed`, which CnAppRoot never reads. The only
assertion that would have caught both is "a non-admin sees the app", so that
is what these three tests assert:

  - an editor reaches the schema designer AND no setup wizard (both halves —
    asserting the wizard's absence alone passes on a blank page)
  - an editor sees the app they were granted
  - an outsider sees none — the control proving openbuild's row-level filter
    still runs on top of OR's coarse `authenticated` read grant

Verified live against the disposable instance: 3 passed.

Co-authored-by: Ruben van der Linde <juan.claude@conduction.nl>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Group-based app grants do not make an Application visible to its editors/viewers

2 participants