array( 'columns' => array( 'is_active desc', 'user_name' ) ) ); /** * Set the base DN to use for a query * * @param string $baseDn * * @return $this */ public function setBaseDn($baseDn) { if ($baseDn && ($baseDn = trim($baseDn))) { $this->baseDn = $baseDn; } return $this; } /** * Return the base DN to use for a query * * @return string */ public function getBaseDn() { return $this->baseDn; } /** * Set the objectClass where to look for users * * @param string $userClass * * @return $this */ public function setUserClass($userClass) { $this->userClass = $this->getNormedAttribute($userClass); return $this; } /** * Return the objectClass where to look for users * * @return string */ public function getUserClass() { return $this->userClass; } /** * Set the attribute name where to find a user's name * * @param string $userNameAttribute * * @return $this */ public function setUserNameAttribute($userNameAttribute) { $this->userNameAttribute = $this->getNormedAttribute($userNameAttribute); return $this; } /** * Return the attribute name where to find a user's name * * @return string */ public function getUserNameAttribute() { return $this->userNameAttribute; } /** * Set the custom LDAP filter to apply on search queries * * @param string $filter * * @return $this */ public function setFilter($filter) { if ($filter && ($filter = trim($filter))) { if ($filter[0] === '(') { $filter = substr($filter, 1, -1); } $this->filter = $filter; } return $this; } /** * Return the custom LDAP filter to apply on search queries * * @return string */ public function getFilter() { return $this->filter; } public function getDomain() { return $this->domain; } /** * Set the domain the backend is responsible for * * @param string $domain * * @return $this */ public function setDomain($domain) { if ($domain && ($domain = trim($domain))) { $this->domain = $domain; } return $this; } /** * Initialize this repository's virtual tables * * @return array * * @throws ProgrammingError In case $this->userClass has not been set yet */ protected function initializeVirtualTables() { if ($this->userClass === null) { throw new ProgrammingError('It is required to set the object class where to find users first'); } return array( 'user' => $this->userClass ); } /** * Initialize this repository's query columns * * @return array * * @throws ProgrammingError In case $this->userNameAttribute has not been set yet */ protected function initializeQueryColumns() { if ($this->userNameAttribute === null) { throw new ProgrammingError('It is required to set a attribute name where to find a user\'s name first'); } if ($this->ds->getCapabilities()->isActiveDirectory()) { $isActiveAttribute = 'userAccountControl'; $createdAtAttribute = 'whenCreated'; $lastModifiedAttribute = 'whenChanged'; } else { // TODO(jom): Elaborate whether it is possible to add dynamic support for the ppolicy $isActiveAttribute = 'shadowExpire'; $createdAtAttribute = 'createTimestamp'; $lastModifiedAttribute = 'modifyTimestamp'; } return array( 'user' => array( 'user' => $this->userNameAttribute, 'user_name' => $this->userNameAttribute, 'is_active' => $isActiveAttribute, 'created_at' => $createdAtAttribute, 'last_modified' => $lastModifiedAttribute ) ); } /** * Initialize this repository's filter columns * * @return array */ protected function initializeFilterColumns() { return array( t('Username') => 'user_name', t('Active') => 'is_active', t('Created At') => 'created_at', t('Last modified') => 'last_modified' ); } /** * Initialize this repository's conversion rules * * @return array */ protected function initializeConversionRules() { if ($this->ds->getCapabilities()->isActiveDirectory()) { $stateConverter = 'user_account_control'; } else { $stateConverter = 'shadow_expire'; } return array( 'user' => array( 'is_active' => $stateConverter, 'created_at' => 'generalized_time', 'last_modified' => 'generalized_time' ) ); } /** * Return whether the given userAccountControl value defines that a user is permitted to login * * @param string|null $value * * @return bool */ protected function retrieveUserAccountControl($value) { if ($value === null) { return $value; } $ADS_UF_ACCOUNTDISABLE = 2; return ((int) $value & $ADS_UF_ACCOUNTDISABLE) === 0; } /** * Return whether the given shadowExpire value defines that a user is permitted to login * * @param string|null $value * * @return bool */ protected function retrieveShadowExpire($value) { if ($value === null) { return $value; } $now = new DateTime(); $bigBang = clone $now; $bigBang->setTimestamp(0); return ((int) $value) >= $bigBang->diff($now)->days; } /** * Validate that the requested table exists * * @param string $table The table to validate * @param RepositoryQuery $query An optional query to pass as context * * @return string * * @throws ProgrammingError In case the given table does not exist */ public function requireTable($table, RepositoryQuery $query = null) { if ($query !== null) { $query->getQuery()->setBase($this->baseDn); if ($this->filter) { $query->getQuery()->setNativeFilter($this->filter); } } return parent::requireTable($table, $query); } /** * Validate that the given column is a valid query target and return it or the actual name if it's an alias * * @param string $table The table where to look for the column or alias * @param string $name The name or alias of the column to validate * @param RepositoryQuery $query An optional query to pass as context * * @return string The given column's name * * @throws QueryException In case the given column is not a valid query column */ public function requireQueryColumn($table, $name, RepositoryQuery $query = null) { $column = parent::requireQueryColumn($table, $name, $query); if ($name === 'user_name' && $query !== null) { $query->getQuery()->setUnfoldAttribute('user_name'); } return $column; } /** * Authenticate the given user * * @param User $user * @param string $password * * @return bool True on success, false on failure * * @throws AuthenticationException In case authentication is not possible due to an error */ public function authenticate(User $user, $password) { if ($this->domain !== null) { if (! $user->hasDomain() || strtolower($user->getDomain()) !== strtolower($this->domain)) { return false; } $username = $user->getLocalUsername(); } else { $username = $user->getUsername(); } try { $userDn = $this ->select() ->where('user_name', str_replace('*', '', $username)) ->getQuery() ->setUsePagedResults(false) ->fetchDn(); if ($userDn === null) { return false; } $validCredentials = $this->ds->testCredentials($userDn, $password); if ($validCredentials) { $user->setAdditional('ldap_dn', $userDn); } return $validCredentials; } catch (LdapException $e) { throw new AuthenticationException( 'Failed to authenticate user "%s" against backend "%s". An exception was thrown:', $username, $this->getName(), $e ); } } /** * Inspect if this LDAP User Backend is working as expected by probing the backend * and testing if thea uthentication is possible * * Try to bind to the backend and fetch a single user to check if: * * * @return Inspection Inspection result */ public function inspect() { $result = new Inspection('Ldap User Backend'); // inspect the used connection to get more diagnostic info in case the connection is not working $result->write($this->ds->inspect()); try { try { $res = $this->select()->fetchRow(); } catch (LdapException $e) { throw new AuthenticationException('Connection not possible', $e); } $result->write('Searching for: ' . sprintf( 'objectClass "%s" in DN "%s" (Filter: %s)', $this->userClass, $this->baseDn ?: $this->ds->getDn(), $this->filter ?: 'None' )); if ($res === false) { throw new AuthenticationException('Error, no users found in backend'); } $result->write(sprintf('%d users found in backend', $this->select()->count())); if (! isset($res->user_name)) { throw new AuthenticationException( 'UserNameAttribute "%s" not existing in objectClass "%s"', $this->userNameAttribute, $this->userClass ); } } catch (AuthenticationException $e) { if (($previous = $e->getPrevious()) !== null) { $result->error($previous->getMessage()); } else { $result->error($e->getMessage()); } } catch (Exception $e) { $result->error(sprintf('Unable to validate authentication: %s', $e->getMessage())); } return $result; } }