Gestão de Documentos (GED)

Permissões

ACL por documento e por pasta com herança — GedPermissionService, níveis view/download/edit/manage.

Permissões

O GED tem dois níveis de ACL — por documento e por pasta — com herança: uma permissão concedida numa pasta vale para tudo dentro dela (documentos e subpastas), a não ser que exista uma permissão mais específica no próprio documento. A resolução fica em App\Service\Ged\GedPermissionService.

Isto é diferente do row-scope de tenant e do escopo do agente de IA — é ACL de aplicação, dentro do módulo GED, não um mecanismo de isolamento multi-tenant nem do MCP.

Níveis

private static array $LEVEL_ORDER = ['view' => 1, 'download' => 2, 'edit' => 3, 'manage' => 4];

Os níveis são ordenados — manage inclui tudo que edit permite, que inclui download, que inclui view. Ao calcular a permissão efetiva de um usuário, o serviço sempre fica com o maior nível encontrado entre as fontes aplicáveis (nunca soma, nunca restringe).

Como a permissão efetiva é calculada

GedPermissionService::getEffectivePermission(int $userId, int $documentId): ?string
GedPermissionService::hasAccess(int $userId, int $documentId, string $requiredLevel): bool

Ordem de resolução para um documento:

  1. Dono do documento (document->created_by === $userId) → manage direto, sem consultar mais nada.
  2. Administrador (session('login') === 'admin') → manage direto.
  3. Herdada da árvore de pastas — sobe de folder_id em folder_id (via parent_id) até achar a primeira pasta com uma FolderPermission para o usuário ou um dos grupos dele. Para na primeira encontrada (não acumula múltiplos níveis de pastas diferentes).
  4. Direta no documento — DocumentPermission específica daquele documento.
  5. Resultado final = o maior nível entre a herdada (3) e a direta (4). null se nenhuma das duas existir (sem acesso).
if (GedPermissionService::hasAccess($userId, $documentId, 'edit')) {
    // usuário pode editar (edit ou manage)
}

Para checar acesso a uma pasta diretamente (sem documento), use getFolderAccess() — mesma lógica de subida na árvore, sem o passo de permissão direta de documento (pastas não têm "dono" automático como documentos).

Conceder e revogar

use App\Service\Ged\GedPermissionService;

// Documento
GedPermissionService::grantDocumentAccess($documentId, 'user', $userId, 'edit', $grantedBy);
GedPermissionService::grantDocumentAccess($documentId, 'group', $groupId, 'view', $grantedBy);
GedPermissionService::revokeDocumentAccess($documentId, 'user', $userId);

// Pasta (herdada por tudo dentro)
GedPermissionService::grantFolderAccess($folderId, 'group', $groupId, 'download', $grantedBy);
GedPermissionService::revokeFolderAccess($folderId, 'group', $groupId);

entity_type é sempre 'user' ou 'group'; entity_id é o id do usuário ou do grupo. grantDocumentAccess/grantFolderAccess fazem upsert (atualizam o nível se já existir uma permissão para a mesma combinação documento/pasta + entidade) e nunca duplicam linha.

Cache de permissão por sessão

A resolução é razoavelmente cara (sobe a árvore de pastas a cada chamada), e por isso é cacheada em session('ged_perm_cache') — não só durante o request, mas persistente entre requests do mesmo usuário logado, até ser invalidada. Sempre que você conceder/revogar uma permissão pelo seu próprio código (fora dos métodos grant*/revoke* acima, que já limpam o cache sozinhos), chame:

GedPermissionService::clearCache();

Caso contrário o usuário pode continuar vendo o nível antigo até a sessão expirar.

Limite real do mecanismo: clearCache() é session()->forget('ged_perm_cache') — limpa só a sessão de quem está executando o request. Um admin que revoga o acesso de outro usuário limpa a própria sessão, não a dele: o usuário afetado pode continuar com o nível antigo em cache até sua sessão expirar ou até ele mesmo disparar uma ação que chame clearCache(). Se o seu caso exige revogação imediata, não confie nesse cache — cheque a permissão sem ele no ponto crítico (ou invalide a sessão do usuário).

A aba "Compartilhamento" do DocumentDetail

A tela admin não chama grantDocumentAccess() diretamente — ela usa <mad-db-blocks> apontando para o model DocumentPermission, com hooks que populam a pivot a partir do form (escolha de usuário ou grupo via mad-dbcombo-field, nível via select) e chamam clearCache() depois:

public function onAddPermission(DocumentPermission $pivot)
{
    $data = (array) $this->form->getData();

    $entityType = in_array($data['entity_type'] ?? '', ['user','group'], true) ? $data['entity_type'] : 'user';
    $level      = in_array($data['level'] ?? '', ['view','download','edit','manage'], true) ? $data['level'] : 'view';
    $entityId   = $entityType === 'user'
        ? (int) ($data['entity_id_user']  ?? 0)
        : (int) ($data['entity_id_group'] ?? 0);

    if (!$entityId) {
        return false; // cancela o add
    }

    $pivot->entity_type = $entityType;
    $pivot->entity_id   = $entityId;
    $pivot->level       = $level;
    $pivot->granted_by  = (int) session('userid');

    GedPermissionService::clearCache();
}

A mesma aba também lista as permissões herdadas das pastas pai (DocumentDetail::getInheritedPermissions(), read-only — para revogar uma herdada você edita a permissão na pasta, não no documento) e permite trocar o nível de uma permissão direta inline, via um <select> que chama onPermissionLevelChange(int $permId, string $level).

Ver também