Merge branch 'feature/improve-autologin-setup-8274'

refs #8274
resolves #8134
fixes #7848
This commit is contained in:
Johannes Meyer 2015-01-28 18:04:55 +01:00
commit ab95ae622c
17 changed files with 171 additions and 120 deletions

View File

@ -1,5 +1,5 @@
[autologin] [autologin]
backend = autologin backend = external
[icingaweb-mysql] [icingaweb-mysql]
backend = db backend = db

View File

@ -8,7 +8,7 @@ use Icinga\Application\Config;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
use Icinga\Application\Logger; use Icinga\Application\Logger;
use Icinga\Authentication\AuthChain; use Icinga\Authentication\AuthChain;
use Icinga\Authentication\Backend\AutoLoginBackend; use Icinga\Authentication\Backend\ExternalBackend;
use Icinga\Exception\AuthenticationException; use Icinga\Exception\AuthenticationException;
use Icinga\Exception\ConfigurationError; use Icinga\Exception\ConfigurationError;
use Icinga\Exception\NotReadableError; use Icinga\Exception\NotReadableError;
@ -39,6 +39,7 @@ class AuthenticationController extends ActionController
$this->redirectNow(Url::fromPath('setup')); $this->redirectNow(Url::fromPath('setup'));
} }
$triedOnlyExternalAuth = null;
$auth = $this->Auth(); $auth = $this->Auth();
$this->view->form = $form = new LoginForm(); $this->view->form = $form = new LoginForm();
$this->view->title = $this->translate('Icingaweb Login'); $this->view->title = $this->translate('Icingaweb Login');
@ -82,7 +83,7 @@ class AuthenticationController extends ActionController
} }
foreach ($chain as $backend) { foreach ($chain as $backend) {
if ($backend instanceof AutoLoginBackend) { if ($backend instanceof ExternalBackend) {
continue; continue;
} }
++$backendsTried; ++$backendsTried;
@ -126,7 +127,8 @@ class AuthenticationController extends ActionController
} elseif ($request->isGet()) { } elseif ($request->isGet()) {
$user = new User(''); $user = new User('');
foreach ($chain as $backend) { foreach ($chain as $backend) {
if ($backend instanceof AutoLoginBackend) { $triedOnlyExternalAuth = $triedOnlyExternalAuth === null;
if ($backend instanceof ExternalBackend) {
$authenticated = $backend->authenticate($user); $authenticated = $backend->authenticate($user);
if ($authenticated === true) { if ($authenticated === true) {
$auth->setAuthenticated($user); $auth->setAuthenticated($user);
@ -134,6 +136,8 @@ class AuthenticationController extends ActionController
Url::fromPath(Url::fromRequest()->getParam('redirect', 'dashboard')) Url::fromPath(Url::fromRequest()->getParam('redirect', 'dashboard'))
); );
} }
} else {
$triedOnlyExternalAuth = false;
} }
} }
} }
@ -141,6 +145,7 @@ class AuthenticationController extends ActionController
$this->view->errorInfo = $e->getMessage(); $this->view->errorInfo = $e->getMessage();
} }
$this->view->requiresExternalAuth = $triedOnlyExternalAuth && !$auth->isAuthenticated();
$this->view->requiresSetup = Icinga::app()->requiresSetup(); $this->view->requiresSetup = Icinga::app()->requiresSetup();
} }

View File

