Skip to content

feat(BA-6837): authorize fragment reads via allow_list read_access tier#12764

Closed
jopemachine wants to merge 13 commits into
feat/BA-6836-app-config-fragment-write-authzfrom
feat/BA-6837-app-config-read-authz
Closed

feat(BA-6837): authorize fragment reads via allow_list read_access tier#12764
jopemachine wants to merge 13 commits into
feat/BA-6836-app-config-fragment-write-authzfrom
feat/BA-6837-app-config-read-authz

Conversation

@jopemachine

@jopemachine jopemachine commented Jul 10, 2026

Copy link
Copy Markdown
Member

Summary

Enforces AppConfig read authorization (BA-6837, BEP-1052 / epic BA-5781) — the read-side mirror of BA-6836 (#12759). A config layer's fragments contribute to a caller's read only when the caller satisfies that layer's read_access tier (reusing AppConfigAccessLevel.is_satisfied_by). No RBAC validator; the allow-list access tiers are AppConfig's authorization system.

How

  • Read query list_visible_fragments_bulk now selects each layer's read_access and returns VisibleFragment(data, read_access) instead of bare fragment data.
  • resolve / resolve_bulk drop layers the caller may not read before merging (exact — these are the security-critical end-user paths). resolve_public (scope=None, requester=None) yields only read_access == public layers.
  • scoped_search filters the returned page by each item's tier (looked up via by_config_and_scope, cached).
  • Read actions carry the caller: requester=current_user() (authenticated resolve / scoped-search), requester=None (anonymous public).
  • No route changes — middleware unchanged (resolve/scoped-search = auth, public = anonymous).

Default policy → behavior preserved

Default read_access (public=public, domain=authenticated, user=owner) matches the existing scope-visibility filter, so default reads are unchanged; only an admin-set non-default read_access (e.g. a public layer set to admin) further restricts.

Notes

  • scoped_search gates the returned page post-fetch; total_count / has_next_page still reflect the visible-scope match count before the read_access gate (paginated counts are not re-derived) — documented in-code. The merged reads (resolve / public) filter fully, so they are exact.
  • superadmin reads every layer; anonymous reads only public-read layers.

📚 Stacked PRs

Part of the AppConfigFragment / AppConfig stack under BEP-1052 (epic BA-5781). Merge in order:

  1. feat(BA-6552): add app_config_fragments DB model and Alembic migration #12306feat(BA-6552): app_config_fragments DB model and Alembic migration
  2. feat(BA-6553): add app_config_fragments repository layer #12307feat(BA-6553): repository layer
  3. refactor(BA-6619): consolidate AppConfigScopeType into common.data (single definition) #12403refactor(BA-6619): consolidate AppConfigScopeType into common.data
  4. refactor(BA-6620): ExistsQuerier ops primitive + AppConfigAllowList.exists #12405refactor(BA-6620): ExistsQuerier + AppConfigAllowList.exists
  5. feat(BA-6554): add app_config_fragment service layer #12358feat(BA-6554): AppConfigFragment service layer
  6. feat(BA-6702): move fragment rank to the allow list with scope defaults #12516feat(BA-6702): move fragment rank to the allow list with scope defaults
  7. feat(BA-6701): expose allow-list rank on the v2 API surface #12517feat(BA-6701): expose allow-list rank on the v2 API surface
  8. feat(BA-6704): cascade app config subtree deletion from the definition #12518feat(BA-6704): cascade app config subtree deletion from the definition
  9. feat(BA-6626): app_config_fragment bulk repository layer #12426feat(BA-6626): app_config_fragment bulk repository layer
  10. feat(BA-6618): app_config_fragment bulk CRUD service layer #12401feat(BA-6618): app_config_fragment bulk CRUD service layer
  11. feat(BA-6810): AppConfigFragment visible-fragments query (repository layer) #12706feat(BA-6810): AppConfigFragment visible-fragments query (repository layer)
  12. feat(BA-6834): AppConfig allow_list permission column (ro/rw, model + migration) #12794feat(BA-6834): AppConfig allow_list read/write access levels (model + migration) — read/write separation foundation
  13. feat(BA-6555): AppConfig merge engine + resolve service (service layer) #12359feat(BA-6555): AppConfig merge engine + resolve service (service layer)
  14. feat(BA-6556): AppConfig REST v2 API (raw fragments and merged read/update) #12377feat(BA-6556): AppConfig REST v2 API
  15. feat(BA-6835): expose the allow_list permission on the v2 API #12756feat(BA-6835): expose read/write access level on the allow_list v2 API
  16. feat(BA-6860): RBAC-gate app_config fragment writes at the processors #12759feat(BA-6836): authorize fragment writes via allow_list write_access tier
  17. 👉 feat(BA-6837): authorize fragment reads via allow_list read_access tier #12764feat(BA-6837): authorize fragment reads via allow_list read_access tier ← you are here

AppConfig is treated as RBAC-Global with no RBAC validator; app_config_allow_list's access tiers are AppConfig's standalone authorization system. The access-level columns sit at the base so every layer above builds on the read/write-separated model.

jopemachine and others added 13 commits July 13, 2026 13:20
…w_list

Decouple read-enablement from write-authorization. Add AppConfigAccessLevel
(public < authenticated < owner < admin) and read_access / write_access columns
on app_config_allow_list, so the row registers a config layer + its merge rank
without its existence implying write access. Creator/updater default and accept
the tiers per scope-type policy (public: read=public/write=admin, domain:
read=authenticated/write=admin, user: read=owner/write=owner); migration
backfills existing rows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve the merged AppConfig for a (user, config_name) on top of the repository's
visible-fragments query:
- AppConfigData: the merged per-user view (ordered fragments + deep-merged config; None
  when no fragment contributes)
- deep-merge in rank order — nested dicts recurse, lists/scalars are replaced wholesale by
  the higher-rank fragment
- resolve / resolve_bulk actions + processors (bulk repeats duplicate names in order)
- mocked-repository service tests

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface read_access / write_access (AppConfigAccessLevel) on the
app_config_allow_list v2 API, mirroring rank / scope_type: response node +
create/update inputs (DTO, GraphQL), adapter mapping and creator/updater spec
threading, SDK request/response, and CLI create/update options. Create defaults
to the scope-type policy when omitted; update leaves omitted tiers unchanged.
Regenerated the GraphQL schema dump.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: octodog <mu001@lablup.com>
…tier

Enforce AppConfig's standalone write authorization: each fragment write is
checked against the write_access tier of the target (config_name, scope_type)
allow_list entry (AppConfigAccessLevel.is_satisfied_by) — no RBAC validator.
A user may manage their own user-scope fragment; public/domain default to
admin-only; superadmin passes any tier. create authorizes from the creator
spec; update/purge load the fragment first; bulk writes authorize per item
(unauthorized items become partial failures). Fragment write routes switch
from superadmin_required to auth_required; the service gains the allow_list
repository (by_config_and_scope) for the tier lookup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: octodog <mu001@lablup.com>
Gate merged-config reads by the read_access tier of each visible layer's
allow_list entry, reusing AppConfigAccessLevel.is_satisfied_by. resolve /
resolve_bulk drop layers the caller may not read before merging (exact);
resolve_public (scope=None, requester=None) yields only public-read layers;
scoped_search filters the returned page by each item's tier. The read query
list_visible_fragments_bulk now returns VisibleFragment(data, read_access);
read actions carry the caller (current_user, or None for anonymous public).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jopemachine jopemachine force-pushed the feat/BA-6836-app-config-fragment-write-authz branch from b65cd3a to 6f1d562 Compare July 13, 2026 04:27
@jopemachine jopemachine force-pushed the feat/BA-6837-app-config-read-authz branch from 23f8791 to fe828c8 Compare July 13, 2026 04:27
@jopemachine jopemachine force-pushed the feat/BA-6836-app-config-fragment-write-authz branch from 6f1d562 to eff9831 Compare July 13, 2026 05:13
@jopemachine

Copy link
Copy Markdown
Member Author

Closing: read authorization is no longer a separate concern. In the finalized design, the allow-list carries a single permission (ro/rw) column (VFolder-style) and reads use the existing scope-visibility (public→everyone, domain→members, user→owner) with no per-config read policy. So there is no read_access to enforce — this PR's work is dropped. Write authorization (rw + scope owner) lives in #12759.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp:manager Related to Manager component size:L 100~500 LoC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant