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', '@cefetes.br'); 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', '@cefetes.br'); $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 . '"'); } }