config = [ 'host' => LDAP_HOST, 'port' => LDAP_PORT, 'base_dn' => LDAP_BASE_DN, 'user_filter' => LDAP_USER_FILTER, 'bind_dn' => LDAP_BIND_DN, 'bind_password' => LDAP_BIND_PASSWORD, 'email_attribute' => LDAP_EMAIL_ATTRIBUTE, 'name_attribute' => LDAP_NAME_ATTRIBUTE ]; } public function authenticate($username, $password) { try { // Connect to LDAP server $this->connect(); // First bind with service account if configured if (!empty($this->config['bind_dn'])) { if (!ldap_bind($this->connection, $this->config['bind_dn'], $this->config['bind_password'])) { throw new Exception('LDAP bind failed with service account'); } } // Search for user $filter = str_replace('{username}', ldap_escape($username, '', LDAP_ESCAPE_FILTER), $this->config['user_filter']); $search = ldap_search($this->connection, $this->config['base_dn'], $filter, [$this->config['email_attribute'], $this->config['name_attribute'], 'dn']); if (!$search) { throw new Exception('LDAP search failed'); } $entries = ldap_get_entries($this->connection, $search); if ($entries['count'] === 0) { return false; } $user_dn = $entries[0]['dn']; // Try to bind with user credentials if (@ldap_bind($this->connection, $user_dn, $password)) { return true; } return false; } catch (Exception $e) { error_log('LDAP Authentication Error: ' . $e->getMessage()); return false; } finally { $this->disconnect(); } } public function getUserInfo($username) { try { $this->connect(); // Bind with service account if configured if (!empty($this->config['bind_dn'])) { if (!ldap_bind($this->connection, $this->config['bind_dn'], $this->config['bind_password'])) { throw new Exception('LDAP bind failed with service account'); } } // Search for user $filter = str_replace('{username}', ldap_escape($username, '', LDAP_ESCAPE_FILTER), $this->config['user_filter']); $search = ldap_search($this->connection, $this->config['base_dn'], $filter, [ $this->config['email_attribute'], $this->config['name_attribute'], 'dn', 'cn', 'givenName', 'sn', 'mail', 'uid' ]); if (!$search) { throw new Exception('LDAP search failed'); } $entries = ldap_get_entries($this->connection, $search); if ($entries['count'] === 0) { return null; } $user_entry = $entries[0]; $userInfo = [ 'ldap_dn' => $user_entry['dn'], 'username' => $username, 'email' => $this->getLdapAttribute($user_entry, $this->config['email_attribute']), 'name' => $this->getLdapAttribute($user_entry, $this->config['name_attribute']), 'first_name' => $this->getLdapAttribute($user_entry, 'givenName'), 'last_name' => $this->getLdapAttribute($user_entry, 'sn'), 'full_name' => $this->getLdapAttribute($user_entry, 'cn'), 'uid' => $this->getLdapAttribute($user_entry, 'uid') ]; return $userInfo; } catch (Exception $e) { error_log('LDAP User Info Error: ' . $e->getMessage()); return null; } finally { $this->disconnect(); } } public function testConnection() { try { $this->connect(); // Test bind if (!empty($this->config['bind_dn'])) { $bind_result = ldap_bind($this->connection, $this->config['bind_dn'], $this->config['bind_password']); } else { $bind_result = ldap_bind($this->connection); } if (!$bind_result) { throw new Exception('LDAP bind failed'); } // Test search $search = ldap_search($this->connection, $this->config['base_dn'], '(objectClass=*)', ['dn']); if (!$search) { throw new Exception('LDAP search failed'); } return true; } catch (Exception $e) { error_log('LDAP Connection Test Error: ' . $e->getMessage()); return false; } finally { $this->disconnect(); } } private function connect() { $connection_string = $this->config['host'] . ':' . $this->config['port']; // Suppress LDAP warnings for cleaner error handling $this->connection = @ldap_connect($connection_string); if (!$this->connection) { error_log('LDAP Connection Error: Failed to connect to LDAP server'); throw new Exception('Failed to connect to LDAP server'); } // Set LDAP options ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, 3); ldap_set_option($this->connection, LDAP_OPT_REFERRALS, 0); // Enable TLS if using port 636 if ($this->config['port'] == 636) { if (!ldap_start_tls($this->connection)) { throw new Exception('Failed to start TLS'); } } } private function disconnect() { if ($this->connection) { ldap_close($this->connection); $this->connection = null; } } private function getLdapAttribute($entry, $attribute) { if (isset($entry[$attribute])) { if ($entry[$attribute]['count'] > 1) { return $entry[$attribute][0]; // Return first value for multi-valued attributes } return $entry[$attribute][0]; } return null; } public function searchUsers($query = '', $limit = 50) { try { $this->connect(); // Bind with service account if configured if (!empty($this->config['bind_dn'])) { if (!ldap_bind($this->connection, $this->config['bind_dn'], $this->config['bind_password'])) { throw new Exception('LDAP bind failed with service account'); } } // Build search filter if ($query) { $escaped_query = ldap_escape($query, '', LDAP_ESCAPE_FILTER); $filter = "(&(|(uid=*$escaped_query*)(cn=*$escaped_query*)(mail=*$escaped_query*)(givenName=*$escaped_query*)(sn=*$escaped_query*))(!(objectClass=computer)))"; } else { $filter = "(&(objectClass=person)(!(objectClass=computer)))"; } $search = ldap_search($this->connection, $this->config['base_dn'], $filter, [ 'uid', 'cn', 'givenName', 'sn', 'mail', 'dn' ]); if (!$search) { throw new Exception('LDAP search failed'); } ldap_control_paged_result($this->connection, $limit); $entries = ldap_get_entries($this->connection, $search); $users = []; for ($i = 0; $i < $entries['count']; $i++) { $user_entry = $entries[$i]; $users[] = [ 'username' => $this->getLdapAttribute($user_entry, 'uid'), 'name' => $this->getLdapAttribute($user_entry, 'cn'), 'first_name' => $this->getLdapAttribute($user_entry, 'givenName'), 'last_name' => $this->getLdapAttribute($user_entry, 'sn'), 'email' => $this->getLdapAttribute($user_entry, 'mail'), 'ldap_dn' => $user_entry['dn'] ]; } return $users; } catch (Exception $e) { error_log('LDAP User Search Error: ' . $e->getMessage()); return []; } finally { $this->disconnect(); } } }