diff options
Diffstat (limited to 'library/Icinga/Authentication/User')
6 files changed, 1174 insertions, 0 deletions
diff --git a/library/Icinga/Authentication/User/DbUserBackend.php b/library/Icinga/Authentication/User/DbUserBackend.php new file mode 100644 index 0000000..0e8cc6a --- /dev/null +++ b/library/Icinga/Authentication/User/DbUserBackend.php @@ -0,0 +1,256 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Authentication\User; + +use Exception; +use Icinga\Data\Inspectable; +use Icinga\Data\Inspection; +use Icinga\Data\Filter\Filter; +use Icinga\Exception\AuthenticationException; +use Icinga\Repository\DbRepository; +use Icinga\User; +use PDO; + +class DbUserBackend extends DbRepository implements UserBackendInterface, Inspectable +{ + /** + * The query columns being provided + * + * @var array + */ + protected $queryColumns = array( + 'user' => array( + 'user' => 'name COLLATE utf8mb4_general_ci', + 'user_name' => 'name', + 'is_active' => 'active', + 'created_at' => 'UNIX_TIMESTAMP(ctime)', + 'last_modified' => 'UNIX_TIMESTAMP(mtime)' + ) + ); + + /** + * The statement columns being provided + * + * @var array + */ + protected $statementColumns = array( + 'user' => array( + 'password' => 'password_hash', + 'created_at' => 'ctime', + 'last_modified' => 'mtime' + ) + ); + + /** + * The columns which are not permitted to be queried + * + * @var array + */ + protected $blacklistedQueryColumns = array('user'); + + /** + * The search columns being provided + * + * @var array + */ + protected $searchColumns = array('user'); + + /** + * The default sort rules to be applied on a query + * + * @var array + */ + protected $sortRules = array( + 'user_name' => array( + 'columns' => array( + 'is_active desc', + 'user_name' + ) + ) + ); + + /** + * The value conversion rules to apply on a query or statement + * + * @var array + */ + protected $conversionRules = array( + 'user' => array( + 'password' + ) + ); + + /** + * Initialize this database user backend + */ + protected function init() + { + if (! $this->ds->getTablePrefix()) { + $this->ds->setTablePrefix('icingaweb_'); + } + } + + /** + * Initialize this repository's filter columns + * + * @return array + */ + protected function initializeFilterColumns() + { + $userLabel = t('Username') . ' ' . t('(Case insensitive)'); + return array( + $userLabel => 'user', + t('Username') => 'user_name', + t('Active') => 'is_active', + t('Created at') => 'created_at', + t('Last modified') => 'last_modified' + ); + } + + /** + * Insert a table row with the given data + * + * @param string $table + * @param array $bind + * + * @return void + */ + public function insert($table, array $bind, array $types = array()) + { + $this->requireTable($table); + $bind['created_at'] = date('Y-m-d H:i:s'); + $this->ds->insert( + $this->prependTablePrefix($table), + $this->requireStatementColumns($table, $bind), + array( + 'active' => PDO::PARAM_INT, + 'password_hash' => PDO::PARAM_LOB + ) + ); + } + + /** + * Update table rows with the given data, optionally limited by using a filter + * + * @param string $table + * @param array $bind + * @param Filter $filter + */ + public function update($table, array $bind, Filter $filter = null, array $types = array()) + { + $this->requireTable($table); + $bind['last_modified'] = date('Y-m-d H:i:s'); + if ($filter) { + $filter = $this->requireFilter($table, $filter); + } + + $this->ds->update( + $this->prependTablePrefix($table), + $this->requireStatementColumns($table, $bind), + $filter, + array( + 'active' => PDO::PARAM_INT, + 'password_hash' => PDO::PARAM_LOB + ) + ); + } + + /** + * Hash and return the given password + * + * @param string $value + * + * @return string + */ + protected function persistPassword($value) + { + return password_hash($value, PASSWORD_DEFAULT); + } + + /** + * Fetch the hashed password for the given user + * + * @param string $username The name of the user + * + * @return string + */ + protected function getPasswordHash($username) + { + if ($this->ds->getDbType() === 'pgsql') { + // Since PostgreSQL version 9.0 the default value for bytea_output is 'hex' instead of 'escape' + $columns = array('password_hash' => 'ENCODE(password_hash, \'escape\')'); + } else { + $columns = array('password_hash'); + } + + $nameColumn = 'name'; + if ($this->ds->getDbType() === 'mysql') { + $username = strtolower($username); + $nameColumn = 'BINARY LOWER(name)'; + } + + $query = $this->ds->select() + ->from($this->prependTablePrefix('user'), $columns) + ->where($nameColumn, $username) + ->where('active', true); + + $statement = $this->ds->getDbAdapter()->prepare($query->getSelectQuery()); + $statement->execute(); + $statement->bindColumn(1, $lob, PDO::PARAM_LOB); + $statement->fetch(PDO::FETCH_BOUND); + if (is_resource($lob)) { + $lob = stream_get_contents($lob); + } + + if ($lob === null) { + return ''; + } + + return $this->ds->getDbType() === 'pgsql' ? pg_unescape_bytea($lob) : $lob; + } + + /** + * 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 { + return password_verify( + $password, + $this->getPasswordHash($user->getUsername()) + ); + } catch (Exception $e) { + throw new AuthenticationException( + 'Failed to authenticate user "%s" against backend "%s". An exception was thrown:', + $user->getUsername(), + $this->getName(), + $e + ); + } + } + + /** + * Inspect this object to gain extended information about its health + * + * @return Inspection The inspection result + */ + public function inspect() + { + $insp = new Inspection('Db User Backend'); + $insp->write($this->ds->inspect()); + try { + $insp->write(sprintf('%s active users', $this->select()->where('is_active', true)->count())); + } catch (Exception $e) { + $insp->error(sprintf('Query failed: %s', $e->getMessage())); + } + return $insp; + } +} diff --git a/library/Icinga/Authentication/User/DomainAwareInterface.php b/library/Icinga/Authentication/User/DomainAwareInterface.php new file mode 100644 index 0000000..3ff9c31 --- /dev/null +++ b/library/Icinga/Authentication/User/DomainAwareInterface.php @@ -0,0 +1,17 @@ +<?php +/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Authentication\User; + +/** + * Interface for user backends that are responsible for a specific domain + */ +interface DomainAwareInterface +{ + /** + * Get the domain the backend is responsible for + * + * @return string + */ + public function getDomain(); +} diff --git a/library/Icinga/Authentication/User/ExternalBackend.php b/library/Icinga/Authentication/User/ExternalBackend.php new file mode 100644 index 0000000..6e79928 --- /dev/null +++ b/library/Icinga/Authentication/User/ExternalBackend.php @@ -0,0 +1,124 @@ +<?php +/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Authentication\User; + +use Icinga\Application\Logger; +use Icinga\Data\ConfigObject; +use Icinga\User; + +/** + * Test login with external authentication mechanism, e.g. Apache + */ +class ExternalBackend implements UserBackendInterface +{ + /** + * Possible variables where to read the user from + * + * @var string[] + */ + public static $remoteUserEnvvars = array('REMOTE_USER', 'REDIRECT_REMOTE_USER'); + + /** + * The name of this backend + * + * @var string + */ + protected $name; + + /** + * Regexp expression to strip values from a username + * + * @var string + */ + protected $stripUsernameRegexp; + + /** + * Create new authentication backend of type "external" + * + * @param ConfigObject $config + */ + public function __construct(ConfigObject $config) + { + $this->stripUsernameRegexp = $config->get('strip_username_regexp'); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->name = $name; + return $this; + } + + /** + * Get the remote user from environment or $_SERVER, if any + * + * @param string $variable The name of the variable where to read the user from + * + * @return string|null + */ + public static function getRemoteUser($variable = 'REMOTE_USER') + { + $username = getenv($variable); + if (! empty($username)) { + return $username; + } + + if (array_key_exists($variable, $_SERVER) && ! empty($_SERVER[$variable])) { + return $_SERVER[$variable]; + } + } + + /** + * Get the remote user information from environment or $_SERVER, if any + * + * @return array Contains always two entries, the username and origin which may both set to null. + */ + public static function getRemoteUserInformation() + { + foreach (static::$remoteUserEnvvars as $envVar) { + $username = static::getRemoteUser($envVar); + if ($username !== null) { + return array($username, $envVar); + } + } + + return array(null, null); + } + + /** + * {@inheritdoc} + */ + public function authenticate(User $user, $password = null) + { + list($username, $field) = static::getRemoteUserInformation(); + if ($username !== null) { + $user->setExternalUserInformation($username, $field); + + if ($this->stripUsernameRegexp) { + $stripped = @preg_replace($this->stripUsernameRegexp, '', $username); + if ($stripped === false) { + Logger::error('Failed to strip external username. The configured regular expression is invalid.'); + return false; + } + + $username = $stripped; + } + + $user->setUsername($username); + return true; + } + + return false; + } +} diff --git a/library/Icinga/Authentication/User/LdapUserBackend.php b/library/Icinga/Authentication/User/LdapUserBackend.php new file mode 100644 index 0000000..6a2cacf --- /dev/null +++ b/library/Icinga/Authentication/User/LdapUserBackend.php @@ -0,0 +1,479 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Authentication\User; + +use DateTime; +use Exception; +use Icinga\Data\ConfigObject; +use Icinga\Data\Inspectable; +use Icinga\Data\Inspection; +use Icinga\Exception\AuthenticationException; +use Icinga\Exception\ProgrammingError; +use Icinga\Exception\QueryException; +use Icinga\Repository\LdapRepository; +use Icinga\Repository\RepositoryQuery; +use Icinga\Protocol\Ldap\LdapException; +use Icinga\User; + +class LdapUserBackend extends LdapRepository implements UserBackendInterface, DomainAwareInterface, Inspectable +{ + /** + * The base DN to use for a query + * + * @var string + */ + protected $baseDn; + + /** + * The objectClass where look for users + * + * @var string + */ + protected $userClass; + + /** + * The attribute name where to find a user's name + * + * @var string + */ + protected $userNameAttribute; + + /** + * The custom LDAP filter to apply on search queries + * + * @var string + */ + protected $filter; + + /** + * The domain the backend is responsible for + * + * @var string + */ + protected $domain; + + /** + * The columns which are not permitted to be queried + * + * @var array + */ + protected $blacklistedQueryColumns = array('user'); + + /** + * The search columns being provided + * + * @var array + */ + protected $searchColumns = array('user'); + + /** + * The default sort rules to be applied on a query + * + * @var array + */ + protected $sortRules = array( + 'user_name' => 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: + * <ul> + * <li>Connection credentials are correct and the bind is possible</li> + * <li>At least one user exists</li> + * <li>The specified userClass has the property specified by userNameAttribute</li> + * </ul> + * + * @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; + } +} diff --git a/library/Icinga/Authentication/User/UserBackend.php b/library/Icinga/Authentication/User/UserBackend.php new file mode 100644 index 0000000..423b278 --- /dev/null +++ b/library/Icinga/Authentication/User/UserBackend.php @@ -0,0 +1,259 @@ +<?php +/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Authentication\User; + +use Icinga\Application\Config; +use Icinga\Application\Logger; +use Icinga\Application\Icinga; +use Icinga\Data\ConfigObject; +use Icinga\Data\ResourceFactory; +use Icinga\Exception\ConfigurationError; +use Icinga\Util\ConfigAwareFactory; + +/** + * Factory for user backends + */ +class UserBackend implements ConfigAwareFactory +{ + /** + * The default user backend types provided by Icinga Web 2 + * + * @var array + */ + protected static $defaultBackends = array( + 'external', + 'db', + 'ldap', + 'msldap' + ); + + /** + * The registered custom user backends with their identifier as key and class name as value + * + * @var array + */ + protected static $customBackends; + + /** + * User backend configuration + * + * @var Config + */ + private static $backends; + + /** + * Set user backend configuration + * + * @param Config $config + */ + public static function setConfig($config) + { + self::$backends = $config; + } + + /** + * Return the configuration of all existing user backends + * + * @return Config + */ + public static function getBackendConfigs() + { + self::assertBackendsExist(); + return self::$backends; + } + + /** + * Check if any user backends exist. If not, throw an error. + * + * @throws ConfigurationError + */ + private static function assertBackendsExist() + { + if (self::$backends === null) { + throw new ConfigurationError( + 'User backends not set up. Please contact your Icinga Web administrator' + ); + } + } + + /** + * Register all custom user backends from all loaded modules + */ + protected static function registerCustomUserBackends() + { + if (static::$customBackends !== null) { + return; + } + + static::$customBackends = array(); + $providedBy = array(); + foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) { + foreach ($module->getUserBackends() as $identifier => $className) { + if (array_key_exists($identifier, $providedBy)) { + Logger::warning( + 'Cannot register user backend of type "%s" provided by module "%s".' + . ' The type is already provided by module "%s"', + $identifier, + $module->getName(), + $providedBy[$identifier] + ); + } elseif (in_array($identifier, static::$defaultBackends)) { + Logger::warning( + 'Cannot register user backend of type "%s" provided by module "%s".' + . ' The type is a default type provided by Icinga Web 2', + $identifier, + $module->getName() + ); + } else { + $providedBy[$identifier] = $module->getName(); + static::$customBackends[$identifier] = $className; + } + } + } + } + + /** + * Get config forms of all custom user backends + */ + public static function getCustomBackendConfigForms() + { + $customBackendConfigForms = []; + static::registerCustomUserBackends(); + foreach (self::$customBackends as $customBackendType => $customBackendClass) { + if (method_exists($customBackendClass, 'getConfigurationFormClass')) { + $customBackendConfigForms[$customBackendType] = $customBackendClass::getConfigurationFormClass(); + } + } + + return $customBackendConfigForms; + } + + /** + * Return the class for the given custom user backend + * + * @param string $identifier The identifier of the custom user backend + * + * @return string|null The name of the class or null in case there was no + * backend found with the given identifier + * + * @throws ConfigurationError In case the class associated to the given identifier does not exist + */ + protected static function getCustomUserBackend($identifier) + { + static::registerCustomUserBackends(); + if (array_key_exists($identifier, static::$customBackends)) { + $className = static::$customBackends[$identifier]; + if (! class_exists($className)) { + throw new ConfigurationError( + 'Cannot utilize user backend of type "%s". Class "%s" does not exist', + $identifier, + $className + ); + } + + return $className; + } + } + + /** + * Create and return a user backend with the given name and given configuration applied to it + * + * @param string $name + * @param ConfigObject $backendConfig + * + * @return UserBackendInterface + * + * @throws ConfigurationError + */ + public static function create($name, ConfigObject $backendConfig = null) + { + if ($backendConfig === null) { + self::assertBackendsExist(); + if (self::$backends->hasSection($name)) { + $backendConfig = self::$backends->getSection($name); + } else { + throw new ConfigurationError('User backend "%s" does not exist', $name); + } + } + + if ($backendConfig->name !== null) { + $name = $backendConfig->name; + } + + if (! ($backendType = strtolower($backendConfig->backend))) { + throw new ConfigurationError( + 'Authentication configuration for user backend "%s" is missing the \'backend\' directive', + $name + ); + } + + if ($backendType === 'external') { + $backend = new ExternalBackend($backendConfig); + $backend->setName($name); + return $backend; + } + if (in_array($backendType, static::$defaultBackends)) { + // The default backend check is the first one because of performance reasons: + // Do not attempt to load a custom user backend unless it's actually required + } elseif (($customClass = static::getCustomUserBackend($backendType)) !== null) { + $backend = new $customClass($backendConfig); + if (! is_a($backend, 'Icinga\Authentication\User\UserBackendInterface')) { + throw new ConfigurationError( + 'Cannot utilize user backend of type "%s". Class "%s" does not implement UserBackendInterface', + $backendType, + $customClass + ); + } + + $backend->setName($name); + return $backend; + } else { + throw new ConfigurationError( + 'Authentication configuration for user backend "%s" defines an invalid backend type.' + . ' Backend type "%s" is not supported', + $name, + $backendType + ); + } + + if ($backendConfig->resource === null) { + throw new ConfigurationError( + 'Authentication configuration for user backend "%s" is missing the \'resource\' directive', + $name + ); + } + + $resourceConfig = ResourceFactory::getResourceConfig($backendConfig->resource); + if ($backendType === 'db' && $resourceConfig->db === 'mysql') { + $resourceConfig->charset = 'utf8mb4'; + } + + $resource = ResourceFactory::createResource($resourceConfig); + $backend = null; + switch ($backendType) { + case 'db': + $backend = new DbUserBackend($resource); + break; + case 'msldap': + $backend = new LdapUserBackend($resource); + $backend->setBaseDn($backendConfig->base_dn); + $backend->setUserClass($backendConfig->get('user_class', 'user')); + $backend->setUserNameAttribute($backendConfig->get('user_name_attribute', 'sAMAccountName')); + $backend->setFilter($backendConfig->filter); + $backend->setDomain($backendConfig->domain); + break; + case 'ldap': + $backend = new LdapUserBackend($resource); + $backend->setBaseDn($backendConfig->base_dn); + $backend->setUserClass($backendConfig->get('user_class', 'inetOrgPerson')); + $backend->setUserNameAttribute($backendConfig->get('user_name_attribute', 'uid')); + $backend->setFilter($backendConfig->filter); + $backend->setDomain($backendConfig->domain); + break; + } + + $backend->setName($name); + return $backend; + } +} diff --git a/library/Icinga/Authentication/User/UserBackendInterface.php b/library/Icinga/Authentication/User/UserBackendInterface.php new file mode 100644 index 0000000..4660eb0 --- /dev/null +++ b/library/Icinga/Authentication/User/UserBackendInterface.php @@ -0,0 +1,39 @@ +<?php +/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Authentication\User; + +use Icinga\Authentication\Authenticatable; +use Icinga\User; + +/** + * Interface for user backends + */ +interface UserBackendInterface extends Authenticatable +{ + /** + * Set this backend's name + * + * @param string $name + * + * @return $this + */ + public function setName($name); + + /** + * Return this backend's name + * + * @return string + */ + public function getName(); + + /** + * Return this backend's configuration form class path + * + * This is not part of the interface to not break existing implementations. + * If you need a custom backend form, implement this method. + * + * @return string + */ + //public static function getConfigurationFormClass(); +} |