configureTls(); } $connection = ldap_connect("{$scheme}://{$host}:{$port}"); if ($connection === false) { throw new RuntimeException('LDAP connect failed.'); } ldap_set_option($connection, LDAP_OPT_PROTOCOL_VERSION, 3); ldap_set_option($connection, LDAP_OPT_REFERRALS, 0); ldap_set_option($connection, LDAP_OPT_NETWORK_TIMEOUT, config('ldap.timeout', 10)); return $connection; } /** * @return resource */ public function bind(bool $ssl = false) { $bindDn = config('ldap.bind_dn'); $bindPassword = config('ldap.bind_password'); if ($bindDn === '' || $bindPassword === '') { throw new RuntimeException('LDAP credentials are not configured.'); } $connection = $this->connect($ssl); if (! @ldap_bind($connection, $bindDn, $bindPassword)) { $error = ldap_error($connection); @ldap_unbind($connection); throw new RuntimeException('LDAP bind failed.' . ($error ? " ({$error})" : '')); } return $connection; } private function configureTls(): void { if (! config('ldap.tls_verify', false)) { putenv('LDAPTLS_REQCERT=never'); @ldap_set_option(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER); } } public function findUserDn($connection, string $username): string { $username = $this->validateUsername($username); $filter = '(sAMAccountName=' . ldap_escape($username, '', LDAP_ESCAPE_FILTER) . ')'; $result = @ldap_search($connection, config('ldap.base_dn'), $filter, ['dn']); if ($result === false) { throw new RuntimeException('LDAP user search failed.'); } $entries = ldap_get_entries($connection, $result); if (($entries['count'] ?? 0) < 1) { throw new InvalidArgumentException('User not found.'); } return $entries[0]['dn']; } public function findGroupDn($connection, string $groupKey): string { $groupKey = trim($groupKey); $escaped = ldap_escape($groupKey, '', LDAP_ESCAPE_FILTER); $filter = "(&(|(sAMAccountName={$escaped})(cn={$escaped}))(objectClass=group))"; $result = @ldap_search($connection, config('ldap.base_dn'), $filter, ['dn', 'cn', 'samaccountname']); if ($result === false) { throw new RuntimeException('LDAP group search failed.'); } $entries = ldap_get_entries($connection, $result); if (($entries['count'] ?? 0) < 1) { throw new InvalidArgumentException('Group not found.'); } return $entries[0]['dn']; } public function validateUsername(string $username): string { $username = trim($username); if (! preg_match('/^[a-zA-Z0-9._-]+$/', $username)) { throw new InvalidArgumentException('Invalid username.'); } return $username; } public function assertUserWritable(string $username): void { $username = mb_strtolower($this->validateUsername($username)); $protected = array_map('mb_strtolower', config('ldap.protected_users', self::PROTECTED_USERS)); if (in_array($username, $protected, true)) { throw new InvalidArgumentException('This account is protected and cannot be modified.'); } } public function assertGroupWritable(string $groupName): void { $groupName = mb_strtolower(trim($groupName)); $protected = array_map('mb_strtolower', config('ldap.protected_groups', self::PROTECTED_GROUPS)); if (in_array($groupName, $protected, true)) { throw new InvalidArgumentException('This group is protected and cannot be modified.'); } } public function domainFromBaseDn(?string $baseDn = null): string { $baseDn = $baseDn ?? config('ldap.base_dn'); preg_match_all('/DC=([^,]+)/i', $baseDn, $matches); return implode('.', $matches[1] ?? []); } public function attributeValue(array $entry, string $key): ?string { $key = strtolower($key); if (! isset($entry[$key]) || ! is_array($entry[$key])) { return null; } return $entry[$key][0] ?? null; } public function membersList(array $entry): array { if (! isset($entry['member']) || ! is_array($entry['member'])) { return []; } $members = []; for ($i = 0; $i < ($entry['member']['count'] ?? 0); $i++) { $dn = $entry['member'][$i] ?? ''; if (preg_match('/^CN=([^,]+)/i', $dn, $matches)) { $members[] = $matches[1]; } } sort($members); return $members; } public function memberUsernames($connection, array $memberDns): array { $usernames = []; foreach ($memberDns as $dn) { $result = @ldap_read($connection, $dn, '(objectClass=*)', ['samaccountname']); if ($result === false) { continue; } $entries = ldap_get_entries($connection, $result); $name = $this->attributeValue($entries[0] ?? [], 'samaccountname'); if ($name !== null) { $usernames[] = $name; } } sort($usernames); return $usernames; } }