Auth: Connect only when a authentication backend is used. Fix log in error messages

refs #5506
refs #5638
fixes #4931
This commit is contained in:
Eric Lippmann 2014-02-18 09:33:33 +01:00
parent 35fc451115
commit 69a482d106
3 changed files with 98 additions and 150 deletions

View File

@ -70,7 +70,7 @@ class AuthenticationController extends ActionController
); );
if (!$auth->authenticate($credentials)) { if (!$auth->authenticate($credentials)) {
$this->view->form->getElement('password') $this->view->form->getElement('password')
->addError(t('Please provide a valid username and password')); ->addError(t('Incorrect username or password'));
} else { } else {
$this->redirectNow($redirectUrl); $this->redirectNow($redirectUrl);
} }

View File

@ -1,21 +1,15 @@
; authentication.ini ; authentication.ini
; ;
; Each section listed in this configuration represents a single backend ; Each section listed in this configuration represents a backend used to authenticate users. The backend configurations
; that can be used to authenticate users or groups. Each databse backend must refer : must define a resource referring to a configured database or LDAP connection in the INI file resources.ini.
; to a resource defined in resources.ini,
; ;
; The order of entries in this configuration is used to determine the fallback ; The backends will be processed from top to bottom using the first backend for authentication which reports that
; priority in case of an error. If the resource referenced in the first ; the user trying to log in is available.
; entry is not reachable, the next lower entry will be used for authentication.
; Please be aware that this behaviour is not valid for the authentication itself.
; The authentication will only be done against the one available resource with the highest
; priority.
[internal_ldap_authentication] [internal_ldap_authentication]
@ldap_auth_disabled@ @ldap_auth_disabled@
backend = ldap backend = ldap
target = user
resource = internal_ldap resource = internal_ldap
; Object class of the user ; Object class of the user
user_class = @ldap_user_objectclass@ user_class = @ldap_user_objectclass@
@ -25,5 +19,4 @@ user_name_attribute = @ldap_attribute_username@
[internal_db_authentication] [internal_db_authentication]
@internal_auth_disabled@ @internal_auth_disabled@
backend = db backend = db
target = "user"
resource = "internal_db" resource = "internal_db"

View File

@ -50,8 +50,6 @@ use Icinga\Exception\NotReadableError;
* Direct instantiation is not permitted, the AuthenticationManager * 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.
*
* @TODO(mh): Group support is not implemented yet (#4624)
**/ **/
class Manager class Manager
{ {
@ -76,13 +74,6 @@ class Manager
**/ **/
private $userBackends = array(); private $userBackends = array();
/**
* Array of group backends
*
* @var array
**/
private $groupBackends = array();
/** /**
* The configuration * The configuration
* *
@ -125,64 +116,55 @@ class Manager
private function setupBackends(Zend_Config $config) private function setupBackends(Zend_Config $config)
{ {
foreach ($config as $name => $backendConfig) { foreach ($config as $name => $backendConfig) {
// We won't initialize disabled backends if ((bool) $backendConfig->get('disabled', false) === true) {
if ($backendConfig->get('disabled') == '1') {
continue; continue;
} }
if ($backendConfig->name === null) { if ($backendConfig->name === null) {
$backendConfig->name = $name; $backendConfig->name = $name;
} }
$backend = $this->createBackend($backendConfig); $backend = $this->createBackend($backendConfig);
if ($backend instanceof UserBackend) { $this->userBackends[$backend->getName()] = $backend;
$backend->connect();
$this->userBackends[$backend->getName()] = $backend;
} elseif ($backend instanceof GroupBackend) {
$backend->connect();
$this->groupBackends[$backend->getName()] = $backend;
}
} }
} }
/** /**
* Create a single backend from the given Zend_Config * Create a backend from the given Zend_Config
* *
* @param Zend_Config $backendConfig * @param Zend_Config $backendConfig
* *
* @return null|UserBackend * @return UserBackend
* @throws ConfigurationError
*/ */
private function createBackend(Zend_Config $backendConfig) private function createBackend(Zend_Config $backendConfig)
{ {
$target = ucwords(strtolower($backendConfig->target)); if (isset($backendConfig->class)) {
$name = $backendConfig->name; // Use a custom backend class, this is only useful for testing
// TODO: implement support for groups (#4624) and remove OR-Clause if (!class_exists($backendConfig->class)) {
if ((!$target || strtolower($target) != "user") && !$backendConfig->class) { throw new ConfigurationError(
Logger::warn('AuthManager: Backend "%s" has no target configuration. (e.g. target=user|group)', $name); 'Authentication configuration for backend "' . $backendConfig->name . '" defines an invalid backend'
return null; . ' class. Backend class "' . $backendConfig->class. '" not found'
} );
try {
if (isset($backendConfig->class)) {
// use a custom backend class, this is probably only useful for testing
if (!class_exists($backendConfig->class)) {
Logger::error('AuthManager: Class not found (%s) for backend %s', $backendConfig->class, $name);
return null;
}
$class = $backendConfig->class;
return new $class($backendConfig);
} }
return new $backendConfig->class($backendConfig);
switch (ResourceFactory::getResourceConfig($backendConfig->resource)->type) { }
case 'db': if (($type = ResourceFactory::getResourceConfig($backendConfig->resource)->type) === null) {
return new DbUserBackend($backendConfig); throw new ConfigurationError(
case 'ldap': 'Authentication configuration for backend "%s" is missing the type directive',
return new LdapUserBackend($backendConfig); $backendConfig->name,
default: $backendConfig->class
Logger::warn('AuthManager: Resource type ' . $backendConfig->type . ' not available.'); );
} }
} catch (Exception $e) { switch (strtolower($type)) {
Logger::warn('AuthManager: Not able to create backend. Exception was thrown: %s', $e->getMessage()); case 'db':
return new DbUserBackend($backendConfig);
case 'ldap':
return new LdapUserBackend($backendConfig);
default:
throw new ConfigurationError(
'Authentication configuration for backend "' . $backendConfig->name. '" defines an invalid backend'
. ' type. Backend type "' . $type . '" is not supported'
);
} }
return null;
} }
/** /**
@ -208,79 +190,65 @@ class Manager
} }
/** /**
* Add a group backend to the stack * Find the backend which provides the user with the given credentials
* *
* @param GroupBackend $groupBackend * @param Credential $credentials
*/
public function addGroupBackend(GroupBackend $groupBackend)
{
$this->groupBackends[$groupBackend->getName()] = $groupBackend;
}
/**
* Get a group backend by name
*
* @param string $name
*
* @return GroupBackend|null
*/
public function getGroupBackend($name)
{
return (isset($this->groupBackends[$name])) ? $this->groupBackends[$name] : null;
}
/**
* Find a backend for the given credentials
*
* @param Credential $credentials
* *
* @return UserBackend|null * @return UserBackend|null
* @throws ConfigurationError * @throws ConfigurationError
*/ */
private function getBackendForCredential(Credential $credentials) private function revealBackend(Credential $credentials)
{ {
$authErrors = 0; if (count($this->userBackends) === 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 ($authErrors >= count($this->userBackends)) {
Logger::fatal('AuthManager: No working backend found, unable to authenticate any user');
throw new ConfigurationError( throw new ConfigurationError(
'No working backend found. Unable to authenticate any user.' . 'No authentication methods available. It seems that none authentication method has been set up. '
"\nPlease examine the logs for more information." . ' Please contact your Icinga Web administrator'
);
}
$backendsWithError = 0;
// TODO(el): Currently the user is only notified about authentication backend problems when all backends
// have errors. It may be the case that the authentication backend which provides the user has errors but other
// authentication backends work. In that scenario the user is presented an error message saying "Incorrect
// username or password". We must inform the user that not all authentication methods are available.
foreach ($this->userBackends as $backend) {
Logger::debug(
'Asking authentication backend "%s" for user "%s"',
$backend->getName(),
$credentials->getUsername()
);
try {
$hasUser = $backend->hasUsername($credentials);
} catch (Exception $e) {
Logger::error(
'Cannot ask authentication backend "%s" for user "%s". An exception was thrown: %s',
$backend->getName(),
$credentials->getUsername(),
$e->getMessage()
);
++$backendsWithError;
continue;
}
if ($hasUser === true) {
Logger::debug(
'Authentication backend "%s" provides user "%s"',
$backend->getName(),
$credentials->getUsername()
);
return $backend;
} else {
Logger::debug(
'Authentication backend "%s" does not provide user "%s"',
$backend->getName(),
$credentials->getUsername()
);
}
}
if ($backendsWithError === count($this->userBackends)) {
throw new ConfigurationError(
'No authentication methods available. It seems that all set up authentication methods have errors. '
. ' Please contact your Icinga Web administrator'
); );
} }
return null; return null;
} }
@ -295,26 +263,13 @@ class Manager
*/ */
public function authenticate(Credential $credentials, $persist = true) public function authenticate(Credential $credentials, $persist = true)
{ {
if (count($this->userBackends) === 0) { $userBackend = $this->revealBackend($credentials);
Logger::error('AuthManager: No authentication backend provided, your users will never be able to login.');
throw new ConfigurationError(
'No authentication backend set - login will never succeed as icinga-web'
. ' doesn\'t know how to determine your user. ' . "\n"
. ' To fix this error, setup your authentication.ini with at least one valid authentication backend.'
);
}
$userBackend = $this->getBackendForCredential($credentials);
if ($userBackend === null) { if ($userBackend === null) {
Logger::info('AuthManager: Unknown user %s tried to log in', $credentials->getUsername()); Logger::info('Unknown user "%s" tried to log in', $credentials->getUsername());
return false; return false;
} }
if (($user = $userBackend->authenticate($credentials)) === null) {
$this->user = $userBackend->authenticate($credentials); Logger::info('User "%s" tried to log in with an incorrect password', $credentials->getUsername());
if ($this->user === null) {
Logger::info('AuthManager: Invalid credentials for user %s provided', $credentials->getUsername());
return false; return false;
} }
@ -323,15 +278,15 @@ class Manager
$membership = new Membership(); $membership = new Membership();
$groups = $membership->getGroupsByUsername($username); $groups = $membership->getGroupsByUsername($username);
$this->user->setGroups($groups); $user->setGroups($groups);
$admissionLoader = new AdmissionLoader(); $admissionLoader = new AdmissionLoader();
$this->user->setPermissions( $user->setPermissions(
$admissionLoader->getPermissions($username, $groups) $admissionLoader->getPermissions($username, $groups)
); );
$this->user->setRestrictions( $user->setRestrictions(
$admissionLoader->getRestrictions($username, $groups) $admissionLoader->getRestrictions($username, $groups)
); );
@ -339,7 +294,7 @@ class Manager
try { try {
$preferencesStore = PreferencesStore::create( $preferencesStore = PreferencesStore::create(
$preferencesConfig, $preferencesConfig,
$this->user $user
); );
$preferences = new Preferences($preferencesStore->load()); $preferences = new Preferences($preferencesStore->load());
} catch (NotReadableError $e) { } catch (NotReadableError $e) {
@ -349,13 +304,13 @@ class Manager
} else { } else {
$preferences = new Preferences(); $preferences = new Preferences();
} }
$this->user->setPreferences($preferences); $user->setPreferences($preferences);
$this->user = $user;
if ($persist == true) { if ($persist == true) {
$this->persistCurrentUser(); $this->persistCurrentUser();
} }
Logger::info('AuthManager: User successfully logged in: %s', $credentials->getUsername()); Logger::info('User "%s" logged in successfully', $credentials->getUsername());
return true; return true;
} }