From 4bacc45fb7daedb07830abaaf8399e17488f7cbf Mon Sep 17 00:00:00 2001 From: Khazhinov Vladislav Date: Wed, 21 Sep 2022 15:24:57 +0300 Subject: [PATCH] multiple ldap server --- .env.example.complete | 3 +- app/Auth/Access/LdapService.php | 102 +++++++++++++++++++++++++------- 2 files changed, 81 insertions(+), 24 deletions(-) diff --git a/.env.example.complete b/.env.example.complete index 03e52d6bb..09d863d93 100644 --- a/.env.example.complete +++ b/.env.example.complete @@ -207,6 +207,7 @@ TWITTER_AUTO_CONFIRM_EMAIL=false # LDAP authentication configuration # Refer to https://www.bookstackapp.com/docs/admin/ldap-auth/ +# If you need to use multiple addresses, separate them with a semicolon. Ex: dc1.domain.local:389;dc2.domain.local:389 LDAP_SERVER=false LDAP_BASE_DN=false LDAP_DN=false @@ -368,4 +369,4 @@ LOG_FAILED_LOGIN_CHANNEL=errorlog_plain_webserver # IP address '146.191.42.4' would result in '146.191.x.x' being logged. # For the IPv6 address '2001:db8:85a3:8d3:1319:8a2e:370:7348' this would result as: # '2001:db8:85a3:8d3:x:x:x:x' -IP_ADDRESS_PRECISION=4 \ No newline at end of file +IP_ADDRESS_PRECISION=4 diff --git a/app/Auth/Access/LdapService.php b/app/Auth/Access/LdapService.php index 359eeca2f..aa4c99772 100644 --- a/app/Auth/Access/LdapService.php +++ b/app/Auth/Access/LdapService.php @@ -217,22 +217,54 @@ class LdapService } $serverDetails = $this->parseServerString($this->config['server']); - $ldapConnection = $this->ldap->connect($serverDetails['host'], $serverDetails['port']); + if (array_key_exists('hosts', $serverDetails)) { + $fail_counter = 0; + foreach ($serverDetails['hosts'] as $serverDetailsItem) { + try { + $ldapConnection = $this->ldap->connect($serverDetailsItem['host'], $serverDetailsItem['port']); - if ($ldapConnection === false) { - throw new LdapException(trans('errors.ldap_cannot_connect')); - } + if ($ldapConnection === false) { + throw new LdapException(trans('errors.ldap_cannot_connect')); + } - // Set any required options - if ($this->config['version']) { - $this->ldap->setVersion($ldapConnection, $this->config['version']); - } + // Set any required options + if ($this->config['version']) { + $this->ldap->setVersion($ldapConnection, $this->config['version']); + } - // Start and verify TLS if it's enabled - if ($this->config['start_tls']) { - $started = $this->ldap->startTls($ldapConnection); - if (!$started) { - throw new LdapException('Could not start TLS connection'); + // Start and verify TLS if it's enabled + if ($this->config['start_tls']) { + $started = $this->ldap->startTls($ldapConnection); + if (!$started) { + throw new LdapException('Could not start TLS connection'); + } + } + } catch (\Throwable $exception) { + $fail_counter++; + } + } + + if ($fail_counter == count($serverDetails['hosts'])) { + throw new LdapException(trans('errors.ldap_cannot_connect')); + } + } else { + $ldapConnection = $this->ldap->connect($serverDetails['host'], $serverDetails['port']); + + if ($ldapConnection === false) { + throw new LdapException(trans('errors.ldap_cannot_connect')); + } + + // Set any required options + if ($this->config['version']) { + $this->ldap->setVersion($ldapConnection, $this->config['version']); + } + + // Start and verify TLS if it's enabled + if ($this->config['start_tls']) { + $started = $this->ldap->startTls($ldapConnection); + if (!$started) { + throw new LdapException('Could not start TLS connection'); + } } } @@ -244,21 +276,45 @@ class LdapService /** * Parse a LDAP server string and return the host and port for a connection. * Is flexible to formats such as 'ldap.example.com:8069' or 'ldaps://ldap.example.com'. + * If you need to use multiple addresses, separate them with a semicolon. + * Ex: dc1.domain.local:389;dc2.domain.local:389 */ protected function parseServerString(string $serverString): array { - $serverNameParts = explode(':', $serverString); + $explodedServerString = explode(';', $serverString); + if (count($explodedServerString) > 1) { + $result = ['hosts' => []]; - // If we have a protocol just return the full string since PHP will ignore a separate port. - if ($serverNameParts[0] === 'ldaps' || $serverNameParts[0] === 'ldap') { - return ['host' => $serverString, 'port' => 389]; + foreach ($explodedServerString as $serverString) { + $serverNameParts = explode(':', $serverString); + + // If we have a protocol just return the full string since PHP will ignore a separate port. + if ($serverNameParts[0] === 'ldaps' || $serverNameParts[0] === 'ldap') { + return ['host' => $serverString, 'port' => 389]; + } + + // Otherwise, extract the port out + $hostName = $serverNameParts[0]; + $ldapPort = (count($serverNameParts) > 1) ? intval($serverNameParts[1]) : 389; + + $result['hosts'][] = ['host' => $hostName, 'port' => $ldapPort]; + } + + return $result; + } else { + $serverNameParts = explode(':', $serverString); + + // If we have a protocol just return the full string since PHP will ignore a separate port. + if ($serverNameParts[0] === 'ldaps' || $serverNameParts[0] === 'ldap') { + return ['host' => $serverString, 'port' => 389]; + } + + // Otherwise, extract the port out + $hostName = $serverNameParts[0]; + $ldapPort = (count($serverNameParts) > 1) ? intval($serverNameParts[1]) : 389; + + return ['host' => $hostName, 'port' => $ldapPort]; } - - // Otherwise, extract the port out - $hostName = $serverNameParts[0]; - $ldapPort = (count($serverNameParts) > 1) ? intval($serverNameParts[1]) : 389; - - return ['host' => $hostName, 'port' => $ldapPort]; } /**