@ -8,16 +8,16 @@ use Zend_Validate_Callback;
use Icinga\Web\Form; use Icinga\Web\Form;
/** /**
* Form class for adding/modifying autologin authentication backends * Form class for adding/modifying authentication backends of type "external"
*/ */
class AutologinBackendForm extends Form class ExternalBackendForm extends Form
{ {
/** /**
* Initialize this form * Initialize this form
*/ */
public function init() public function init()
{ {
$this->setName('form_config_authbackend_autologin'); $this->setName('form_config_authbackend_external');
} }
/** /**
@ -69,7 +69,7 @@ class AutologinBackendForm extends Form
'backend', 'backend',
array( array(
'disabled' => true, 'disabled' => true,
'value' => 'autologin' 'value' => 'external'
) )
); );
@ -79,7 +79,7 @@ class AutologinBackendForm extends Form
/** /**
* Validate the configuration by creating a backend and requesting the user count * Validate the configuration by creating a backend and requesting the user count
* *
* Returns always true as autologin backends are just "passive" backends. (The webserver authenticates users.) * Returns always true as backends of type "external" are just "passive" backends.
* *
* @param Form $form The form to fetch the configuration values from * @param Form $form The form to fetch the configuration values from
* *

View File

@ -14,7 +14,7 @@ use Icinga\Data\ResourceFactory;
use Icinga\Exception\ConfigurationError; use Icinga\Exception\ConfigurationError;
use Icinga\Forms\Config\Authentication\DbBackendForm; use Icinga\Forms\Config\Authentication\DbBackendForm;
use Icinga\Forms\Config\Authentication\LdapBackendForm; use Icinga\Forms\Config\Authentication\LdapBackendForm;
use Icinga\Forms\Config\Authentication\AutologinBackendForm; use Icinga\Forms\Config\Authentication\ExternalBackendForm;
class AuthenticationBackendConfigForm extends ConfigForm class AuthenticationBackendConfigForm extends ConfigForm
{ {
@ -67,8 +67,8 @@ class AuthenticationBackendConfigForm extends ConfigForm
} elseif ($type === 'ldap') { } elseif ($type === 'ldap') {
$form = new LdapBackendForm(); $form = new LdapBackendForm();
$form->setResources(isset($this->resources['ldap']) ? $this->resources['ldap'] : array()); $form->setResources(isset($this->resources['ldap']) ? $this->resources['ldap'] : array());
} elseif ($type === 'autologin') { } elseif ($type === 'external') {
$form = new AutologinBackendForm(); $form = new ExternalBackendForm();
} else { } else {
throw new InvalidArgumentException(sprintf($this->translate('Invalid backend type "%s" provided'), $type)); throw new InvalidArgumentException(sprintf($this->translate('Invalid backend type "%s" provided'), $type));
} }
@ -251,14 +251,14 @@ class AuthenticationBackendConfigForm extends ConfigForm
$configValues['name'] = $authBackend; $configValues['name'] = $authBackend;
$this->populate($configValues); $this->populate($configValues);
} elseif (empty($this->resources)) { } elseif (empty($this->resources)) {
$autologinBackends = array_filter( $externalBackends = array_filter(
$this->config->toArray(), $this->config->toArray(),
function ($authBackendCfg) { function ($authBackendCfg) {
return isset($authBackendCfg['backend']) && $authBackendCfg['backend'] === 'autologin'; return isset($authBackendCfg['backend']) && $authBackendCfg['backend'] === 'external';
} }
); );
if (false === empty($autologinBackends)) { if (false === empty($externalBackends)) {
throw new ConfigurationError($this->translate('Could not find any resources for authentication')); throw new ConfigurationError($this->translate('Could not find any resources for authentication'));
} }
} }
@ -299,14 +299,14 @@ class AuthenticationBackendConfigForm extends ConfigForm
$backendTypes['ldap'] = 'LDAP'; $backendTypes['ldap'] = 'LDAP';
} }
$autologinBackends = array_filter( $externalBackends = array_filter(
$this->config->toArray(), $this->config->toArray(),
function ($authBackendCfg) { function ($authBackendCfg) {
return isset($authBackendCfg['backend']) && $authBackendCfg['backend'] === 'autologin'; return isset($authBackendCfg['backend']) && $authBackendCfg['backend'] === 'external';
} }
); );
if ($backendType === 'autologin' || empty($autologinBackends)) { if ($backendType === 'external' || empty($externalBackends)) {
$backendTypes['autologin'] = $this->translate('Autologin'); $backendTypes['external'] = $this->translate('External');
} }
if ($backendType === null) { if ($backendType === null) {

View File

@ -1,11 +1,28 @@
<div id="login"> <div id="login">
<div class="logo"> <div class="logo">
<div class="image"> <div class="image">
<img class="fade-in one" src="<?= $this->baseUrl('img/logo_icinga_big.png') ?>" alt="<?= t('The Icinga logo') ?>" > <img class="fade-in one" src="<?= $this->baseUrl('img/logo_icinga_big.png'); ?>" alt="<?= $this->translate('The Icinga logo'); ?>" >
</div> </div>
</div> </div>
<div class="form" data-base-target="layout"> <div class="form" data-base-target="layout">
<h1>Welcome to Icinga Web 2</h1> <?php if ($requiresSetup): ?>
<p class="config-note"><?= sprintf(
$this->translate(
'It appears that you did not configure Icinga Web 2 yet so it\'s not possible to log in without any defined '
. 'authentication method. Please define a authentication method by following the instructions in the'
. ' %1$sdocumentation%3$s or by using our %2$sweb-based setup-wizard%3$s.'
),
'<a href="http://docs.icinga.org/" title="' . $this->translate('Icinga Web 2 Documentation') . '">', // TODO: More exact documentation link..
'<a href="' . $this->href('setup') . '" title="' . $this->translate('Icinga Web 2 Setup-Wizard') . '">',
'</a>'
); ?></p>
<?php elseif ($requiresExternalAuth): ?>
<p class="info-box"><span class="icon-info"></span><?= $this->translate(
'You\'re currently not authenticated using any of the web server\'s authentication mechanisms.'
. ' Make sure you\'ll configure such, otherwise you\'ll not be able to login.'
); ?></p>
<?php endif ?>
<h1><?= $this->translate('Welcome to Icinga Web 2'); ?></h1>
<?php <?php
/* TODO: remove this as soon as notifications and forms are ready */ /* TODO: remove this as soon as notifications and forms are ready */
if (isset($this->errorInfo)): ?> if (isset($this->errorInfo)): ?>
@ -14,18 +31,6 @@
</div> </div>
<?php endif ?> <?php endif ?>
<?= $this->form ?> <?= $this->form ?>
<div class="footer">Icinga Web 2 &copy; 2013-2015<br><a href="https://www.icinga.org">The Icinga Project</a></div> <div class="footer">Icinga Web 2 &copy; 2013-2015<br><a href="https://www.icinga.org"><?= $this->translate('The Icinga Project'); ?></a></div>
<?php if ($requiresSetup): ?>
<div class="config-note"><?= sprintf(
t(
'It appears that you did not configure Icinga Web 2 yet so it\'s not possible to log in without any defined '
. 'authentication method. Please define a authentication method by following the instructions in the'
. ' %1$sdocumentation%3$s or by using our %2$sweb-based setup-wizard%3$s.'
),
'<a href="http://docs.icinga.org/" title="Icinga Web 2 Documentation">', // TODO: Documentation link
'<a href="' . $this->href('setup') . '" title="Icinga Web 2 Setup-Wizard">',
'</a>'
); ?></div>
<?php endif ?>
</div> </div>
</div> </div>

View File

@ -24,7 +24,7 @@ For delegating authentication to the web server simply add `autologin` to your a
```` ````
[autologin] [autologin]
backend = autologin backend = external
```` ````
If your web server is not configured for authentication though the `autologin` section has no effect. If your web server is not configured for authentication though the `autologin` section has no effect.

View File

@ -1,90 +1,83 @@
# Externel Authentication # External Authentication
It is possible to use the authentication mechanism of the webserver, It is possible to utilize the authentication mechanism of the webserver instead
instead of using the internal authentication-manager to of the internal authentication of Icinga Web 2 to authenticate users. This might
authenticate users. This might be useful if you only have very few users, and be useful if you only have very few users and user management over **.htaccess**
user management over *.htaccess* is sufficient, or if you must use some other is not sufficient or if you are required to use some other authentication
authentication mechanism that is only available through your webserver. mechanism that is only available by utilizing the webserver.
When external authentication is used, Icingaweb will entrust the Icinga Web 2 will entrust the complete authentication process to the
complete authentication process to the external authentication provider (the webserver): authentication provider of the webserver, if external authentication is used.
The provider should take care of authenticating the user and declining So it is very important that the webserver's authentication is configured
all requests with invalid or missing credentials. When the authentication correctly as wrong configuration might lead to unauthorized access or a
was succesful, it should provide the authenticated users name to its php-module malfunction in the login-process.
and Icingaweb will assume that the user is authorized to access the page.
Because of this it is very important that the webservers authentication is
configured correctly, as wrong configuration could lead to unauthorized
access to the site, or a broken login-process.
## Using External Authentication
## Use External Authentication External authentication in Icinga Web 2 requires the following preparations:
Using external authentication in Icingaweb requires two steps to work: 1. The external authentication must be set up properly to correctly
authenticate users
2. Icinga Web 2 must be configured to use external authentication
1. The external authentication must be set up correctly to always ### Preparing the External Authentication Provider
authenticate the users.
2. Icingaweb must be configured to use the external authentication.
This step depends heavily on the used webserver and authentication mechanism you
want to use. It is not possible to cover all possibillities and you should
probably read the documentation for your webserver to get detailed instructions
on how to set up authentication properly.
### Prepare the External Authentication Provider In general you need to make sure that:
This step depends heavily on the used webserver and authentication
mechanism you want to use. It is not possible to cover all possibillities
and you should probably read the documentation for your webserver for
detailed instructions on how to set up authentication properly.
In general, you need to make sure that:
- All routes require authentication
- Only permitted users are allowed to authenticate
- All routes require authentication
- Only permitted users are allowed to authenticate
#### Example Configuration for Apache and HTTPDigestAuthentication #### Example Configuration for Apache and HTTPDigestAuthentication
The following example will show how to enable external authentication in Apache using The following example will show how to enable external authentication in Apache
*HTTP Digest Authentication*. using *HTTP Digest Authentication*.
##### Create users ##### Creating users
To create users for a digest authentication we can use the tool *htdigest*. To create users for digest authentication you can use the tool *htdigest*. In
We choose *.icingawebdigest* as a name for the created file, containing this example **.icingawebdigest** is the name of the file containing the user
the user credentials. credentials.
This command will create a new file with the user *jdoe*. *htdigest* This command creates a new file with the user *jdoe*. *htdigest* will prompt
will prompt you for your password, after it has been executed. If you you for a password. If you want to add more users to the file you need to omit
want to add more users to the file you need to ommit the *-c* parameter the *-c* parameter in all following commands to not to overwrite the file.
in all further commands to avoInid the file to be overwritten.
````
sudo htdigest -c /etc/icingaweb2/.icingawebdigest "Icinga Web 2" jdoe
````
sudo htdigest -c /etc/httpd/conf.d/.icingawebdigest "Icingaweb 2" jdoe ##### Configuring the Webserver
The webserver should require authentication for all public Icinga Web 2 files.
##### Set up authentication ````
<Directory "/usr/share/icingaweb2/public">
AuthType digest
AuthName "Icinga Web 2"
AuthDigestProvider file
AuthUserFile /etc/icingaweb2/.icingawebdigest
Require valid-user
</Directory>
````
The webserver should require authentication for all public icingaweb files. ### Preparing Icinga Web 2
Once external authentication is set up correctly you need to configure Icinga
Web 2. In case you already completed the setup wizard it is likely that you are
now finished.
<Directory "/var/www/html/icingaweb"> To get Icinga Web 2 to use external authentication the file
AuthType digest **config/authentication.ini** is required. Just add the following section
AuthName "Icingaweb 2" called "autologin", or any name of your choice, and save your changes:
AuthDigestProvider file
AuthUserFile /etc/httpd/conf.d/.icingawebdigest
Require valid-user
</Directory>
````
### Prepare Icingaweb [autologin]
backend = external
````
When the external authentication is set up correctly, we need Congratulations! You are now logged in when visiting Icinga Web 2.
to configure IcingaWeb to use it as an authentication source. The
configuration key *authenticationMode* in the section *global* defines
if the authentication should be handled internally or externally. Since
we want to delegate the authentication to the Webserver we choose
"external" as the new value:
[global]
; ...
authenticationMode = "external"
; ...

View File

@ -107,4 +107,4 @@ In case you do not remember the token you can show it using the `icingacli`:
**Step 5: Web Setup** **Step 5: Web Setup**
Visit Icinga Web 2 in your browser and complete installation using the web setup. Visit Icinga Web 2 in your browser and complete installation using the web setup: /icingaweb2/setup

View File

@ -407,7 +407,7 @@ abstract class ApplicationBootstrap
*/ */
protected function loadSetupModuleIfNecessary() protected function loadSetupModuleIfNecessary()
{ {
if (! @file_exists($this->config->resolvePath('config.ini'))) { if (! @file_exists($this->config->resolvePath('authentication.ini'))) {
$this->requiresSetup = true; $this->requiresSetup = true;
$this->moduleManager->loadModule('setup'); $this->moduleManager->loadModule('setup');
} elseif ($this->setupTokenExists()) { } elseif ($this->setupTokenExists()) {

View File

@ -11,7 +11,7 @@ use Icinga\User;
/** /**
* Test login with external authentication mechanism, e.g. Apache * Test login with external authentication mechanism, e.g. Apache
*/ */
class AutoLoginBackend extends UserBackend class ExternalBackend extends UserBackend
{ {
/** /**
* Regexp expression to strip values from a username * Regexp expression to strip values from a username
@ -21,7 +21,7 @@ class AutoLoginBackend extends UserBackend
private $stripUsernameRegexp; private $stripUsernameRegexp;
/** /**
* Create new autologin backend * Create new authentication backend of type "external"
* *
* @param ConfigObject $config * @param ConfigObject $config
*/ */
@ -33,7 +33,7 @@ class AutoLoginBackend extends UserBackend
/** /**
* Count the available users * Count the available users
* *
* Autologin backends will always return 1 * Authenticaton backends of type "external" will always return 1
* *
* @return int * @return int
*/ */

View File

@ -5,7 +5,7 @@
namespace Icinga\Authentication; namespace Icinga\Authentication;
use Countable; use Countable;
use Icinga\Authentication\Backend\AutoLoginBackend; use Icinga\Authentication\Backend\ExternalBackend;
use Icinga\Authentication\Backend\DbUserBackend; use Icinga\Authentication\Backend\DbUserBackend;
use Icinga\Authentication\Backend\LdapUserBackend; use Icinga\Authentication\Backend\LdapUserBackend;
use Icinga\Data\ConfigObject; use Icinga\Data\ConfigObject;
@ -69,8 +69,8 @@ abstract class UserBackend implements Countable
); );
} }
$backendType = strtolower($backendType); $backendType = strtolower($backendType);
if ($backendType === 'autologin') { if ($backendType === 'external') {
$backend = new AutoLoginBackend($backendConfig); $backend = new ExternalBackend($backendConfig);
$backend->setName($name); $backend->setName($name);
return $backend; return $backend;
} }

View File

@ -7,7 +7,7 @@ namespace Icinga\Module\Setup\Forms;
use Icinga\Web\Form; use Icinga\Web\Form;
use Icinga\Forms\Config\Authentication\DbBackendForm; use Icinga\Forms\Config\Authentication\DbBackendForm;
use Icinga\Forms\Config\Authentication\LdapBackendForm; use Icinga\Forms\Config\Authentication\LdapBackendForm;
use Icinga\Forms\Config\Authentication\AutologinBackendForm; use Icinga\Forms\Config\Authentication\ExternalBackendForm;
use Icinga\Data\ConfigObject; use Icinga\Data\ConfigObject;
/** /**
@ -80,7 +80,7 @@ class AuthBackendPage extends Form
'Before you are able to authenticate using the LDAP connection defined earlier you need to' 'Before you are able to authenticate using the LDAP connection defined earlier you need to'
. ' provide some more information so that Icinga Web 2 is able to locate account details.' . ' provide some more information so that Icinga Web 2 is able to locate account details.'
); );
} else { // if ($this->config['type'] === 'autologin' } else { // if ($this->config['type'] === 'external'
$note = $this->translate( $note = $this->translate(
'You\'ve chosen to authenticate using a web server\'s mechanism so it may be necessary' 'You\'ve chosen to authenticate using a web server\'s mechanism so it may be necessary'
. ' to adjust usernames before any permissions, restrictions, etc. are being applied.' . ' to adjust usernames before any permissions, restrictions, etc. are being applied.'
@ -103,8 +103,8 @@ class AuthBackendPage extends Form
} elseif ($this->config['type'] === 'ldap') { } elseif ($this->config['type'] === 'ldap') {
$backendForm = new LdapBackendForm(); $backendForm = new LdapBackendForm();
$backendForm->createElements($formData)->removeElement('resource'); $backendForm->createElements($formData)->removeElement('resource');
} else { // $this->config['type'] === 'autologin' } else { // $this->config['type'] === 'external'
$backendForm = new AutologinBackendForm(); $backendForm = new ExternalBackendForm();
$backendForm->createElements($formData); $backendForm->createElements($formData);
} }

View File

@ -36,6 +36,28 @@ class AuthenticationPage extends Form
) )
) )
); );
if (isset($formData['type']) && $formData['type'] === 'external' && !isset($_SERVER['REMOTE_USER'])) {
$this->addElement(
'note',
'external_note',
array(
'value' => $this->translate(
'You\'re currently not authenticated using any of the web server\'s authentication '
. 'mechanisms. Make sure you\'ll configure such, otherwise you\'ll not be able to '
. 'log into Icinga Web 2.'
),
'decorators' => array(
'ViewHelper',
array(
'HtmlTag',
array('tag' => 'p', 'class' => 'icon-info info-box')
)
)
)
);
}
$this->addElement( $this->addElement(
'note', 'note',
'description', 'description',
@ -54,13 +76,14 @@ class AuthenticationPage extends Form
if (Platform::extensionLoaded('ldap')) { if (Platform::extensionLoaded('ldap')) {
$backendTypes['ldap'] = 'LDAP'; $backendTypes['ldap'] = 'LDAP';
} }
$backendTypes['autologin'] = $this->translate('Autologin'); $backendTypes['external'] = $this->translate('External');
$this->addElement( $this->addElement(
'select', 'select',
'type', 'type',
array( array(
'required' => true, 'required' => true,
'autosubmit' => true,
'label' => $this->translate('Authentication Type'), 'label' => $this->translate('Authentication Type'),
'description' => $this->translate('The type of authentication to use when accessing Icinga Web 2'), 'description' => $this->translate('The type of authentication to use when accessing Icinga Web 2'),
'multiOptions' => $backendTypes 'multiOptions' => $backendTypes

View File

@ -139,7 +139,7 @@ class AuthenticationStep extends Step
. '<td><strong>' . t('User Name Attribute') . '</strong></td>' . '<td><strong>' . t('User Name Attribute') . '</strong></td>'
. '<td>' . $this->data['backendConfig']['user_name_attribute'] . '</td>' . '<td>' . $this->data['backendConfig']['user_name_attribute'] . '</td>'
. '</tr>' . '</tr>'
) : ($authType === 'autologin' ? ( ) : ($authType === 'external' ? (
'<tr>' '<tr>'
. '<td><strong>' . t('Filter Pattern') . '</strong></td>' . '<td><strong>' . t('Filter Pattern') . '</strong></td>'
. '<td>' . $this->data['backendConfig']['strip_username_regexp'] . '</td>' . '<td>' . $this->data['backendConfig']['strip_username_regexp'] . '</td>'

View File

@ -113,18 +113,33 @@
margin-left: 5em; margin-left: 5em;
} }
div.config-note { p.config-note {
width: 50%; width: 50%;
padding: 1em; padding: 1em;
margin: 5em auto 0; margin: 0 auto 2.5em;
text-align: center; text-align: center;
color: white; color: white;
background-color: @colorCritical; background-color: @colorCritical;
a {
color: white;
font-weight: bold;
}
} }
div.config-note a { p.info-box {
color: white; width: 50%;
font-weight: bold; height: 2.2em;
margin: 0 auto 2.5em;
span.icon-info {
float: left;
height: 100%;
}
em {
text-decoration: underline;
}
} }
} }

View File

@ -203,3 +203,9 @@ table.benchmark {
[class^="icon-"]:before, [class*=" icon-"]:before { [class^="icon-"]:before, [class*=" icon-"]:before {
text-decoration: none; text-decoration: none;
} }
.info-box {
padding: 0.5em;
border: 1px solid lightgrey;
background-color: infobackground;
}

View File

@ -220,6 +220,10 @@
} }
} }
#setup_authentication_type p.info-box em {
text-decoration: underline;
}
#setup_ldap_discovery_confirm table { #setup_ldap_discovery_confirm table {
margin: 1em 0; margin: 1em 0;
border-collapse: separate; border-collapse: separate;