Skip to content

feat(panel-admin): RBAC com roles e policies no painel admin - #514

Draft
danielhe4rt wants to merge 3 commits into
4.xfrom
feature/rbac-admin-panel
Draft

feat(panel-admin): RBAC com roles e policies no painel admin#514
danielhe4rt wants to merge 3 commits into
4.xfrom
feature/rbac-admin-panel

Conversation

@danielhe4rt

Copy link
Copy Markdown
Contributor

Contexto

O acesso ao painel admin era decidido por uma lista de usernames em config/he4rt.php. Quem estava na lista via tudo; quem não estava não via nada. Não havia meio-termo — nenhuma forma de dar a um moderador acesso só à moderação, ou a um responsável por marketing acesso só ao encurtador.

Este PR troca o allowlist por autorização de verdade: spatie/laravel-permission para o schema de roles/permissions e filament-shield para gerar as permissions a partir dos recursos do painel. Acessar o painel passa a significar possuir um role, e cada um dos 15 recursos é governado por uma policy.

Ainda é draft — o fluxo está funcional, mas faltam as pontas descritas em Pendências antes de mergear.

Alterações

  • identity — publica a migration do spatie/laravel-permission dentro do módulo, com dois ajustes que o stub publicado exige aqui: model_morph_key vira UUID (o User usa UUID como PK, então atribuir role falharia no insert, não no migrate) e timestamps() vira timestampsTz(). Adiciona HasRoles ao User.
  • panel-admin — 15 policies geradas pelo Shield, uma por recurso. Elas vivem no módulo de apresentação, não ao lado de cada model de domínio, porque autorizar uma tela é uma preocupação de apresentação.
  • Registro via Gate::guessPolicyNamesUsing() — registrar dentro de Filament::serving() só resolvia as policies durante um request HTTP. Um componente Livewire exercitado diretamente rodava sem policy nenhuma, então um usuário com permissão de moderação abria uma página de marketing e recebia 200.
  • Três acomodações que o Shield precisou neste repositório: ele lê apenas o composer.json da raiz ao resolver PSR-4 (daí a declaração do namespace He4rt\PanelAdmin lá); ele rejeita o separador _ junto de snake case (daí o separador placeholder na config e o builder próprio); e ele só procura um diretório Policies irmão quando o model está sob Models.
  • isAdmin() removido. O gate do Telescope agora pede o role super_admin.
  • Testes — os que dependiam do allowlist constroem um role de verdade. Dois helpers em tests/Pest.php carregam isso: panelUserWith() para permissions específicas e panelAdminUser() para o super admin. Novos: PermissionTablesTest, PolicyCoverageTest e AreaIsolationTest (este último cobrindo tanto componente Livewire quanto rota HTTP).
  • Sob phpunit, desativa o lookup de proxy do Cloudflare: ele busca por HTTP e cacheia para sempre, então um cache frio virava 500 em todo teste roteado.

Plano de Testes

  • Executar make check
  • Executar make test
  • Verificar que um usuário sem role nenhum não acessa /admin
  • Verificar que um usuário com permission só de moderação recebe 403 numa página de marketing
  • Verificar que super_admin acessa todos os 15 recursos e o Telescope

Pendências antes de sair do draft

  • Bootstrap de acesso — não existe seeder nem command que crie o role super_admin e o atribua a alguém. O allowlist era o que cumpria esse papel; nada tomou o lugar dele, então após o deploy ninguém entra no painel.
  • Tela de Roles invisível — o painel monta a sidebar manualmente via NavigationBuilder, e defaultNavigation() não inclui os itens do RoleResource do Shield. A rota existe, o link não.
  • Sem UI para vincular usuário a role — não há UserResource no panel-admin. Dá para criar roles no Shield, mas o vínculo user↔role só via tinker. Candidato a PR separado.
  • Config mortaconfig/he4rt.php ainda carrega 'admins' => env('HE4RT_ADMINS_USERNAMES', ...), que já não é lido por nada.
  • Atualizar docs/admin/en/users/authentication.md e roles.md.

Issues Relacionadas

Publishes spatie/laravel-permission and moves its migration into the
identity module, where the ServiceProvider already loads migrations from.

Two fixes the published stub needs in this project:

- model_morph_key was unsignedBigInteger, which cannot hold the UUID
  primary key our User model uses. Assigning a role would have failed at
  insert time, not at migrate time.
- timestamps() becomes timestampsTz(), as every date column in this
  project must be timezone aware.

Adds the HasRoles trait to User so the schema can be exercised. The rest
of the authorization rework (dropping isAdmin, gating canAccessPanel)
lands in a later commit.
Replaces the username allowlist with real authorization. Panel access now
means holding a role, and each of the 15 resources is governed by a policy
generated from Shield.

Shield needed three accommodations for this repository:

- It reads only the root composer.json when resolving PSR-4, so the
  He4rt\PanelAdmin namespace is declared there for it to find.
- It rejects the '_' separator alongside snake case, so the config carries
  a placeholder separator and a custom builder joins the parts.
- It only looks for a sibling "Policies" directory when a model lives under
  "Models", which would scatter panel policies across the domain modules.
  The policies live in this module and are bound to their models here.

isAdmin() is gone, along with the he4rt.admins config it read. The
Telescope gate now asks for the super_admin role.

Tests that relied on the allowlist build a real role instead. Two helpers
carry that: panelUserWith() for specific permissions and panelAdminUser()
for the super admin.

Also disables the Cloudflare proxy lookup under phpunit: it fetches over
HTTP and caches forever, so a cold cache turned every routed test into a
500 once stray requests are banned.
Registering the policies inside Filament::serving() only bound them during
an HTTP request. A Livewire component exercised directly — which is how
most panel tests drive it — ran with no policy at all, so a user carrying
a moderation permission could open a marketing page and get a 200.

Gate::guessPolicyNamesUsing() resolves them in every context. The callback
returns the framework's own default alongside ours, and the Gate discards
whichever does not exist.

Adds the pair that would have caught this: the same wrong-area check over
both a Livewire component and an HTTP route.
@danielhe4rt danielhe4rt added type:feat New feature mod:panel-admin Admin Filament panel mod:identity Auth & user identity difficulty:hard 1-2 weeks labels Aug 23, 2026
@hefeus

hefeus commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

@danielhe4rt Poderia me passar como está o andamento dessa issue? Se ainda fará algo com ela, ou se é somente revisão mesmo? Pois com suas alterações e preciso esperar para finalizar o pr #487 que tinha coisas referente a essas politicas.

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

Labels

difficulty:hard 1-2 weeks mod:identity Auth & user identity mod:panel-admin Admin Filament panel type:feat New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants