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 = 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 = 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; } /** * Apply the given configuration to this backend * * @param ConfigObject $config * * @return $this */ public function setConfig(ConfigObject $config) { return $this ->setBaseDn($config->base_dn) ->setUserClass($config->user_class) ->setUserNameAttribute($config->user_name_attribute) ->setFilter($config->filter); } /** * 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) { try { $userDn = $this ->select() ->where('user_name', str_replace('*', '', $user->getUsername())) ->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:', $user->getUsername(), $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: *