Commit 09cef093800ce953e71eeab94490096852b48a7e

Authored by Willen Borges Coelho
1 parent 4e566b9c
Exists in main

Não bloqueia buscas/bind quando STARTTLS falha no AD

STARTTLS virou best-effort: se o servidor não aceitar negociar na
porta 389 (connect error visto em produção), o resto do sistema
continua funcionando normalmente via bind simples -- só a troca de
senha (unicodePwd) exige canal criptografado e agora falha com uma
mensagem clara nesse caso específico, em vez de derrubar toda consulta
ao AD.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Showing 1 changed file with 21 additions and 9 deletions   Show diff stats
@@ -8,6 +8,7 @@ final class Ldap @@ -8,6 +8,7 @@ final class Ldap
8 { 8 {
9 /** @var resource|\LDAP\Connection */ 9 /** @var resource|\LDAP\Connection */
10 private $connect; 10 private $connect;
  11 + private bool $encrypted;
11 12
12 public function __construct() 13 public function __construct()
13 { 14 {
@@ -20,19 +21,22 @@ final class Ldap @@ -20,19 +21,22 @@ final class Ldap
20 ldap_set_option($this->connect, LDAP_OPT_REFERRALS, 0); 21 ldap_set_option($this->connect, LDAP_OPT_REFERRALS, 0);
21 ldap_set_option($this->connect, LDAP_OPT_NETWORK_TIMEOUT, 5); 22 ldap_set_option($this->connect, LDAP_OPT_NETWORK_TIMEOUT, 5);
22 23
  24 + $this->encrypted = str_starts_with(strtolower($server), 'ldaps://');
  25 +
23 // O AD recusa operações sensíveis (troca de senha via unicodePwd) em canal 26 // O AD recusa operações sensíveis (troca de senha via unicodePwd) em canal
24 - // não criptografado ("Server is unwilling to perform"). Se o servidor não  
25 - // for ldaps://, negocia STARTTLS antes de qualquer bind.  
26 - if (!str_starts_with(strtolower($server), 'ldaps://')) {  
27 - if (!@ldap_start_tls($this->connect)) {  
28 - throw new LdapException(  
29 - 'Falha ao negociar STARTTLS com o LDAP: ' . ldap_error($this->connect) .  
30 - '. Troque ldap.server para ldaps:// no config/local.php se o servidor não suportar STARTTLS.'  
31 - );  
32 - } 27 + // não criptografado. Tenta STARTTLS só como bônus: se o servidor não suportar
  28 + // (nem todo AD aceita na 389), segue sem criptografia -- buscas/bind continuam
  29 + // funcionando normalmente, só a troca de senha vai exigir isso explicitamente.
  30 + if (!$this->encrypted && @ldap_start_tls($this->connect)) {
  31 + $this->encrypted = true;
33 } 32 }
34 } 33 }
35 34
  35 + public function isEncrypted(): bool
  36 + {
  37 + return $this->encrypted;
  38 + }
  39 +
36 /** 40 /**
37 * Tenta autenticar o usuário com a própria senha dele (bind direto). 41 * Tenta autenticar o usuário com a própria senha dele (bind direto).
38 * Retorna true/false, nunca lança por credencial errada. 42 * Retorna true/false, nunca lança por credencial errada.
@@ -118,6 +122,14 @@ final class Ldap @@ -118,6 +122,14 @@ final class Ldap
118 122
119 public function modify(string $dn, array $attributes): void 123 public function modify(string $dn, array $attributes): void
120 { 124 {
  125 + if (array_key_exists('unicodePwd', $attributes) && !$this->encrypted) {
  126 + throw new LdapException(
  127 + 'Não foi possível negociar um canal seguro (STARTTLS) com o LDAP, e o Active Directory ' .
  128 + 'exige isso para trocar senha. Peça ao time de AD para liberar STARTTLS na porta 389, ' .
  129 + 'ou troque ldap.server para ldaps://... (porta 636) no config/local.php.'
  130 + );
  131 + }
  132 +
121 $ok = @ldap_mod_replace($this->connect, $dn, $attributes); 133 $ok = @ldap_mod_replace($this->connect, $dn, $attributes);
122 if (!$ok) { 134 if (!$ok) {
123 throw new LdapException('Erro ao modificar LDAP: ' . ldap_error($this->connect)); 135 throw new LdapException('Erro ao modificar LDAP: ' . ldap_error($this->connect));