Summary
OpenRegister's RBAC currently treats public as universally inclusive — every read rule that targets public is also evaluated for authenticated users. This is the explicit "logged-in users should also have at least the same rights as 'public' users" semantics in PermissionHandler::hasPermission (line 229-241) and the matching qualification logic in MagicRbacHandler::processConditionalRule (if ($group === 'public') $userQualifies = true).
That's the right default for most schemas — but for tiered visibility flows (a public catalogue with date-windowed visibility plus a separate authenticated curated view) and for privacy-strict schemas (access earned only through explicit group membership), inheritance leaks unintended access.
This change adds an opt-out: an inheritFromPublic boolean (default true) at the authorization-block level (schema-level, with register-level cascade and tenant-wide IAppConfig default). When false, authenticated users do NOT qualify for public rules — they qualify only via their own group memberships. Anonymous users see no behaviour change. Default stays true, so existing schemas are unaffected.
OpenSpec change directory: openspec/changes/rbac-disable-public-inheritance/
Specs
- rbac-scopes (delta) — schema/register authorization gains
inheritFromPublic boolean; PHP-side hasPermission and SQL-side applyRbacFilters honour the flag identically. Delta: specs/rbac-scopes/spec.md
Cross-app
No paired changes. Default true preserves current behaviour for all consumers (DocuDesk, OpenCatalogi, Softwarecatalog, Procest, Pipelinq, ZaakAfhandelApp). The OpenCatalogi PublicationsController flow that surfaced the original use case automatically benefits once a publication schema sets inheritFromPublic: false.
Tasks
1. resolveInheritFromPublic helper
2. PHP-side enforcement
3. SQL-side enforcement
4. Schema entity / serialisation
5. Tenant default IAppConfig
6. Cross-app integration check
7. Unit + integration tests
8. Documentation
9. Quality and verification
Summary
OpenRegister's RBAC currently treats
publicas universally inclusive — every read rule that targetspublicis also evaluated for authenticated users. This is the explicit "logged-in users should also have at least the same rights as 'public' users" semantics inPermissionHandler::hasPermission(line 229-241) and the matching qualification logic inMagicRbacHandler::processConditionalRule(if ($group === 'public') $userQualifies = true).That's the right default for most schemas — but for tiered visibility flows (a public catalogue with date-windowed visibility plus a separate authenticated curated view) and for privacy-strict schemas (access earned only through explicit group membership), inheritance leaks unintended access.
This change adds an opt-out: an
inheritFromPublicboolean (defaulttrue) at the authorization-block level (schema-level, with register-level cascade and tenant-wide IAppConfig default). Whenfalse, authenticated users do NOT qualify forpublicrules — they qualify only via their own group memberships. Anonymous users see no behaviour change. Default staystrue, so existing schemas are unaffected.OpenSpec change directory:
openspec/changes/rbac-disable-public-inheritance/Specs
inheritFromPublicboolean; PHP-sidehasPermissionand SQL-sideapplyRbacFiltershonour the flag identically. Delta:specs/rbac-scopes/spec.mdCross-app
No paired changes. Default
truepreserves current behaviour for all consumers (DocuDesk, OpenCatalogi, Softwarecatalog, Procest, Pipelinq, ZaakAfhandelApp). The OpenCatalogi PublicationsController flow that surfaced the original use case automatically benefits once a publication schema setsinheritFromPublic: false.Tasks
1. resolveInheritFromPublic helper
private array $cachedInheritFromPublic = [];field onPermissionHandler.phpfor per-request caching keyed by schema ID.resolveInheritFromPublic(Schema $schema): boolimplementing the cascade: schema authorization → register authorization → IAppConfigopenregister.rbac.inherit_from_public_default→ hard-codedtrue. Treatnullas "unset".2. PHP-side enforcement
PermissionHandler::hasPermissionlines 229-241, wrap the inheritance fallback inif ($this->resolveInheritFromPublic($schema) === true).hasPermission.3. SQL-side enforcement
MagicRbacHandler::applyRbacFilters, resolveinheritFromPubliconce at the top.processAuthorizationRule→processConditionalRuleandprocessSimpleRule.processConditionalRule: when$group === 'public'ANDinheritFromPublic === falseAND$userId !== null, set$userQualifies = false.processSimpleRule: when$rule === 'public'ANDinheritFromPublic === falseAND$userId !== null, returnfalse.buildRbacConditionsSql,processConditionalRuleSql.applyRbacFiltersfour-state matrix.buildRbacConditionsSqlfour-state matrix.4. Schema entity / serialisation
Schema::getAuthorization/setAuthorizationround-trips preserveinheritFromPublic.Register::getAuthorizationsimilarly preserves the field.5. Tenant default IAppConfig
openregister.rbac.inherit_from_public_default(read by helper).getValueBoolor equivalent.6. Cross-app integration check
7. Unit + integration tests
applyRbacFiltersandbuildRbacConditionsSql.inheritFromPublic: false+ public-conditional read; verify anon allowed, auth denied without explicit group, auth allowed with explicit group.8. Documentation
rbac-scopesdocumentation with the new field, cascade, four-state matrix,authenticated-rule alternative.inheritFromPublic: false.9. Quality and verification
inheritFromPublic: falseand public-conditional read; verify the four-state matrix manually.openspec validate rbac-disable-public-inheritance— clean.