Segurança
Autenticação & Autorização
O Auth System é responsável por verificar identidade (autenticação), permissões (autorização), e manter o estado do utilizador — incluindo suporte a impersonation para debugging.
Conceitos Chave
| Conceito | Pergunta | Descrição |
|---|---|---|
| Autenticação | "Quem é você?" | Verificar identidade do utilizador |
| Autorização | "O que pode fazer?" | Verificar permissões para ações |
| Permissão | — | Direito específico (ex: invoices:create) |
| Policy | — | Classe que define regras de autorização |
| Impersonation | — | Admin atuar como outro utilizador |
Quick Start
// 1. OBTER o AuthManager
$auth = app('auth');
// 2. VERIFICAR se utilizador está logado
if ($auth->check()) {
$user = $auth->user();
echo "Olá, " . $user->getUsername();
}
// 3. VERIFICAR uma permissão
if ($auth->can('invoices:create')) {
// Utilizador pode criar faturas
}
// 4. VERIFICAR múltiplas permissões
if ($auth->canAny(['sales:view', 'sales:edit'])) {
// Tem pelo menos uma das permissões
}
// 5. OBTER ID do utilizador
$userId = $auth->id();
Padrão de Permissões
O formato segue módulo:ação:
entidades:clientesstocks:artigosvendas:facturasinvoices:createinvoices:admincrm:editAuthManager
O AuthManager é o ponto central para toda a lógica de autenticação:
Verificação de Estado
$auth = app('auth');
// Está autenticado?
$auth->check(): bool
// Obter utilizador atual
$auth->user(): ?User
// Obter ID do utilizador
$auth->id(): ?int
// Está ativo?
$auth->isActive(): bool
Verificação de Permissões
// Verifica UMA permissão
$auth->can('stocks:artigos'): bool
// Verifica se tem ALGUMA
$auth->canAny(['view', 'edit']): bool
// Verifica se tem TODAS
$auth->canAll(['view', 'edit']): bool
Gestão de Utilizador
// Atualiza utilizador (sync legacy)
$auth->setUser(User $user): void
// Dados como array
$auth->getUserData(): array
Impersonation
// Em modo impersonation?
$auth->isImpersonating(): bool
// É admin developer?
$auth->isDeveloperAdmin(): bool
// ID do admin original
$auth->impersonatorId(): ?int
Policies (Autorização)
Policies encapsulam regras de autorização específicas para um recurso. Em vez de espalhar if statements pelo código, centralize a lógica:
use Og\Modules\Common\Auth\Authorization;
class InvoicePolicy extends Authorization
{
// Pode visualizar a fatura?
public function view($invoice): bool
{
return $this->user->checkPriv('invoices:view');
}
// Pode editar a fatura?
public function edit($invoice): bool
{
// Criador OU admin
return $invoice->created_by === $this->user->getId()
|| $this->user->checkPriv('invoices:admin');
}
// Pode eliminar a fatura?
public function delete($invoice): bool
{
return $this->user->checkPriv('invoices:admin');
}
}
Usando uma Policy
$policy = new InvoicePolicy($user, $app);
// Verificar manualmente
if ($policy->edit($invoice)) {
// Pode editar
}
// Ou usar authorize() — lança exceção se falhar
try {
$policy->authorize('edit', $invoice);
} catch (AuthorizationException $e) {
return response()->error($e->getMessage(), 403);
}
Access Response
A classe Response encapsula decisões de acesso com mensagens e códigos HTTP personalizados:
Permitir
Response::allow();
Response::allow('OK', 'ACCESS_OK');
Negar
Response::deny('Sem permissão');
Response::denyWithStatus(403, 'Proibido');
Response::denyAsNotFound('Não encontrado');
class DocumentPolicy extends Authorization
{
public function access($document): Response
{
if (!$this->user->checkPriv('documents:view')) {
return Response::deny('Sem permissão');
}
if ($document->is_confidential && !$this->user->checkPriv('documents:confidential')) {
return Response::denyWithStatus(403, 'Documento confidencial');
}
if ($document->status === 'deleted') {
return Response::denyAsNotFound('Documento não encontrado');
}
return Response::allow();
}
}
Impersonation
Permite que um administrador "atue como" outro utilizador para debugging ou suporte:
Audit Log com Impersonation
class AuditService
{
public function log(string $action, $resource): void
{
$auth = app('auth');
$data = [
'action' => $action,
'user_id' => $auth->id(),
];
// Se em impersonation, registar admin original
if ($auth->isImpersonating()) {
$data['impersonator_id'] = $auth->impersonatorId();
$data['is_impersonated'] = true;
}
AuditLog::create($data);
}
}
Exemplos Práticos
🎯 Proteger uma Action
class CreateInvoiceAction
{
public function execute(array $data): Invoice
{
$auth = app('auth');
if (!$auth->check()) {
throw new AuthorizationException('Não autenticado');
}
if (!$auth->can('invoices:create')) {
throw new AuthorizationException('Sem permissão');
}
return Invoice::create([
...$data,
'created_by' => $auth->id(),
]);
}
}
🎯 Middleware de Autorização
class RequirePermissionMiddleware
{
public function handle(Request $request, string $permission): Response
{
$auth = app('auth');
if (!$auth->check()) {
return redirect('/login');
}
if (!$auth->can($permission)) {
return response()->error('Acesso negado', 403);
}
return $next($request);
}
}
// Uso na rota:
Route::get('/invoices', [InvoiceController::class, 'index'])
->middleware('permission:invoices:view');
Integração com Legacy
O AuthManager mantém sincronização bidirecional entre o sistema moderno e as variáveis globais legacy ($u).
// Código legacy usa $u global
global $u;
if ($u->checkPriv('stocks:artigos')) {
// ...
}
// Código moderno usa AuthManager
$auth = app('auth');
if ($auth->can('stocks:artigos')) {
// ...
}
// ✅ Ambos funcionam e estão sincronizados!
Boas Práticas
✅ Verificar Cedo
public function execute(): void
{
// BOM — verificar no início
if (!app('auth')->can('perm')) {
throw new Exception();
}
// ... resto do código
}
✅ Usar Policies
// BOM — lógica centralizada
$policy->authorize('edit', $doc);
// MAU — lógica espalhada
if ($user->id === $doc->owner
|| $user->checkPriv('admin')) {
}
✅ Logar Acessos Negados
if (!$auth->can($perm)) {
Funcs::log('security',
"Negado: user={$auth->id()}"
);
throw new Exception();
}
❌ Confiar no Cliente
// MAU — confiar no request
$inv->user_id = $request->input('user_id');
// BOM — usar ID autenticado
$inv->user_id = app('auth')->id();