X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/919660678bec2b94eaa84ac60d0313f5ef07dfb7..refs/pull/1756/head:/app/Auth/Access/LdapService.php diff --git a/app/Auth/Access/LdapService.php b/app/Auth/Access/LdapService.php index 4fbf29365..c7415e1f7 100644 --- a/app/Auth/Access/LdapService.php +++ b/app/Auth/Access/LdapService.php @@ -1,11 +1,10 @@ config['email_attribute']; - $user = $this->getUserWithAttributes($userName, ['cn', 'uid', 'dn', $emailAttr]); + $displayNameAttr = $this->config['display_name_attribute']; + + $user = $this->getUserWithAttributes($userName, ['cn', 'uid', 'dn', $emailAttr, $displayNameAttr]); if ($user === null) { return null; } + $userCn = $this->getUserResponseProperty($user, 'cn', null); return [ - 'uid' => (isset($user['uid'])) ? $user['uid'][0] : $user['dn'], - 'name' => $user['cn'][0], + 'uid' => $this->getUserResponseProperty($user, 'uid', $user['dn']), + 'name' => $this->getUserResponseProperty($user, $displayNameAttr, $userCn), 'dn' => $user['dn'], - 'email' => (isset($user[$emailAttr])) ? (is_array($user[$emailAttr]) ? $user[$emailAttr][0] : $user[$emailAttr]) : null + 'email' => $this->getUserResponseProperty($user, $emailAttr, null), ]; } + /** + * Get a property from an LDAP user response fetch. + * Handles properties potentially being part of an array. + * @param array $userDetails + * @param string $propertyKey + * @param $defaultValue + * @return mixed + */ + protected function getUserResponseProperty(array $userDetails, string $propertyKey, $defaultValue) + { + if (isset($userDetails[$propertyKey])) { + return (is_array($userDetails[$propertyKey]) ? $userDetails[$propertyKey][0] : $userDetails[$propertyKey]); + } + + return $defaultValue; + } + /** * @param Authenticatable $user - * @param string $username - * @param string $password + * @param string $username + * @param string $password * @return bool * @throws LdapException */ @@ -108,6 +127,7 @@ class LdapService if ($ldapUser === null) { return false; } + if ($ldapUser['uid'] !== $user->external_auth_id) { return false; } @@ -162,15 +182,14 @@ class LdapService throw new LdapException(trans('errors.ldap_extension_not_installed')); } - // Get port from server string and protocol if specified. - $ldapServer = explode(':', $this->config['server']); - $hasProtocol = preg_match('/^ldaps{0,1}\:\/\//', $this->config['server']) === 1; - if (!$hasProtocol) { - array_unshift($ldapServer, ''); + // Check if TLS_INSECURE is set. The handle is set to NULL due to the nature of + // the LDAP_OPT_X_TLS_REQUIRE_CERT option. It can only be set globally and not per handle. + if ($this->config['tls_insecure']) { + $this->ldap->setOption(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER); } - $hostName = $ldapServer[0] . ($hasProtocol?':':'') . $ldapServer[1]; - $defaultPort = $ldapServer[0] === 'ldaps' ? 636 : 389; - $ldapConnection = $this->ldap->connect($hostName, count($ldapServer) > 2 ? intval($ldapServer[2]) : $defaultPort); + + $serverDetails = $this->parseServerString($this->config['server']); + $ldapConnection = $this->ldap->connect($serverDetails['host'], $serverDetails['port']); if ($ldapConnection === false) { throw new LdapException(trans('errors.ldap_cannot_connect')); @@ -185,6 +204,27 @@ class LdapService return $this->ldapConnection; } + /** + * 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' + * @param $serverString + * @return array + */ + protected function parseServerString($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; + return ['host' => $hostName, 'port' => $ldapPort]; + } + /** * Build a filter string by injecting common variables. * @param string $filterString @@ -196,7 +236,7 @@ class LdapService $newAttrs = []; foreach ($attrs as $key => $attrText) { $newKey = '${' . $key . '}'; - $newAttrs[$newKey] = $attrText; + $newAttrs[$newKey] = $this->ldap->escape($attrText); } return strtr($filterString, $newAttrs); } @@ -266,7 +306,8 @@ class LdapService $baseDn = $this->config['base_dn']; $groupsAttr = strtolower($this->config['group_attribute']); - $groups = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, 'CN='.$groupName, [$groupsAttr]); + $groupFilter = 'CN=' . $this->ldap->escape($groupName); + $groups = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $groupFilter, [$groupsAttr]); if ($groups['count'] === 0) { return []; } @@ -278,23 +319,26 @@ class LdapService /** * Filter out LDAP CN and DN language in a ldap search return * Gets the base CN (common name) of the string - * @param string $ldapSearchReturn + * @param array $userGroupSearchResponse * @return array */ - protected function groupFilter($ldapSearchReturn) + protected function groupFilter(array $userGroupSearchResponse) { $groupsAttr = strtolower($this->config['group_attribute']); $ldapGroups = []; $count = 0; - if (isset($ldapSearchReturn[$groupsAttr]['count'])) { - $count = (int) $ldapSearchReturn[$groupsAttr]['count']; + + if (isset($userGroupSearchResponse[$groupsAttr]['count'])) { + $count = (int)$userGroupSearchResponse[$groupsAttr]['count']; } - for ($i=0; $i<$count; $i++) { - $dnComponents = ldap_explode_dn($ldapSearchReturn[$groupsAttr][$i], 1); + + for ($i = 0; $i < $count; $i++) { + $dnComponents = $this->ldap->explodeDn($userGroupSearchResponse[$groupsAttr][$i], 1); if (!in_array($dnComponents[0], $ldapGroups)) { $ldapGroups[] = $dnComponents[0]; } } + return $ldapGroups; }