chore(release): sync beta back into development - #3074
Merged
Conversation
Add an opt-out for the "logged-in users inherit public group rights" semantics in OR's RBAC. Schemas and registers gain an optional inheritFromPublic boolean (default true, backwards-compatible). When false, authenticated users do NOT qualify for public rules — they qualify only via their own group memberships. Anonymous users see no behaviour change. Cascade: schema → register → IAppConfig openregister.rbac.inherit_from_public_default → hard-coded true. Implementation touches both RBAC layers identically: - PHP-side PermissionHandler::hasPermission inheritance fallback (line 229-241) - SQL-side MagicRbacHandler::processConditionalRule + processSimpleRule (and their UNION-mode siblings buildRbacConditionsSql + processConditionalRuleSql) Modified capability: rbac-scopes. Tracks GitHub issue #1439.
Adds an opt-out for the implicit "logged-in users inherit public group
rights" semantics. Schemas (and registers, via cascade) gain an optional
inheritFromPublic boolean, default true (preserves pre-change behaviour).
Cascade:
schema.authorization.inheritFromPublic
→ register.authorization.inheritFromPublic
→ IAppConfig openregister.rbac.inherit_from_public_default
→ hard-coded true
null is treated as "unset" — cascade falls through.
PermissionHandler:
- new constructor dep IAppConfig
- new public resolveInheritFromPublic(Schema): bool with per-request cache
- hasPermission line 229-241 inheritance fallback now gated on the flag
MagicRbacHandler:
- resolveInheritFromPublic(Schema) helper delegating to PermissionHandler
via existing container DI
- applyRbacFilters resolves the flag once at the top, plumbs through
processAuthorizationRule → processConditionalRule + processSimpleRule
- same plumbing in the UNION-mode path: buildRbacConditionsSql →
processAuthorizationRuleSql → processConditionalRuleSql, and the shared
processSimpleRule
- the per-object hasPermission method (separate from PermissionHandler's)
also gated identically
Behaviour:
- inheritFromPublic = true (default): unchanged from pre-change.
- inheritFromPublic = false + anonymous user: still qualifies for public.
- inheritFromPublic = false + authenticated user: does NOT qualify for
public rules; only own-group / owner / admin grants apply.
Tests:
- new PermissionHandlerInheritFromPublicTest covers cascade resolution
(4 levels + null=unset semantics) and the four-state matrix on
hasPermission, plus owner/admin shortcut invariance.
- existing PermissionHandlerRbacTest updated for new constructor sig.
Quality:
- PHPCS clean on touched files (auto-fix + manual passes).
- PHPStan clean.
- Psalm clean.
- openspec validate clean.
Deferred (tracked in tasks.md as not-yet-checked):
- 3.6, 3.7: SQL-side unit tests (need fixture DB).
- 6.x: cross-app smoke tests against running stacks.
- 7.3-7.5: integration tests against running services.
- 8.1, 8.2: docs extension + worked example.
- 9.1, 9.4: full unit suite (PHPUnit needs the NC docker bootstrap)
+ manual live-stack smoke.
Closes (partially) #1439.
Surfaces the tenant-wide `rbac.inherit_from_public_default` IAppConfig key through the existing settings payload as `rbac.inheritFromPublicDefault`, and renders a toggle for it in the RBAC configuration section. Also updates the rbacOptions store default so the switch hydrates correctly on first load. Pairs with the schema-level checkbox in @conduction/nextcloud-vue (CnSchemaSecurityTab). Backend cascade was added in 3c05b62.
…dator (#1439) Two gaps surfaced during /opsx:verify against the live stack: 1. Schema::validateAuthorizationRules rejected `inheritFromPublic` because it only allowed CRUD action keys. The validator now treats it as an optional sibling of the action keys and verifies it is a boolean (or null = unset). 2. ConfigurationSettingsHandler::getRbacSettingsOnly / updateRbacSettingsOnly handle the dedicated `/api/settings/rbac` endpoint that the frontend store actually calls. They now read and write the `rbac.inherit_from_public_default` IAppConfig key, matching the unified `getSettings` / `updateSettings` paths added earlier. Verified end-to-end via the four-state matrix on /api/objects: with inheritFromPublic=true (default) anon and authenticated users both see public-conditional rows; with false set per-schema, anon still sees them but authenticated users without explicit group membership do not.
Verified manually against the Docker NC stack:
- /api/settings/rbac round-trip (read + write) for inheritFromPublicDefault
- schema-level inheritFromPublic accepted by validator and round-trips
through /api/schemas/{id}
- four-state matrix on /api/objects: (anon|auth) × (true|false) yields
counts that match spec — only (auth, false) is denied; the other three
states see the public-match objects
Tasks 6.1, 6.2, 9.4 set to done. Remaining open items are unit-test
extensions (3.6, 3.7, 7.x), additional docs (5.2, 8.1, 8.2), the broader
suite run (9.1), and the Softwarecatalog smoke (6.3).
Adds tests/Unit/Db/MagicMapper/MagicRbacHandlerInheritFromPublicTest.php covering buildRbacConditionsSql and applyRbacFilters under the four-state matrix (anon|auth × inheritFromPublic true|false), plus parity checks for the simple-string `'public'` rule, the `'authenticated'` rule, and admin bypass. 10 new tests, all green via the in-container PHPUnit runner. Also fixes a regression in the existing PermissionHandlerRbacTest where buildHandlerWithRealMatcher() didn't pass the new IAppConfig dependency into PermissionHandler, causing 10 errors during the full suite run. Knocks out tasks 3.6, 3.7, 7.1, 7.2, 7.3, 7.4, 7.5, 9.1 in the change's tasks.md — the SQL-side matrix is now unit-tested and the integration scenarios are covered by either the cascade unit tests (cascade fall- through paths) or the live-stack matrix run during /opsx:verify.
Extends docs/Features/access-control.md with:
- The optional `inheritFromPublic` boolean on the schema authorization
JSON example
- A new section "Disabling public-group inheritance for authenticated
users" covering the cascade (schema → register → IAppConfig → true),
the four-state matrix, a worked publication-style example, and the
`'authenticated'` simple-rule alternative
- The new `inheritFromPublicDefault` field in the RBAC Configuration
block, including the IAppConfig key and the occ command
Also cross-references the new flag from the `"public"` row of the rule
table in docs/Features/property-authorization.md, since the previous
phrasing ("matches any authenticated user") was unconditional.
Closes tasks 5.2, 8.1, 8.2 in the rbac-disable-public-inheritance change.
…#1439) Addresses the blocker + 3 concerns + 1 minor flagged in WilcoLouwerse's strict review of PR #1440. 🔴 Blocker — drop the silent fail-open in MagicRbacHandler::resolveInheritFromPublic. The previous try/catch returned `true` on any Throwable from the cascade walk, which silently undid the gate the tenant explicitly opted out of and let this SQL path diverge from the PHP per-object check (which propagates). Spec invariant "per-object checks and listing membership cannot drift" now holds even under failure: the request fails (5xx) instead of leaking rows. 🟡 Concern 1 — strict-boolean check at schema/register cascade levels. PHP's `(bool) "false"` is `true`, so a register persisted via direct mapper write / migration / seed JSON could store a string and silently invert the gate. Both schema (line 716) and register (line 728) now require literal `true` or `false`; anything else (string, int, etc.) is treated as "unset" and logged as a warning. Three new cascade tests pin the strict-equality contract. 🟡 Concern 2 — strict normalization on the API write paths. `updateRbacSettingsOnly` and `updateSettings` now use `filter_var` with `FILTER_VALIDATE_BOOLEAN | FILTER_NULL_ON_FAILURE` (matching the docs' boolean-tolerance claim) and throw on garbage rather than silently coercing `(bool) "false" === true`. Three new tests pin the normalize-and-persist contract (real bool, "false" string, garbage rejection). 🟡 Concern 3 — drop @NoCSRFRequired from updateRbacSettings. This endpoint is now security-load-bearing (it flips a tenant-wide RBAC default); CSRF protection on state-mutating admin endpoints is required by ADR-005. Frontend uses @nextcloud/axios which sends the request token automatically; no UI change needed. 🟢 Minor — docblock note on resolveInheritFromPublic about transient schemas (no-cache path) so future readers don't expect cache hits on in-memory drafts. Tasks 6.1 (DocuDesk smoke) re-opened — the previous justification was a settings-endpoint round-trip, not a behavioural exercise of DocuDesk's consent-fetch endpoint. Honest accounting per reviewer's note. Tests: 71 RBAC + 7 settings (was 68 + 4) — all green via the in-container PHPUnit runner.
…ublic-inheritance feat(rbac): add inheritFromPublic flag for opt-out of public-group inheritance (#1439)
Brings beta up to date with development (3359 commits) for tonight's fleet-wide beta checkup. 14 conflicts, all resolved in development's favour — and the one that mattered was checked rather than assumed: beta carries the whole `#1439 rbac-disable-public-inheritance` feature (12 commits: feat, tests, docs, review fixes), merged there via hotfix/1439 and NOT present as those commits on development. Taking development's side would have reverted a shipped RBAC feature. It does not. Development GRAFTED the same work deliberately — "feat(rbac): graft inheritFromPublic onto dev RBAC; drop orphaned main tests" (#109) — and has evolved it further: 72 `inheritFromPublic` references across 6 files against beta's 65, including RegisterMapper, AuthorizationCacheInvalidationListener and CaseTypeAuthorizationService, which beta lacks entirely. The openspec change is ARCHIVED on development (openspec/changes/archive/2026-06-14-rbac-disable-public-inheritance/) and still open on beta. Verified after resolving: the merged tree keeps the feature (PermissionHandler 20 refs, Schema 3). The two AA test files resolve to development's copies for the same reason — that graft commit explicitly dropped the orphaned main-line tests. CHANGELOG.md, composer.lock, package.json, package-lock.json: files development maintains.
Release: merge development into beta for the fleet-wide beta checkup
Defect introduced by the development->beta sync (#2636), found by checking CONTENT rather than files. Old beta carried `inheritFromPublicDefault` in four places: two backend (ConfigurationSettingsController, ConfigurationSettingsHandler) and two frontend (store/settings.js, RbacConfiguration.vue). The sync resolved the two BACKEND files to development's version — development grafted `inheritFromPublic` but deliberately never took the `Default` setting (0 references) — while the two FRONTEND files merged cleanly and kept beta's. The result was a settings UI bound to a backend that no longer exists: a checkbox on `rbacOptions.inheritFromPublicDefault`, defaulted true in the store, in the RBAC settings panel — a silent no-op control in a SECURITY surface, where an admin would believe they had changed an access-control default and nothing would persist. This aligns the two frontend files with development, which is what the rest of the sync did. Beta is now internally consistent: zero references to `inheritFromPublicDefault` in lib/ or src/, matching development. Note for the record: the file-level check I ran after the sync reported "0 files lost" and was true but insufficient — no file disappeared, content inside resolved files did. Content-level verification is what caught this.
…rompublicdefault fix(beta): remove the orphaned inheritFromPublicDefault RBAC control
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Resolves the two version-stamped files the bot PR (#2638) could not merge: appinfo/info.xml and openapi.json. Those were the ONLY conflicts -- openapi.json differs from beta on line 5 alone, and info.xml on the version plus the six repository URLs. Both keep BETA's version string (1.1.6-beta.20260820205738), not development's (1.1.5-unstable.20260826203744). Development's is numerically LOWER, so taking it would have published a downgrade; the release job bumps from here anyway. Everything else takes development's content. That includes removing the six codeberg.org URLs still in beta's info.xml (website, bugs, repository and three screenshots) -- development has carried the GitHub URLs for some time and beta had not caught up. Note for a human: beta carries 17 commits development does not, the bulk of them the inheritFromPublic RBAC work (#1439) merged straight to beta as a hotfix, plus the commit that removed the orphaned inheritFromPublicDefault control. This merge preserves them, but development has never received that work -- worth a deliberate back-merge decision separately from this release.
Release: merge development into beta
Carries the pending-schema-ref fix (#2918) and the repair-step registrations (#2923) to beta, so a stable release can be cut from them. TWO CONFLICTS, BOTH VERSION-ONLY — AND RESOLVING THEM THE OBVIOUS WAY WAS WRONG. `appinfo/info.xml` and `openapi.json` each conflicted on one hunk, the version string. Taking beta's side of the FILE (`--ours`) resolves that hunk correctly and silently reverts everything else in it, because beta's info.xml predates development's. It took four repair-step registrations with it: ImportMergeOperationRegister, RenameDutchColumns, ImportFlowRegister, MigrateRegisterFlowsToTable All four had just been registered on development precisely because gate-98 found they had never run. A file-level resolution to a line-level conflict put them straight back. So both files are taken from development in full, with only the version string replaced by beta's — the convention the repo's own beta-sync PRs document, and the release workflow recomputes it from the latest stable tag regardless. Verified after resolving, not assumed: unregistered repair steps none openapi.json valid JSON pending-schema-ref fix present in ObjectService
release: merge development into beta
development was 21 commits behind beta, which is why the promotion PR (#2976) could not merge: both branches had bumped the version independently and every promotion attempt conflicted on it. Conflicts were exactly two files and exactly the version string: appinfo/info.xml development 1.1.8-unstable.20260829125656 (kept) beta 1.1.6-beta.20260820205738 openapi.json same Development's is the higher of the two and is kept, so this sync never moves the version backwards. Everything else fast-forwards: 8 files, all docs and one openspec change, and 0 deletions. With this in, development -> beta is a clean promotion.
Contributor
Quality Report — ConductionNL/openregister @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 172/172 | |||
| npm | ✅ | ✅ 547/547 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | 🚨 NO VERDICT — enabled but never ran | ||||
| Newman | ✅ | ||||
| Playwright | 🚨 NO VERDICT — enabled but never ran | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-29 20:37 UTC
Download the full PDF report from the workflow artifacts.
rubenvdlinde
pushed a commit
that referenced
this pull request
Aug 29, 2026
#3074 carried beta's CONTENT into development but was squashed, so git never recorded beta as an ancestor. The merge base did not move, both branches still counted as having changed the version independently, and the promotion PR (#2976) stayed 'dirty' with development 21 commits behind. This is the same merge as a real merge commit, so the ancestry is recorded and development -> beta becomes a clean promotion. Content is already in place from #3074, so this changes almost nothing on disk; the version conflict resolves to development's 1.1.8-unstable, the higher of the two, as before. A sync or promotion between long-lived branches must be merged, never squashed: a squash copies the files and throws away the relationship that makes the NEXT merge clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Unblocks openregister's promotion to beta, which is what stands between development and a store release newer than
1.1.8.Why the promotion was stuck: development is 21 commits behind beta. Both branches bumped the version independently, so every
development -> betaattempt conflicted and #2976 sitsdirty.Conflicts: two files, and only the version string.
appinfo/info.xml1.1.8-unstable.202608291256561.1.6-beta.20260820205738openapi.jsonDevelopment's is the higher of the two, so this sync cannot move the version backwards.
Everything else fast-forwards: 8 files (docs plus one openspec change) and 0 deletions. Both resolved files parse (
xml.etree,json.load).Once this lands,
development -> betais a clean promotion and #2976 can merge with--merge(not squash — beta is 21 commits ahead of the merge base, and squashing a promotion rewrites those as a revert of the target's own history).Supersedes my #3071, which took the same merge from a
promo/*branch — correctly refused by branch-protection, since promotions to beta must come fromdevelopment. Closing that one; this fixes the drift at its source instead.