AuthManager: Implement backend chain

refs #4641
refs #4590
refs #4593
This commit is contained in:
Marius Hein 2013-08-28 10:16:18 +02:00
parent 12b3f5f732
commit dfb7238b81
8 changed files with 626 additions and 331 deletions

View File

@ -1,5 +1,4 @@
<?php <?php
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga 2 Web. * This file is part of Icinga 2 Web.
@ -29,12 +28,18 @@
namespace Icinga\Authentication\Backend; namespace Icinga\Authentication\Backend;
use \Exception;
use \stdClass;
use \Zend_Config;
use \Zend_Db; use \Zend_Db;
use \Icinga\Application\DbAdapterFactory;
use \Icinga\Exception\ProgrammingError;
use \Icinga\User; use \Icinga\User;
use \Icinga\Authentication\UserBackend; use \Icinga\Authentication\UserBackend;
use \Icinga\Authentication\Credentials; use \Icinga\Authentication\Credentials;
use \Icinga\Authentication; use \Icinga\Authentication;
use \Icinga\Application\Logger; use \Icinga\Application\Logger;
use \Icinga\Exception\ConfigurationError;
/** /**
* User authentication backend (@see Icinga\Authentication\UserBackend) for * User authentication backend (@see Icinga\Authentication\UserBackend) for
@ -47,21 +52,37 @@ use \Icinga\Application\Logger;
class DbUserBackend implements UserBackend class DbUserBackend implements UserBackend
{ {
/** /**
* Mapping of all table column names * Table map for column username
*
* @var string
*/ */
const USER_NAME_COLUMN = 'username'; const USER_NAME_COLUMN = 'username';
/**
* Table map for column salt
*
* @var string
*/
const SALT_COLUMN = 'salt'; const SALT_COLUMN = 'salt';
/**
* Table map for column password
*
* @var string
*/
const PASSWORD_COLUMN = 'password'; const PASSWORD_COLUMN = 'password';
/**
* Table map for column active
*
* @var string
*/
const ACTIVE_COLUMN = 'active'; const ACTIVE_COLUMN = 'active';
/** /**
* The database connection that will be used for fetching users * The database connection that will be used for fetching users
* *
* @var \Zend_Db * @var Zend_Db
*/ */
private $db = null; private $db = null;
@ -70,25 +91,45 @@ class DbUserBackend implements UserBackend
* *
* @var String * @var String
*/ */
private $userTable = "account"; private $userTable = 'account';
/**
* Name of the backend
*
* @var string
*/
private $name;
/** /**
* Create a DbUserBackend * Create a DbUserBackend
* *
* @param Zend_Db The database that provides the authentication data * @param Zend_Config $config The database that provides the authentication data
* @throws ConfigurationError
*/ */
public function __construct($database) public function __construct(Zend_Config $config)
{ {
$this->db = $database; $this->name = $config->name;
$this->db = DbAdapterFactory::getDbAdapter($config->resource);
// Test if the connection is available // Throw any errors for Authentication/Manager
$this->db->getConnection(); $this->db->getConnection();
} }
/**
* Name of the backend
*
* @return string
*/
public function getName()
{
return $this->name;
}
/** /**
* Check if the user identified by the given credentials is available * Check if the user identified by the given credentials is available
* *
* @param Credentials $credentials The login credentials * @param Credentials $credential
* *
* @return boolean True when the username is known and currently active. * @return boolean True when the username is known and currently active.
*/ */
@ -105,7 +146,7 @@ class DbUserBackend implements UserBackend
/** /**
* Authenticate a user with the given credentials * Authenticate a user with the given credentials
* *
* @param Credentials $credentials The login credentials * @param Credentials $credential
* *
* @return User|null The authenticated user or Null. * @return User|null The authenticated user or Null.
*/ */
@ -118,16 +159,20 @@ class DbUserBackend implements UserBackend
$this->db->getConnection(); $this->db->getConnection();
try { try {
$salt = $this->getUserSalt($credential->getUsername()); $salt = $this->getUserSalt($credential->getUsername());
} catch (\Exception $e) { } catch (Exception $e) {
Logger::error($e->getMessage()); Logger::error(
'Could not create salt for user %s. Exception was thrown: %s',
$credential->getUsername(),
$e->getMessage()
);
return null; return null;
} }
$res = $this->db $res = $this->db
->select()->from($this->userTable) ->select()->from($this->userTable)
->where(self::USER_NAME_COLUMN.' = ?', $credential->getUsername()) ->where(self::USER_NAME_COLUMN . ' = ?', $credential->getUsername())
->where(self::ACTIVE_COLUMN. ' = ?', true) ->where(self::ACTIVE_COLUMN . ' = ?', true)
->where( ->where(
self::PASSWORD_COLUMN. ' = ?', self::PASSWORD_COLUMN . ' = ?',
hash_hmac( hash_hmac(
'sha256', 'sha256',
$salt, $salt,
@ -138,14 +183,17 @@ class DbUserBackend implements UserBackend
if ($res !== false) { if ($res !== false) {
return $this->createUserFromResult($res); return $this->createUserFromResult($res);
} }
return null;
} }
/** /**
* Fetch the users salt from the database * Fetch the users salt from the database
* *
* @param $username The user whose salt should be fetched. * @param string$username The user whose salt should be fetched
* *
* @return String|null Returns the salt-string or Null, when the user does not exist. * @return string|null Return the salt-string or null, when the user does not exist
* @throws ProgrammingError
*/ */
private function getUserSalt($username) private function getUserSalt($username)
{ {
@ -157,16 +205,16 @@ class DbUserBackend implements UserBackend
if ($res !== false) { if ($res !== false) {
return $res->{self::SALT_COLUMN}; return $res->{self::SALT_COLUMN};
} else { } else {
throw new \Exception('No Salt found for user "' . $username . '"'); throw new ProgrammingError('No Salt found for user "' . $username . '"');
} }
} }
/** /**
* Fetch the user information from the database * Fetch the user information from the database
* *
* @param $username The name of the user. * @param string $username The name of the user
* *
* @return User|null Returns the user object, or null when the user does not exist. * @return User|null Returns the user object, or null when the user does not exist
*/ */
private function getUserByName($username) private function getUserByName($username)
{ {
@ -178,15 +226,15 @@ class DbUserBackend implements UserBackend
$this->db->getConnection(); $this->db->getConnection();
$res = $this->db-> $res = $this->db->
select()->from($this->userTable) select()->from($this->userTable)
->where(self::USER_NAME_COLUMN.' = ?', $username) ->where(self::USER_NAME_COLUMN .' = ?', $username)
->where(self::ACTIVE_COLUMN.' = ?', true) ->where(self::ACTIVE_COLUMN .' = ?', true)
->query()->fetch(); ->query()->fetch();
if ($res !== false) { if ($res !== false) {
return $this->createUserFromResult($res); return $this->createUserFromResult($res);
} }
return null; return null;
} catch (\Zend_Db_Statement_Exception $exc) { } catch (\Zend_Db_Statement_Exception $exc) {
Logger::error("Could not fetch users from db : %s ", $exc->getMessage()); Logger::error('Could not fetch users from db : %s ', $exc->getMessage());
return null; return null;
} }
} }
@ -194,11 +242,11 @@ class DbUserBackend implements UserBackend
/** /**
* Create a new instance of User from a query result * Create a new instance of User from a query result
* *
* @param $result The query result containing the user row * @param stdClass $resultRow
* *
* @return User The created instance of User. * @return User The created instance of User.
*/ */
private function createUserFromResult($resultRow) private function createUserFromResult(stdClass $resultRow)
{ {
$usr = new User( $usr = new User(
$resultRow->{self::USER_NAME_COLUMN} $resultRow->{self::USER_NAME_COLUMN}
@ -206,6 +254,7 @@ class DbUserBackend implements UserBackend
return $usr; return $usr;
} }
/** /**
* Return the number of users in this database connection * Return the number of users in this database connection
* *

View File

@ -28,49 +28,70 @@
namespace Icinga\Authentication\Backend; namespace Icinga\Authentication\Backend;
use Icinga\User; use \stdClass;
use Icinga\Authentication\UserBackend; use \Zend_Config;
use Icinga\Authentication\Credentials; use \Icinga\User;
use Icinga\Protocol\Ldap; use \Icinga\Authentication\UserBackend;
use \Icinga\Authentication\Credentials;
use \Icinga\Protocol\Ldap;
use Icinga\Protocol\Ldap\Connection;
use \Icinga\Application\Config as IcingaConfig; use \Icinga\Application\Config as IcingaConfig;
/** /**
* User authentication backend (@see Icinga\Authentication\UserBackend) for * User authentication backend
* authentication of users via LDAP. The attributes and location of the */
* user is configurable via the application.ini
*
* See the UserBackend class (@see Icinga\Authentication\UserBackend) for
* usage information
**/
class LdapUserBackend implements UserBackend class LdapUserBackend implements UserBackend
{ {
/** /**
* @var Ldap\Connection * Ldap resource
*
* @var Connection
**/ **/
protected $connection; protected $connection;
/** /**
* The ldap connection information * The ldap connection information
* *
* @var object * @var Zend_Config
*/ */
private $config; private $config;
/** /**
* Creates a new Authentication backend using the * Name of the backend
* connection information provided in $config
* *
* @param object $config The ldap connection information * @var string
**/ */
public function __construct($config) private $name;
/**
* Create new Ldap User backend
*
* @param Zend_Config $config
*/
public function __construct(Zend_Config $config)
{ {
$this->connection = new Ldap\Connection($config); $this->connection = new Ldap\Connection($config);
$this->config = $config; $this->config = $config;
$this->name = $config->name;
} }
/** /**
* @see Icinga\Authentication\UserBackend::hasUsername * Name of the backend
**/ *
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Test if the username exists
*
* @param Credentials $credential
*
* @return bool
*/
public function hasUsername(Credentials $credential) public function hasUsername(Credentials $credential)
{ {
return $this->connection->fetchOne( return $this->connection->fetchOne(
@ -79,11 +100,11 @@ class LdapUserBackend implements UserBackend
} }
/** /**
* Removes the '*' characted from $string * Removes the '*' character from $string
* *
* @param String $string * @param string $string
* *
* @return String * @return string
**/ **/
protected function stripAsterisks($string) protected function stripAsterisks($string)
{ {
@ -91,13 +112,11 @@ class LdapUserBackend implements UserBackend
} }
/** /**
* Tries to fetch the username given in $username from * Tries to fetch the username
* the ldap connection, using the configuration parameters
* given in the Authentication configuration
* *
* @param String $username The username to select * @param string $username The username to select
* *
* @return object $result * @return stdClass $result
**/ **/
protected function selectUsername($username) protected function selectUsername($username)
{ {
@ -115,8 +134,12 @@ class LdapUserBackend implements UserBackend
} }
/** /**
* @see Icinga\Authentication\UserBackend::authenticate * Authenticate
**/ *
* @param Credentials $credentials
*
* @return User
*/
public function authenticate(Credentials $credentials) public function authenticate(Credentials $credentials)
{ {
if (!$this->connection->testCredentials( if (!$this->connection->testCredentials(

View File

@ -29,26 +29,43 @@
namespace Icinga\Authentication; namespace Icinga\Authentication;
/** /**
* Data holder object for authentication information * Data holder object for authentication information
* *
* This object should be used instead of passing names and * This object should be used instead of passing names and
* passwords as primitives in order to allow additional information * passwords as primitives in order to allow additional information
* to be provided (like the domain) when needed * to be provided (like the domain) when needed.
**/ */
class Credentials class Credentials
{ {
protected $username; /**
protected $password; * Username
protected $domain; *
* @var string
*/
private $username;
/**
* Password
*
* @var string
*/
private $password;
/**
* Domain
*
* @var string
*/
private $domain;
/** /**
* Create a new credential object * Create a new credential object
* *
* @param String $username * @param string $username
* @param String $password * @param string $password
* @param String $domain * @param string $domain
**/ */
public function __construct($username = "", $password = null, $domain = null) public function __construct($username = '', $password = null, $domain = null)
{ {
$this->username = $username; $this->username = $username;
$this->password = $password; $this->password = $password;
@ -56,50 +73,62 @@ class Credentials
} }
/** /**
* @return String * Getter for username
**/ *
* @return string
*/
public function getUsername() public function getUsername()
{ {
return $this->username; return $this->username;
} }
/** /**
* @param String $username * Setter for username
**/ *
* @param string $username
*/
public function setUsername($username) public function setUsername($username)
{ {
return $this->username = $username; $this->username = $username;
} }
/** /**
* @return String * Getter for password
**/ *
* @return string
*/
public function getPassword() public function getPassword()
{ {
return $this->password; return $this->password;
} }
/** /**
* @param String $password * Setter for password
**/ *
* @param string $password
*/
public function setPassword($password) public function setPassword($password)
{ {
return $this->password = $password; $this->password = $password;
} }
/** /**
* @return String * Getter for domain
**/ *
* @return string
*/
public function getDomain() public function getDomain()
{ {
return $this->domain; return $this->domain;
} }
/** /**
* @param String $domain * Setter for domain
**/ *
* @param string $domain
*/
public function setDomain($domain) public function setDomain($domain)
{ {
return $this->domain = $domain; $this->domain = $domain;
} }
} }

View File

@ -0,0 +1,44 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}}
namespace Icinga\Authentication;
/**
* Api behaviour for a group backend
*
* @TODO(mh): Groups not implemented at present, re-implement if needed (#4624)
*/
interface GroupBackend
{
/**
* Name of the backend
*
* @return string
*/
public function getName();
}

View File

@ -28,17 +28,20 @@
namespace Icinga\Authentication; namespace Icinga\Authentication;
use \Exception;
use \Zend_Config;
use \Icinga\Application\Logger; use \Icinga\Application\Logger;
use \Icinga\Application\Config as IcingaConfig; use \Icinga\Application\Config as IcingaConfig;
use \Icinga\Application\DbAdapterFactory; use \Icinga\Application\DbAdapterFactory;
use \Icinga\Exception\ConfigurationError as ConfigError; use \Icinga\Exception\ConfigurationError as ConfigError;
use \Icinga\User; use \Icinga\User;
use \Icinga\Exception\ConfigurationError;
/** /**
* The authentication manager allows to identify users and * The authentication manager allows to identify users and
* to persist authentication information in a session. * to persist authentication information in a session.
* *
* Direct instanciation is not permitted, the Authencation manager * Direct instantiation is not permitted, the AuthenticationManager
* must be created using the getInstance method. Subsequent getInstance * must be created using the getInstance method. Subsequent getInstance
* calls return the same object and ignore any additional configuration * calls return the same object and ignore any additional configuration
* *
@ -46,37 +49,57 @@ use \Icinga\User;
* you have to decide whether you want to modify the session on the first * you have to decide whether you want to modify the session on the first
* initialization and provide the 'writeSession' option if so, otherwise * initialization and provide the 'writeSession' option if so, otherwise
* session changes won't be written to disk. This is done to prevent PHP * session changes won't be written to disk. This is done to prevent PHP
* from blockung concurrent requests * from blocking concurrent requests
* *
* @TODO: Group support is not implemented yet * @TODO(mh): Group support is not implemented yet (#4624)
**/ **/
class Manager class Manager
{ {
const BACKEND_TYPE_USER = "User"; /**
const BACKEND_TYPE_GROUP = "Group"; * Backend type user
*
* @var string
*/
const BACKEND_TYPE_USER = 'user';
/** /**
* @var Manager * Backend type group
**/ *
* @var string
*/
const BACKEND_TYPE_GROUP = 'group';
/**
* Singleton instance
*
* @var self
*/
private static $instance = null; private static $instance = null;
/** /**
* Instance of authenticated user
*
* @var User * @var User
**/ **/
private $user = null; private $user = null;
private $groups = array();
/** /**
* @var UserBackend * Array of user backends
*
* @var UserBackend[]
**/ **/
private $userBackend = null; private $userBackends = array();
/** /**
* @var GroupBackend * Array of group backends
*
* @var array
**/ **/
private $groupBackend = null; private $groupBackends = array();
/** /**
* Session
*
* @var Session * @var Session
**/ **/
private $session = null; private $session = null;
@ -86,42 +109,31 @@ class Manager
* configuration provided in the authentication.ini if no config is given) * configuration provided in the authentication.ini if no config is given)
* and with the given options. * and with the given options.
* *
* @param IcingaConfig $config The configuration to use for authentication * @param Zend_Config $config The configuration to use for authentication
* instead of the authentication.ini * instead of the authentication.ini
* @param Array $options Additional options that affect the managers behaviour. * @param array $options Additional options that affect the managers behaviour.
* Supported values: * Supported values:
* * writeSession : Whether the session should be writable * * writeSession: Whether the session should be writable
* * userBackendClass : Allows to provide an own user backend class * * sessionClass: Allows to provide a different session implementation)
* (used for testing) * * noDefaultConfig: Disable default configuration from authentication.ini
* * groupBackendClass : Allows to provide an own group backend class
* (used for testing)
* * sessionClass : Allows to provide a different session implementation)
**/ **/
private function __construct($config = null, array $options = array()) private function __construct(Zend_Config $config = null, array $options = array())
{ {
if ($config === null) { if ($config === null && !(isset($options['noDefaultConfig']) && $options['noDefaultConfig'] == true)) {
$config = IcingaConfig::app('authentication'); $config = IcingaConfig::app('authentication');
} }
if (isset($options["userBackendClass"])) {
$this->userBackend = $options["userBackendClass"]; if ($config !== null) {
} else { $this->setupBackends($config);
$this->userBackend = $this->initBestBackend(self::BACKEND_TYPE_USER, $config);
} }
if (isset($options["groupBackendClass"])) { if (!isset($options['sessionClass'])) {
$this->groupBackend = $options["groupBackendClass"];
} else {
// @TODO(mh): Re-enable when ready (#4624)
// Deactivated, logging error messages breaks bootstrap
// $this->groupBackend = $this->initBestBackend(self::BACKEND_TYPE_GROUP, $config);
}
if (!isset($options["sessionClass"])) {
$this->session = new PhpSession($config->session); $this->session = new PhpSession($config->session);
} else { } else {
$this->session = $options["sessionClass"]; $this->session = $options['sessionClass'];
} }
if (isset($options["writeSession"]) && $options["writeSession"] === true) {
if (isset($options['writeSession']) && $options['writeSession'] === true) {
$this->session->read(true); $this->session->read(true);
} else { } else {
$this->session->read(); $this->session->read();
@ -129,9 +141,15 @@ class Manager
} }
/** /**
* @see Manager:__construct() * Get a singleton instance of our self
**/ *
public static function getInstance($config = null, array $options = array()) * @param Zend_Config $config
* @param array $options
*
* @return self
* @see Manager:__construct
*/
public static function getInstance(Zend_Config $config = null, array $options = array())
{ {
if (self::$instance === null) { if (self::$instance === null) {
self::$instance = new Manager($config, $options); self::$instance = new Manager($config, $options);
@ -140,72 +158,139 @@ class Manager
} }
/** /**
* Clear the instance (this is mostly needed for testing and shouldn't be called otherwise) * Initialize multiple backends from Zend Config
**/ */
public static function clearInstance() private function setupBackends(Zend_Config $config)
{ {
self::$instance = null; foreach ($config as $name => $backendConfig) {
if ($backendConfig->name === null) {
$backendConfig->name = $name;
}
$backend = $this->createBackend($backendConfig);
if ($backend instanceof UserBackend) {
$this->userBackends[$backend->getName()] = $backend;
} elseif ($backend instanceof GroupBackend) {
$this->groupBackends[$backend->getName()] = $backend;
}
}
} }
/** /**
* Create a connection to the best available backend * Create a single backend from Zend Config
* *
* @param String $target "User" or "Group", depending on what * @param Zend_Config $backendConfig
* authentication information the backend should provide
* @param Mixed $backends The configuration containing all backend configurations
* in falling priority
* *
* @return (null|UserBackend|GroupBackend) * @return null|UserBackend
*/ */
private function initBestBackend($target, $backends) private function createBackend(Zend_Config $backendConfig)
{
foreach ($backends as $key => $backend) {
if (strtolower($target) === strtolower($backend->target)) {
$db = $this->tryToInitBackend($target, $backend);
if (isset($db)) {
return $db;
}
}
}
Logger::error(
'Failed to create any authentication backend '
. 'for the target "' . $target . '". Entities belonging to this target'
. ' will not be able to authenticate.'
);
return null;
}
/**
* Try to create the backend with the given configuration
*
* @param String $target "User" or "Group", depending on what
* authentication information the backend should provide
* @param $backendConfig The configuration containing backend description
*
* @return UserBackend|null Return the created backend or null
*/
private function tryToInitBackend($target, $backendConfig)
{ {
$type = ucwords(strtolower($backendConfig->backend)); $type = ucwords(strtolower($backendConfig->backend));
$target = ucwords(strtolower($backendConfig->target));
$name = $backendConfig->name;
if (!$type) { if (!$type) {
Logger::warn('Backend has no type configured. (e.g. backend=ldap)'); Logger::warn('AuthManager: Backend "%s" has no type configured. (e.g. backend=ldap)', $name);
return null; return null;
} }
if (!$target) {
Logger::warn('AuthManager: Backend "%s" has no target configured. (e.g. target=user|group)', $name);
return null;
}
try { try {
if ($backendConfig->backend === 'db') { $class = '\\Icinga\\Authentication\\Backend\\' . $type . $target . 'Backend';
$resource = DbAdapterFactory::getDbAdapter($backendConfig->resource); if (!class_exists($class)) {
Logger::error('AuthManager: Class not found (%s) for backend %s', $class, $name);
return null;
} else { } else {
$resource = $backendConfig; return new $class($backendConfig);
} }
$class = '\\Icinga\\Authentication\\Backend\\' . $type . $target. 'Backend';
return new $class($resource);
} catch (\Exception $e) { } catch (\Exception $e) {
$msg = 'Not able to create backend. Exception: ' . $e->getMessage(); Logger::warn('AuthManager: Not able to create backend. Exception was thrown: %s', $e->getMessage());
Logger::warn($msg);
return null; return null;
} }
} }
/**
* Add a user backend to stack
*
* @param UserBackend $userBackend
*/
public function addUserBackend(UserBackend $userBackend)
{
$this->userBackends[] = $userBackend;
}
/**
* Add a group backend to stack
*
* @param $groupBackend
*/
public function addGroupBackend($groupBackend)
{
$this->groupBackends[] = $groupBackend;
}
/**
* Find a backend for a credential
*
* @param Credentials $credentials
*
* @return UserBackend|null
* @throws ConfigurationError
*/
private function getBackendForCredentials(Credentials $credentials)
{
$authErrors = 0;
foreach ($this->userBackends as $userBackend) {
$flag = false;
try {
Logger::debug(
'AuthManager: Try backend %s for user %s',
$userBackend->getName(),
$credentials->getUsername()
);
$flag = $userBackend->hasUsername($credentials);
} catch (Exception $e) {
Logger::error(
'AuthManager: Backend "%s" has errors. Exception was thrown: %s',
$userBackend->getName(),
$e->getMessage()
);
$authErrors++;
continue;
}
if ($flag === true) {
Logger::debug(
'AuthManager: Backend %s has user %s',
$userBackend->getName(),
$credentials->getUsername()
);
return $userBackend;
}
}
if (count($this->userBackends) === $authErrors) {
Logger::fatal('AuthManager: No working backend found, unable to authenticate any user');
throw new ConfigurationError(
'No working backend found. Unable to authenticate any user.'
. "\n"
. 'Please examine the logs for more information.'
);
}
return null;
}
/** /**
* Try to authenticate the current user with the Credentials (@see Credentials). * Try to authenticate the current user with the Credentials (@see Credentials).
* *
@ -218,22 +303,26 @@ class Manager
*/ */
public function authenticate(Credentials $credentials, $persist = true) public function authenticate(Credentials $credentials, $persist = true)
{ {
if (!$this->userBackend) { if (count($this->userBackends) === 0) {
Logger::error("No authentication backend provided, your users will never be able to login."); Logger::error('AuthManager: No authentication backend provided, your users will never be able to login.');
throw new ConfigError( throw new ConfigError(
"No authentication backend set - login will never succeed as icinga-web ". 'No authentication backend set - login will never succeed as icinga-web '
"doesn't know how to determine your user. \n". . 'doesn\'t know how to determine your user. ' . "\n"
"To fix this error, setup your authentication.ini with a valid authentication backend." . 'To fix this error, setup your authentication.ini with at least one valid authentication backend.'
); );
}
$userBackend = $this->getBackendForCredentials($credentials);
if ($userBackend === null) {
Logger::info('AuthManager: Unknown user %s tried to log in', $credentials->getUsername());
return false; return false;
} }
if (!$this->userBackend->hasUsername($credentials)) {
Logger::info("Unknown user %s tried to log in", $credentials->getUsername()); $this->user = $userBackend->authenticate($credentials);
return false;
}
$this->user = $this->userBackend->authenticate($credentials);
if ($this->user == null) { if ($this->user == null) {
Logger::info("Invalid credentials for user %s provided", $credentials->getUsername()); Logger::info('AuthManager: Invalid credentials for user %s provided', $credentials->getUsername());
return false; return false;
} }
@ -241,6 +330,9 @@ class Manager
$this->persistCurrentUser(); $this->persistCurrentUser();
$this->session->write(); $this->session->write();
} }
Logger::info('AuthManager: User successfully logged in: %s', $credentials->getUsername());
return true; return true;
} }
@ -250,7 +342,7 @@ class Manager
**/ **/
public function persistCurrentUser() public function persistCurrentUser()
{ {
$this->session->set("user", $this->user); $this->session->set('user', $this->user);
} }
/** /**
@ -258,7 +350,7 @@ class Manager
**/ **/
public function authenticateFromSession() public function authenticateFromSession()
{ {
$this->user = $this->session->get("user", null); $this->user = $this->session->get('user', null);
} }
/** /**
@ -266,8 +358,8 @@ class Manager
* *
* @param Boolean $ignoreSession Set to true to prevent authentication by session * @param Boolean $ignoreSession Set to true to prevent authentication by session
* *
* @param Boolean * @return bool
**/ */
public function isAuthenticated($ignoreSession = false) public function isAuthenticated($ignoreSession = false)
{ {
if ($this->user === null && !$ignoreSession) { if ($this->user === null && !$ignoreSession) {
@ -296,6 +388,9 @@ class Manager
} }
/** /**
* Getter for groups belong authenticated user
*
* @return array
* @see User::getGroups * @see User::getGroups
**/ **/
public function getGroups() public function getGroups()
@ -303,6 +398,11 @@ class Manager
return $this->user->getGroups(); return $this->user->getGroups();
} }
/**
* Getter for session
*
* @return Session
*/
public function getSession() public function getSession()
{ {
return $this->session; return $this->session;

View File

@ -28,7 +28,8 @@
namespace Icinga\Authentication; namespace Icinga\Authentication;
use Icinga\Application\Logger as Logger; use Icinga\Application\Logger;
use \Icinga\Exception\ConfigurationError;
/** /**
* Class PhpSession * Class PhpSession
@ -44,11 +45,33 @@ use Icinga\Application\Logger as Logger;
*/ */
class PhpSession extends Session class PhpSession extends Session
{ {
const SESSION_NAME = "Icinga2Web"; /**
* Name of the session
*
* @var string
*/
const SESSION_NAME = 'Icinga2Web';
/**
* Flag if session is open
*
* @var bool
*/
private $isOpen = false; private $isOpen = false;
/**
* Flag if session is flushed
*
* @var bool
*/
private $isFlushed = false; private $isFlushed = false;
private static $DEFAULT_COOKIEOPTIONS = array( /**
* Configuration for cookie options
*
* @var array
*/
private static $defaultCookieOptions = array(
'use_trans_sid' => false, 'use_trans_sid' => false,
'use_cookies' => true, 'use_cookies' => true,
'cookie_httponly' => true, 'cookie_httponly' => true,
@ -60,39 +83,41 @@ class PhpSession extends Session
/** /**
* Creates a new PHPSession object using the provided options (if any) * Creates a new PHPSession object using the provided options (if any)
* *
* @param Array $options An optional array of ini options to set, * @param array $options An optional array of ini options to set,
*
* @throws ConfigurationError
* @see http://php.net/manual/en/session.configuration.php * @see http://php.net/manual/en/session.configuration.php
**/ */
public function __construct(array $options = null) public function __construct(array $options = null)
{ {
if ($options !== null) { if ($options !== null) {
$options = array_merge(PhpSession::$DEFAULT_COOKIEOPTIONS, $options); $options = array_merge(PhpSession::$defaultCookieOptions, $options);
} else { } else {
$options = PhpSession::$DEFAULT_COOKIEOPTIONS; $options = PhpSession::$defaultCookieOptions;
} }
foreach ($options as $sessionVar => $value) { foreach ($options as $sessionVar => $value) {
if (ini_set("session.".$sessionVar, $value) === false) { if (ini_set("session.".$sessionVar, $value) === false) {
Logger::warn( Logger::warn(
"Could not set php.ini setting %s = %s. This might affect your sessions behaviour.", 'Could not set php.ini setting %s = %s. This might affect your sessions behaviour.',
$sessionVar, $sessionVar,
$value $value
); );
} }
} }
if (!is_writable(session_save_path())) { if (!is_writable(session_save_path())) {
throw new \Icinga\Exception\ConfigurationError("Can't save session"); throw new ConfigurationError('Can\'t save session');
} }
} }
/** /**
* Returns true when the session has not yet been closed * Returns true when the session has not yet been closed
* *
* @return Boolean * @return bool
**/ */
private function sessionCanBeChanged() private function sessionCanBeChanged()
{ {
if ($this->isFlushed) { if ($this->isFlushed) {
Logger::error("Tried to work on a closed session, session changes will be ignored"); Logger::error('Tried to work on a closed session, session changes will be ignored');
return false; return false;
} }
return true; return true;
@ -101,12 +126,12 @@ class PhpSession extends Session
/** /**
* Returns true when the session has not yet been opened * Returns true when the session has not yet been opened
* *
* @return Boolean * @return bool
**/ */
private function sessionCanBeOpened() private function sessionCanBeOpened()
{ {
if ($this->isOpen) { if ($this->isOpen) {
Logger::warn("Tried to open a session more than once"); Logger::warn('Tried to open a session more than once');
return false; return false;
} }
return $this->sessionCanBeChanged(); return $this->sessionCanBeChanged();
@ -115,15 +140,15 @@ class PhpSession extends Session
/** /**
* Opens a PHP session when possible * Opens a PHP session when possible
* *
* @return Boolean True on success * @return bool True on success
**/ */
public function open() public function open()
{ {
if (!$this->sessionCanBeOpened()) { if (!$this->sessionCanBeOpened()) {
return false; return false;
} }
session_name(PhpSession::SESSION_NAME); session_name(self::SESSION_NAME);
session_start(); session_start();
$this->isOpen = true; $this->isOpen = true;
$this->setAll($_SESSION); $this->setAll($_SESSION);
@ -131,10 +156,10 @@ class PhpSession extends Session
} }
/** /**
* Ensures that the session is open modifyable * Ensures that the session is open modifiable
* *
* @return Boolean True on success * @return bool True on success
**/ */
private function ensureOpen() private function ensureOpen()
{ {
// try to open first // try to open first
@ -147,14 +172,14 @@ class PhpSession extends Session
} }
/** /**
* Reads all values written to the underyling session and * Reads all values written to the underling session and
* makes them accessible. if keepOpen is not set, the session * makes them accessible. if keepOpen is not set, the session
* is immediately closed again * is immediately closed again
* *
* @param Boolean $keepOpen Set to true when modifying the session * @param bool $keepOpen Set to true when modifying the session
* *
* @return Boolean True on success * @return bool True on success
**/ */
public function read($keepOpen = false) public function read($keepOpen = false)
{ {
if (!$this->ensureOpen()) { if (!$this->ensureOpen()) {
@ -168,13 +193,14 @@ class PhpSession extends Session
} }
/** /**
* Writes all values of this session opbject to the underyling session implementation * Writes all values of this session object to the underlying session implementation
*
* If keepOpen is not set, the session is closed * If keepOpen is not set, the session is closed
* *
* @param Boolean $keepOpen Set to true when modifying the session further * @param bool $keepOpen Set to true when modifying the session further
* *
* @return Boolean True on success * @return bool True on success
**/ */
public function write($keepOpen = false) public function write($keepOpen = false)
{ {
if (!$this->ensureOpen()) { if (!$this->ensureOpen()) {
@ -192,10 +218,12 @@ class PhpSession extends Session
} }
/** /**
* Closes and writes the session. Call @see PHPSession::write in order to persist changes * Closes and writes the session
* and only call this if you want the session to be closed without any changes
* *
**/ * Only call this if you want the session to be closed without any changes.
*
* @see PHPSession::write
*/
public function close() public function close()
{ {
if (!$this->isFlushed) { if (!$this->isFlushed) {
@ -206,8 +234,7 @@ class PhpSession extends Session
/** /**
* Deletes the current session, causing all session information to be lost * Deletes the current session, causing all session information to be lost
* */
**/
public function purge() public function purge()
{ {
if ($this->ensureOpen()) { if ($this->ensureOpen()) {
@ -220,23 +247,21 @@ class PhpSession extends Session
/** /**
* Removes session cookies * Removes session cookies
* */
**/
private function clearCookies() private function clearCookies()
{ {
if (ini_get("session.use_cookies")) { if (ini_get('session.use_cookies')) {
Logger::debug("Clearing cookies"); Logger::debug('Clear session cookie');
$params = session_get_cookie_params(); $params = session_get_cookie_params();
setcookie( setcookie(
session_name(), session_name(),
'', '',
time() - 42000, time() - 42000,
$params["path"], $params['path'],
$params["domain"], $params['domain'],
$params["secure"], $params['secure'],
$params["httponly"] $params['httponly']
); );
} }
} }
} }

View File

@ -29,58 +29,71 @@
namespace Icinga\Authentication; namespace Icinga\Authentication;
/** /**
* Base class for session, providing getter, setters and required * Base class for handling sessions
* interface methods */
*
**/
abstract class Session abstract class Session
{ {
/**
* Container for session values
*
* @var array
*/
private $sessionValues = array(); private $sessionValues = array();
/** /**
* Opens a session or creates a new one if not exists * Opens a session or creates a new one if not exists
* */
**/
abstract public function open(); abstract public function open();
/** /**
* Reads all values from the underyling session implementation * Reads all values from the underlying session implementation
* *
* @param Boolean $keepOpen True to keep the session open (depends on implementaiton) * @param bool $keepOpen True to keep the session open
**/ */
abstract public function read($keepOpen = false); abstract public function read($keepOpen = false);
/** /**
* Persists changes to the underlying session implementation * Persists changes to the underlying session implementation
* *
* @param Boolean $keepOpen True to keep the session open (depends on implementaiton) * @param bool $keepOpen True to keep the session open
**/ */
abstract public function write($keepOpen = false); abstract public function write($keepOpen = false);
/**
* Close session
*/
abstract public function close(); abstract public function close();
/**
* Purge session
*/
abstract public function purge(); abstract public function purge();
/** /**
* Sets a $value under the provided key in the internal session data array * Setter for session values
* Does not persist those changes, use @see Session::write in order to persist the changes
* made here.
* *
* @param String $key * You have to persist values manually
* @param mixed $value *
**/ * @see self::persist
* @param string $key Name of value
* @param mixed $value Value
*/
public function set($key, $value) public function set($key, $value)
{ {
$this->sessionValues[$key] = $value; $this->sessionValues[$key] = $value;
} }
/** /**
* Returns the session value stored under $key or $defaultValue if not found. * Getter fpr session values
* call @see Session:read in order to populate this array with the underyling session implementation
* *
* @param String $key * Values are available after populate session with method read.
*
* @param string $key
* @param mixed $defaultValue * @param mixed $defaultValue
* *
* @return mixed * @return mixed
**/ * @see self::read
*/
public function get($key, $defaultValue = null) public function get($key, $defaultValue = null)
{ {
return isset($this->sessionValues[$key]) ? return isset($this->sessionValues[$key]) ?
@ -88,22 +101,23 @@ abstract class Session
} }
/** /**
* Returns the current session value state (also dirty changes not yet written to the session) * Getter for all session values
* *
* @return Array * This are also dirty, unwritten values.
**/ *
* @return array
*/
public function getAll() public function getAll()
{ {
return $this->sessionValues; return $this->sessionValues;
} }
/** /**
* Writes all values provided in the key=>value array to the internal session value state. * Put an array into session
* In order to persist these chages, call @see Session:write
* *
* @param Array $values * @param array $values
* @param Boolean $overwrite Whether to overwrite already set values * @param bool $overwrite Overwrite existing values
**/ */
public function setAll(array $values, $overwrite = false) public function setAll(array $values, $overwrite = false)
{ {
if ($overwrite) { if ($overwrite) {
@ -119,8 +133,7 @@ abstract class Session
/** /**
* Clears all values from the session cache * Clears all values from the session cache
* */
**/
public function clear() public function clear()
{ {
$this->sessionValues = array(); $this->sessionValues = array();

View File

@ -28,23 +28,27 @@
namespace Icinga\Authentication; namespace Icinga\Authentication;
use \Zend_Config;
use \Icinga\User;
/** /**
* Interface for backends that authenticate users * Public api for an user backend object
*/ */
interface UserBackend interface UserBackend
{ {
/** /**
* Create a userbackend from the given configuration or resource * Creates a new object
* *
* @param $config * @param Zend_Config $config
*/ */
public function __construct($config); public function __construct(Zend_Config $config);
/** /**
* Test if the username exists * Test if the username exists
* *
* @param Credentials $credentials * @param Credentials $credentials
* @return boolean *
* @return bool
*/ */
public function hasUsername(Credentials $credentials); public function hasUsername(Credentials $credentials);
@ -52,10 +56,18 @@ interface UserBackend
* Authenticate * Authenticate
* *
* @param Credentials $credentials * @param Credentials $credentials
*
* @return User * @return User
*/ */
public function authenticate(Credentials $credentials); public function authenticate(Credentials $credentials);
/**
* Name of the backend
*
* @return string
*/
public function getName();
/** /**
* Get the number of users available through this backend * Get the number of users available through this backend
* *