Skip to content

refactor: decouple dashboard widget filtering from webware-acl - #13

Merged
tyrsson merged 5 commits into
0.1.xfrom
refactor-acl-decoupling
Aug 24, 2026
Merged

refactor: decouple dashboard widget filtering from webware-acl#13
tyrsson merged 5 commits into
0.1.xfrom
refactor-acl-decoupling

Conversation

@tyrsson

@tyrsson tyrsson commented Aug 23, 2026

Copy link
Copy Markdown
Member

Summary

Removes the hard runtime dependency on webware/webware-acl by type-hinting Laminas\Permissions\Acl\AclInterface in the dashboard widget pipeline. The host application supplies the ACL implementation via service aliasing (in practice webware-acl's Acl, which already implements the laminas interface).

Changes

  • composer.json: webware/webware-acl moved from require to suggest; acl VCS repository removed; mezzio/mezzio-authentication-session added to require-dev
  • src/ConfigProvider.php: ships default authorization rules under the mezzio-authorization-acl config key, matching the structure required by mezzio's laminas-permissions-acl integration package — roles User + Administrator (child of User), resource webware.admin.dashboard.read (the admin dashboard route name), allow Administrator. webware-acl is database driven and does not consume this config; the defaults exist for hosts using laminas-permissions-acl, and the config aggregator merges them with host config
  • src/Middleware/DashboardMiddleware.php: reads the user from Mezzio\Authentication\UserInterface::class, type-hints laminas AclInterface; fails closed when the user is not role-aware (zero widgets, never unfiltered)
  • src/Widget/AclWidgetFilterIterator.php: re-typed to laminas AclInterface / ?RoleInterface; null user denies all
  • src/Container/DashboardMiddlewareFactory.php: laminas AclInterface import
  • Tests: rewrote AclWidgetFilterIteratorTest (unskipped, GenericRole + fail-closed), new DashboardMiddlewareTest (4 unit tests) and DashboardMiddlewareIntegrationTest (2 integration tests using real laminas Acl + real mezzio PhpSession, no database), ConfigProviderIntegrationTest asserts the mezzio-authorization-acl defaults and that admin registers no ACL service
  • docs/dashboard-widget-system.md + plan/refactor-acl-decoupling-1.md updated

Conventions honored

  • The route name is the resource; privileges are encoded in the trailing route-name segment (e.g. webware.admin.dashboard.read) — same convention as webware-acl's database rules
  • REQ-003: widget filtering remains in webware-admin and fails closed
  • REQ-004: widgets are always filtered; integration tests use laminas-permissions-acl without a DB
  • CON-002: auth testing uses mezzio-authentication-session (dev only), no database

Validation

  • 17 tests, 39 assertions passing (12 unit + 5 integration)
  • Refactored classes at 100% coverage (ConfigProvider, RegisterWidgetEvent, DashboardMiddleware, AclWidgetFilterIterator)
  • mago format/lint/analyze/guard clean; analysis baseline reduced from 8 entries to 1 stub-limitation false positive

Follow-up (separate PR in webware-acl)

  • webware-acl swaps its suggest entry for require: webware/webware-admin + admin VCS repository (it ships the management UI, so it needs admin)
  • Hosts alias Laminas\Permissions\Acl\AclInterface::class => Webware\Acl\Acl::class

Type-hint Laminas\Permissions\Acl\AclInterface as the ACL seam in the dashboard middleware, widget filter iterator, and their container factory so the host application can alias any ACL implementation. Widget filtering remains in webware-admin and fails closed when no role-aware user is present.

- Move webware/webware-acl from require to suggest and drop its VCS repository
- Remove the AclInterface service definition from ConfigProvider
- Rewrite AclWidgetFilterIterator tests to use laminas' GenericRole; add fail-closed coverage
- Add DashboardMiddleware unit and integration tests (real laminas Acl + mezzio-authentication-session, no database)
- Add mezzio/mezzio-authentication-session as a dev dependency
- Update dashboard widget documentation and add the refactor plan

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@codecov

codecov Bot commented Aug 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

tyrsson and others added 3 commits August 23, 2026 12:41
Restore getAclConfig() in webware-admin under the mezzio-authorization-acl config key, matching the structure required by mezzio's laminas-permissions-acl integration package: roles User and Administrator (child of User), resource webware.admin.dashboard.read (the admin dashboard route name), allow Administrator. webware-acl is database driven and never consumes this config; the defaults exist for hosts using laminas-permissions-acl directly.

- Resource is the route name (privileges encoded in the trailing segment, e.g. .read) per the webware/mezzio convention
- ConfigProviderIntegrationTest asserts the shipped defaults and the absence of an AclInterface service definition
- Annotate the user attribute retrieval (mixed-assignment) and tighten the iterator docblock
- Regenerate analysis-baseline.toml: 8 entries (6 stale Webware\UserManager refs) reduced to 1 stub-limitation false positive
- Update dashboard widget docs and refactor plan accordingly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace generic array<string, mixed> returns with named array-shape aliases (AclConfig, DefaultConfig, Dependencies, RouteProviders, Templates, ViewHelpers, ProviderConfig) declared in the class docblock, mirroring the pattern used in webware-message-bus. Mark the class @internal.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mirror the webware-message-bus ConfigProviderTest pattern: TestAssets\ExpectedConfig holds the expected config values, and ConfigProviderIntegrationTest asserts every getter plus __invoke() with exact assertSame comparisons plus an invoke-twice determinism check.

This raises the covered MSI from 62% to 100% (all 54 mutants killed; the 20 escaped ArrayItemRemoval and related array-shape mutants are now detected).

- Normalize the templates path to dirname(__DIR__) . '/templates/admin' so the emitted config is identical to the expected asset
- Update plan TEST-004 to document the exact-shape assertion pattern

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move WidgetInterface and AclWidgetFilterIterator from the Widget\ sub-namespace to the Webware\Admin package root. Add a WidgetContainer (IteratorAggregate) whose typed constructor and addWidget() are the only mutation paths, so the filter is guaranteed WidgetInterface instances by construction and the instanceof guard is dropped. RegisterWidgetEvent now owns the container; DashboardMiddleware passes it directly to the filter. Update tests, docs, and the plan; regenerate the analysis baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@tyrsson
tyrsson merged commit 468e8b4 into 0.1.x Aug 24, 2026
11 checks passed
@tyrsson
tyrsson deleted the refactor-acl-decoupling branch August 24, 2026 02:59
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.

Decouple admin from webware-acl: abstract ACL usage behind a neutral contract

1 participant