Skip to content

chore(release): sync beta back into development - #3074

Merged
rubenvdlinde merged 22 commits into
developmentfrom
chore/sync-beta-into-development
Aug 29, 2026
Merged

chore(release): sync beta back into development#3074
rubenvdlinde merged 22 commits into
developmentfrom
chore/sync-beta-into-development

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

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 -> beta attempt conflicted and #2976 sits dirty.

Conflicts: two files, and only the version string.

file development beta kept
appinfo/info.xml 1.1.8-unstable.20260829125656 1.1.6-beta.20260820205738 development
openapi.json same same development

Development'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 -> beta is 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 from development. Closing that one; this fixes the drift at its source instead.

rjzondervan and others added 22 commits May 7, 2026 13:08
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.
…1439)

Five auto-fixable violations the local cached run missed:

  - 2× "Expected 1 blank line after function; 2 found" between the
    `coerceStrictBoolOrLog` helper and its neighbours
  - 3× parameter-type alignment in the helper's @PARAM block

Picked up by composer phpcs (CI scope: lib/) on PR #1441.
…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.
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.
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openregister @ 277f648

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
rubenvdlinde merged commit 81e1aa7 into development Aug 29, 2026
46 checks passed
@rubenvdlinde
rubenvdlinde deleted the chore/sync-beta-into-development branch August 29, 2026 20:57
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.
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.

3 participants