Auth.php
4.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<?php
final class Auth
{
private const MAX_ATTEMPTS = 5;
private const WINDOW_MINUTES = 15;
public static function start(): void
{
if (session_status() === PHP_SESSION_NONE) {
session_name(Config::get('app.session_name', 'ad_session'));
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'secure' => (!empty($_SERVER['HTTPS'])),
'httponly' => true,
'samesite' => 'Lax',
]);
session_start();
}
}
public static function attempt(string $login, string $password): array
{
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
if (self::isRateLimited($login, $ip)) {
return ['ok' => false, 'error' => 'Muitas tentativas. Aguarde alguns minutos e tente novamente.'];
}
$ldap = new Ldap();
$bindOk = $ldap->tryUserBind($login, $password);
self::logAttempt($login, $ip, $bindOk);
if (!$bindOk) {
return ['ok' => false, 'error' => 'Usuário ou senha incorretos.'];
}
$admin = AdminRepository::findActiveByLogin($login);
if (!$admin) {
AuditLog::record($login, 'login_negado', null, 'usuário autenticado no AD mas sem permissão no sistema');
return ['ok' => false, 'error' => 'Seu usuário não tem permissão para acessar este sistema.'];
}
// Atualiza o nome em cache a partir do AD, sem depender da conta de serviço falhar.
try {
$ldap->bindService();
$entry = $ldap->findUserByLogin($login);
$nome = $entry['displayname'][0] ?? null;
if ($nome) {
AdminRepository::touchLoginCache($login, $nome);
$admin['nome'] = $nome;
}
} catch (Throwable $e) {
// Não bloqueia o login por causa da conta de serviço.
}
self::start();
session_regenerate_id(true);
$_SESSION['login'] = $login;
$_SESSION['nome'] = $admin['nome'] ?? $login;
$_SESSION['papel'] = $admin['papel'];
AuditLog::record($login, 'login', null, 'login efetuado');
return ['ok' => true];
}
public static function logout(): void
{
self::start();
$login = $_SESSION['login'] ?? 'desconhecido';
AuditLog::record($login, 'logout');
$_SESSION = [];
session_destroy();
}
public static function requireLogin(): void
{
self::start();
if (empty($_SESSION['login'])) {
header('Location: index.php?erro=sessao_expirada');
exit;
}
}
public static function requireAdmin(): void
{
self::requireLogin();
if (($_SESSION['papel'] ?? null) !== 'admin') {
http_response_code(403);
echo 'Acesso restrito a administradores.';
exit;
}
}
public static function currentLogin(): ?string
{
return $_SESSION['login'] ?? null;
}
public static function currentName(): ?string
{
return $_SESSION['nome'] ?? null;
}
public static function isAdmin(): bool
{
return ($_SESSION['papel'] ?? null) === 'admin';
}
private static function isRateLimited(string $login, string $ip): bool
{
$stmt = Database::pdo()->prepare(
'SELECT COUNT(*) FROM login_attempts
WHERE login = :login AND ip = :ip AND sucesso = 0
AND criado_em >= (NOW() - INTERVAL :minutes MINUTE)'
);
$stmt->bindValue('login', $login);
$stmt->bindValue('ip', $ip);
$stmt->bindValue('minutes', self::WINDOW_MINUTES, PDO::PARAM_INT);
$stmt->execute();
return (int) $stmt->fetchColumn() >= self::MAX_ATTEMPTS;
}
private static function logAttempt(string $login, string $ip, bool $success): void
{
$stmt = Database::pdo()->prepare(
'INSERT INTO login_attempts (login, ip, sucesso) VALUES (:login, :ip, :sucesso)'
);
$stmt->execute(['login' => $login, 'ip' => $ip, 'sucesso' => $success ? 1 : 0]);
}
}