X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/dabe79a438f22612e7d68c8d1de7817505b59b93..refs/pull/2902/head:/app/Auth/Access/LdapService.php diff --git a/app/Auth/Access/LdapService.php b/app/Auth/Access/LdapService.php index 554bc4b48..7bfdb5328 100644 --- a/app/Auth/Access/LdapService.php +++ b/app/Auth/Access/LdapService.php @@ -1,10 +1,13 @@ -ldap = $ldap; + $this->userAvatars = $userAvatars; $this->config = config('services.ldap'); - $this->userRepo = $userRepo; $this->enabled = config('auth.method') === 'ldap'; } /** * Check if groups should be synced. + * * @return bool */ public function shouldSyncGroups() @@ -41,6 +44,7 @@ class LdapService extends ExternalAuthService /** * Search for attributes for a specific user on the ldap. + * * @throws LdapException */ private function getUserWithAttributes(string $userName, array $attributes): ?array @@ -48,6 +52,13 @@ class LdapService extends ExternalAuthService $ldapConnection = $this->getConnection(); $this->bindSystemUser($ldapConnection); + // Clean attributes + foreach ($attributes as $index => $attribute) { + if (strpos($attribute, 'BIN;') === 0) { + $attributes[$index] = substr($attribute, strlen('BIN;')); + } + } + // Find user $userFilter = $this->buildFilter($this->config['user_filter'], ['user' => $userName]); $baseDn = $this->config['base_dn']; @@ -65,6 +76,7 @@ class LdapService extends ExternalAuthService /** * Get the details of a user from LDAP using the given username. * User found via configurable user filter. + * * @throws LdapException */ public function getUserDetails(string $userName): ?array @@ -72,54 +84,76 @@ class LdapService extends ExternalAuthService $idAttr = $this->config['id_attribute']; $emailAttr = $this->config['email_attribute']; $displayNameAttr = $this->config['display_name_attribute']; + $thumbnailAttr = $this->config['thumbnail_attribute']; - $user = $this->getUserWithAttributes($userName, ['cn', 'dn', $idAttr, $emailAttr, $displayNameAttr]); + $user = $this->getUserWithAttributes($userName, array_filter([ + 'cn', 'dn', $idAttr, $emailAttr, $displayNameAttr, $thumbnailAttr, + ])); - if ($user === null) { + if (is_null($user)) { return null; } $userCn = $this->getUserResponseProperty($user, 'cn', null); - return [ + $formatted = [ 'uid' => $this->getUserResponseProperty($user, $idAttr, $user['dn']), 'name' => $this->getUserResponseProperty($user, $displayNameAttr, $userCn), 'dn' => $user['dn'], 'email' => $this->getUserResponseProperty($user, $emailAttr, null), + 'avatar'=> $thumbnailAttr ? $this->getUserResponseProperty($user, $thumbnailAttr, null) : null, ]; + + if ($this->config['dump_user_details']) { + throw new JsonDebugException([ + 'details_from_ldap' => $user, + 'details_bookstack_parsed' => $formatted, + ]); + } + + return $formatted; } /** * Get a property from an LDAP user response fetch. * Handles properties potentially being part of an array. + * If the given key is prefixed with 'BIN;', that indicator will be stripped + * from the key and any fetched values will be converted from binary to hex. */ protected function getUserResponseProperty(array $userDetails, string $propertyKey, $defaultValue) { + $isBinary = strpos($propertyKey, 'BIN;') === 0; $propertyKey = strtolower($propertyKey); + $value = $defaultValue; + + if ($isBinary) { + $propertyKey = substr($propertyKey, strlen('BIN;')); + } + if (isset($userDetails[$propertyKey])) { - return (is_array($userDetails[$propertyKey]) ? $userDetails[$propertyKey][0] : $userDetails[$propertyKey]); + $value = (is_array($userDetails[$propertyKey]) ? $userDetails[$propertyKey][0] : $userDetails[$propertyKey]); + if ($isBinary) { + $value = bin2hex($value); + } } - return $defaultValue; + return $value; } /** * Check if the given credentials are valid for the given user. + * * @throws LdapException */ - public function validateUserCredentials(Authenticatable $user, string $username, string $password): bool + public function validateUserCredentials(?array $ldapUserDetails, string $password): bool { - $ldapUser = $this->getUserDetails($username); - if ($ldapUser === null) { - return false; - } - - if ($ldapUser['uid'] !== $user->external_auth_id) { + if (is_null($ldapUserDetails)) { return false; } $ldapConnection = $this->getConnection(); + try { - $ldapBind = $this->ldap->bind($ldapConnection, $ldapUser['dn'], $password); + $ldapBind = $this->ldap->bind($ldapConnection, $ldapUserDetails['dn'], $password); } catch (ErrorException $e) { $ldapBind = false; } @@ -130,7 +164,9 @@ class LdapService extends ExternalAuthService /** * Bind the system user to the LDAP connection using the given credentials * otherwise anonymous access is attempted. + * * @param $connection + * * @throws LdapException */ protected function bindSystemUser($connection) @@ -153,8 +189,10 @@ class LdapService extends ExternalAuthService /** * Get the connection to the LDAP server. * Creates a new connection if one does not exist. - * @return resource + * * @throws LdapException + * + * @return resource */ protected function getConnection() { @@ -167,8 +205,8 @@ class LdapService extends ExternalAuthService throw new LdapException(trans('errors.ldap_extension_not_installed')); } - // 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. + // Disable certificate verification. + // This option works globally and must be set before a connection is created. if ($this->config['tls_insecure']) { $this->ldap->setOption(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER); } @@ -185,7 +223,16 @@ class LdapService extends ExternalAuthService $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'); + } + } + $this->ldapConnection = $ldapConnection; + return $this->ldapConnection; } @@ -205,6 +252,7 @@ class LdapService extends ExternalAuthService // Otherwise, extract the port out $hostName = $serverNameParts[0]; $ldapPort = (count($serverNameParts) > 1) ? intval($serverNameParts[1]) : 389; + return ['host' => $hostName, 'port' => $ldapPort]; } @@ -218,11 +266,13 @@ class LdapService extends ExternalAuthService $newKey = '${' . $key . '}'; $newAttrs[$newKey] = $this->ldap->escape($attrText); } + return strtr($filterString, $newAttrs); } /** * Get the groups a user is a part of on ldap. + * * @throws LdapException */ public function getUserGroups(string $userName): array @@ -236,11 +286,13 @@ class LdapService extends ExternalAuthService $userGroups = $this->groupFilter($user); $userGroups = $this->getGroupsRecursive($userGroups, []); + return $userGroups; } /** * Get the parent groups of an array of groups. + * * @throws LdapException */ private function getGroupsRecursive(array $groupsArray, array $checked): array @@ -267,6 +319,7 @@ class LdapService extends ExternalAuthService /** * Get the parent groups of a single group. + * * @throws LdapException */ private function getGroupGroups(string $groupName): array @@ -300,7 +353,7 @@ class LdapService extends ExternalAuthService $count = 0; if (isset($userGroupSearchResponse[$groupsAttr]['count'])) { - $count = (int)$userGroupSearchResponse[$groupsAttr]['count']; + $count = (int) $userGroupSearchResponse[$groupsAttr]['count']; } for ($i = 0; $i < $count; $i++) { @@ -315,6 +368,7 @@ class LdapService extends ExternalAuthService /** * Sync the LDAP groups to the user roles for the current user. + * * @throws LdapException */ public function syncGroups(User $user, string $username) @@ -322,4 +376,22 @@ class LdapService extends ExternalAuthService $userLdapGroups = $this->getUserGroups($username); $this->syncWithGroups($user, $userLdapGroups); } + + /** + * Save and attach an avatar image, if found in the ldap details, and attach + * to the given user model. + */ + public function saveAndAttachAvatar(User $user, array $ldapUserDetails): void + { + if (is_null(config('services.ldap.thumbnail_attribute')) || is_null($ldapUserDetails['avatar'])) { + return; + } + + try { + $imageData = $ldapUserDetails['avatar']; + $this->userAvatars->assignToUserFromExistingData($user, $imageData, 'jpg'); + } catch (\Exception $exception) { + Log::info("Failed to use avatar image from LDAP data for user id {$user->id}"); + } + } }