Ldap.php
4.16 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
<?php
final class LdapException extends RuntimeException
{
}
final class Ldap
{
/** @var resource|\LDAP\Connection */
private $connect;
public function __construct()
{
$server = Config::get('ldap.server');
$this->connect = ldap_connect($server);
if (!$this->connect) {
throw new LdapException('Não foi possível conectar ao servidor LDAP.');
}
ldap_set_option($this->connect, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($this->connect, LDAP_OPT_REFERRALS, 0);
ldap_set_option($this->connect, LDAP_OPT_NETWORK_TIMEOUT, 5);
}
/**
* Tenta autenticar o usuário com a própria senha dele (bind direto).
* Retorna true/false, nunca lança por credencial errada.
*/
public function tryUserBind(string $login, string $password): bool
{
if ($login === '' || $password === '') {
return false;
}
$userPrincipal = $login . Config::get('ldap.domain_suffix', '@example.org');
set_error_handler(static function () {
return true;
}); // silencia warning nativo do ldap_bind em credencial inválida, sem usar @
try {
return (bool) ldap_bind($this->connect, $userPrincipal, $password);
} finally {
restore_error_handler();
}
}
/**
* Faz bind com a conta de serviço (somente leitura), usada para todas as buscas.
*/
public function bindService(): void
{
$user = Config::get('ldap.service_user');
$pass = Config::get('ldap.service_pass');
$userPrincipal = $user . Config::get('ldap.domain_suffix', '@example.org');
$ok = @ldap_bind($this->connect, $userPrincipal, $pass);
if (!$ok) {
throw new LdapException('Falha ao autenticar a conta de serviço no LDAP.');
}
}
public function search(string $filter): array
{
$baseDn = Config::get('ldap.base_dn');
$result = @ldap_search($this->connect, $baseDn, $filter);
if ($result === false) {
throw new LdapException('Erro na busca LDAP: ' . ldap_error($this->connect));
}
$entries = @ldap_get_entries($this->connect, $result);
if ($entries === false) {
throw new LdapException('Erro ao ler entradas LDAP: ' . ldap_error($this->connect));
}
return $entries;
}
public function findUserByCn(string $cn): ?array
{
$filter = '(cn=' . ldap_escape($cn, '', LDAP_ESCAPE_FILTER) . ')';
$entries = $this->search($filter);
return $entries['count'] > 0 ? $entries[0] : null;
}
public function findUserByLogin(string $login): ?array
{
$filter = '(sAMAccountName=' . ldap_escape($login, '', LDAP_ESCAPE_FILTER) . ')';
$entries = $this->search($filter);
return $entries['count'] > 0 ? $entries[0] : null;
}
public function groupMembers(string $groupDnOrFilter): array
{
// aceita tanto um filtro pronto ("cn=grpX") quanto um CN cru
$filter = str_contains($groupDnOrFilter, '=')
? $groupDnOrFilter
: '(cn=' . ldap_escape($groupDnOrFilter, '', LDAP_ESCAPE_FILTER) . ')';
$entries = $this->search($filter);
if ($entries['count'] === 0) {
return [];
}
$members = [];
$count = (int) ($entries[0]['member']['count'] ?? 0);
for ($i = 0; $i < $count; $i++) {
$dn = $entries[0]['member'][$i];
$parts = explode(',', $dn);
$cn = str_replace('CN=', '', $parts[0]);
$members[] = $cn;
}
return $members;
}
public function modify(string $dn, array $attributes): void
{
$ok = @ldap_mod_replace($this->connect, $dn, $attributes);
if (!$ok) {
throw new LdapException('Erro ao modificar LDAP: ' . ldap_error($this->connect));
}
}
public function dnOf(array $entry): ?string
{
return $entry['dn'] ?? null;
}
public static function encodePassword(string $plain): string
{
// AD exige a senha entre aspas e em UTF-16LE para unicodePwd.
return iconv('UTF-8', 'UTF-16LE', '"' . $plain . '"');
}
}