Merge branch 'master' into bugfix/allow-to-configure-how-to-manage-groups-9609

This commit is contained in:
Johannes Meyer 2015-07-30 16:16:04 +02:00
commit 316a4d8b82
44 changed files with 1010 additions and 433 deletions

View File

@ -6,20 +6,14 @@
use Icinga\Application\Config; 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\User\ExternalBackend;
use Icinga\Exception\AuthenticationException;
use Icinga\Exception\ConfigurationError;
use Icinga\Exception\NotReadableError;
use Icinga\Forms\Authentication\LoginForm; use Icinga\Forms\Authentication\LoginForm;
use Icinga\User; use Icinga\Web\Controller;
use Icinga\Web\Controller\ActionController;
use Icinga\Web\Url; use Icinga\Web\Url;
/** /**
* Application wide controller for authentication * Application wide controller for authentication
*/ */
class AuthenticationController extends ActionController class AuthenticationController extends Controller
{ {
/** /**
* This controller does not require authentication * This controller does not require authentication
@ -34,118 +28,19 @@ class AuthenticationController extends ActionController
public function loginAction() public function loginAction()
{ {
$icinga = Icinga::app(); $icinga = Icinga::app();
if ($icinga->setupTokenExists() && $icinga->requiresSetup()) { if (($requiresSetup = $icinga->requiresSetup()) && $icinga->setupTokenExists()) {
$this->redirectNow(Url::fromPath('setup')); $this->redirectNow(Url::fromPath('setup'));
} }
$form = new LoginForm();
$triedOnlyExternalAuth = null; if ($this->Auth()->isAuthenticated()) {
$auth = $this->Auth(); $this->redirectNow($form->getRedirectUrl());
$this->view->form = $form = new LoginForm();
$this->view->title = $this->translate('Icingaweb Login');
try {
$redirectUrl = $this->view->form->getValue('redirect');
if ($redirectUrl) {
$redirectUrl = Url::fromPath($redirectUrl);
} else {
$redirectUrl = Url::fromPath('dashboard');
} }
if (! $requiresSetup) {
if ($auth->isAuthenticated()) { $form->handleRequest();
$this->rerenderLayout()->redirectNow($redirectUrl);
} }
$this->view->form = $form;
try { $this->view->title = $this->translate('Icinga Web 2 Login');
$config = Config::app('authentication'); $this->view->requiresSetup = $requiresSetup;
} catch (NotReadableError $e) {
throw new ConfigurationError(
$this->translate('Could not read your authentication.ini, no authentication methods are available.'),
0,
$e
);
}
$chain = new AuthChain($config);
$request = $this->getRequest();
if ($request->isPost() && $this->view->form->isValid($request->getPost())) {
$user = new User($this->view->form->getValue('username'));
$password = $this->view->form->getValue('password');
$backendsTried = 0;
$backendsWithError = 0;
$redirectUrl = $form->getValue('redirect');
if ($redirectUrl) {
$redirectUrl = Url::fromPath($redirectUrl);
} else {
$redirectUrl = Url::fromPath('dashboard');
}
foreach ($chain as $backend) {
if ($backend instanceof ExternalBackend) {
continue;
}
++$backendsTried;
try {
$authenticated = $backend->authenticate($user, $password);
} catch (AuthenticationException $e) {
Logger::error($e);
++$backendsWithError;
continue;
}
if ($authenticated === true) {
$auth->setAuthenticated($user);
$this->rerenderLayout()->redirectNow($redirectUrl);
}
}
if ($backendsTried === 0) {
$this->view->form->addError(
$this->translate(
'No authentication methods available. Did you create'
. ' authentication.ini when setting up Icinga Web 2?'
)
);
} else if ($backendsTried === $backendsWithError) {
$this->view->form->addError(
$this->translate(
'All configured authentication methods failed.'
. ' Please check the system log or Icinga Web 2 log for more information.'
)
);
} elseif ($backendsWithError) {
$this->view->form->addError(
$this->translate(
'Please note that not all authentication methods were available.'
. ' Check the system log or Icinga Web 2 log for more information.'
)
);
}
if ($backendsTried > 0 && $backendsTried !== $backendsWithError) {
$this->view->form->getElement('password')->addError($this->translate('Incorrect username or password'));
}
} elseif ($request->isGet()) {
$user = new User('');
foreach ($chain as $backend) {
$triedOnlyExternalAuth = $triedOnlyExternalAuth === null;
if ($backend instanceof ExternalBackend) {
$authenticated = $backend->authenticate($user);
if ($authenticated === true) {
$auth->setAuthenticated($user);
$this->rerenderLayout()->redirectNow(
Url::fromPath(Url::fromRequest()->getParam('redirect', 'dashboard'))
);
}
} else {
$triedOnlyExternalAuth = false;
}
}
}
} catch (Exception $e) {
$this->view->form->addError($e->getMessage());
}
$this->view->requiresExternalAuth = $triedOnlyExternalAuth && ! $auth->isAuthenticated();
$this->view->requiresSetup = Icinga::app()->requiresSetup();
} }
/** /**
@ -157,10 +52,12 @@ class AuthenticationController extends ActionController
if (! $auth->isAuthenticated()) { if (! $auth->isAuthenticated()) {
$this->redirectToLogin(); $this->redirectToLogin();
} }
$isRemoteUser = $auth->getUser()->isRemoteUser(); // Get info whether the user is externally authenticated before removing authorization which destroys the
// session and the user object
$isExternalUser = $auth->getUser()->isExternalUser();
$auth->removeAuthorization(); $auth->removeAuthorization();
if ($isRemoteUser === true) { if ($isExternalUser) {
$this->_response->setHttpResponseCode(401); $this->getResponse()->setHttpResponseCode(401);
} else { } else {
$this->redirectToLogin(); $this->redirectToLogin();
} }

View File

@ -32,12 +32,14 @@ class ConfigController extends Controller
$tabs->add('general', array( $tabs->add('general', array(
'title' => $this->translate('Adjust the general configuration of Icinga Web 2'), 'title' => $this->translate('Adjust the general configuration of Icinga Web 2'),
'label' => $this->translate('General'), 'label' => $this->translate('General'),
'url' => 'config/general' 'url' => 'config/general',
'baseTarget' => '_main'
)); ));
$tabs->add('resource', array( $tabs->add('resource', array(
'title' => $this->translate('Configure which resources are being utilized by Icinga Web 2'), 'title' => $this->translate('Configure which resources are being utilized by Icinga Web 2'),
'label' => $this->translate('Resources'), 'label' => $this->translate('Resources'),
'url' => 'config/resource' 'url' => 'config/resource',
'baseTarget' => '_main'
)); ));
return $tabs; return $tabs;
} }
@ -51,12 +53,14 @@ class ConfigController extends Controller
$tabs->add('userbackend', array( $tabs->add('userbackend', array(
'title' => $this->translate('Configure how users authenticate with and log into Icinga Web 2'), 'title' => $this->translate('Configure how users authenticate with and log into Icinga Web 2'),
'label' => $this->translate('User Backends'), 'label' => $this->translate('User Backends'),
'url' => 'config/userbackend' 'url' => 'config/userbackend',
'baseTarget' => '_main'
)); ));
$tabs->add('usergroupbackend', array( $tabs->add('usergroupbackend', array(
'title' => $this->translate('Configure how users are associated with groups by Icinga Web 2'), 'title' => $this->translate('Configure how users are associated with groups by Icinga Web 2'),
'label' => $this->translate('User Group Backends'), 'label' => $this->translate('User Group Backends'),
'url' => 'usergroupbackend/list' 'url' => 'usergroupbackend/list',
'baseTarget' => '_main'
)); ));
return $tabs; return $tabs;
} }

View File

@ -166,7 +166,8 @@ class RoleController extends AuthBackendController
'Configure roles to permit or restrict users and groups accessing Icinga Web 2' 'Configure roles to permit or restrict users and groups accessing Icinga Web 2'
), ),
'label' => $this->translate('Roles'), 'label' => $this->translate('Roles'),
'url' => 'role/list' 'url' => 'role/list',
'baseTarget' => '_main'
) )
); );

View File

@ -3,16 +3,24 @@
namespace Icinga\Forms\Authentication; namespace Icinga\Forms\Authentication;
use Icinga\Authentication\Auth;
use Icinga\Authentication\User\ExternalBackend;
use Icinga\User;
use Icinga\Web\Form; use Icinga\Web\Form;
use Icinga\Web\Url; use Icinga\Web\Url;
/** /**
* Class LoginForm * Form for user authentication
*/ */
class LoginForm extends Form class LoginForm extends Form
{ {
/** /**
* Initialize this login form * Redirect URL
*/
const REDIRECT_URL = 'dashboard';
/**
* {@inheritdoc}
*/ */
public function init() public function init()
{ {
@ -22,7 +30,7 @@ class LoginForm extends Form
} }
/** /**
* @see Form::createElements() * {@inheritdoc}
*/ */
public function createElements(array $formData) public function createElements(array $formData)
{ {
@ -54,4 +62,83 @@ class LoginForm extends Form
) )
); );
} }
/**
* {@inheritdoc}
*/
public function getRedirectUrl()
{
$redirect = null;
if ($this->created) {
$redirect = $this->getElement('redirect')->getValue();
}
if (empty($redirect)) {
$redirect = static::REDIRECT_URL;
}
return Url::fromPath($redirect);
}
/**
* {@inheritdoc}
*/
public function onSuccess()
{
$auth = Auth::getInstance();
$authChain = $auth->getAuthChain();
$authChain->setSkipExternalBackends(true);
$user = new User($this->getElement('username')->getValue());
$password = $this->getElement('password')->getValue();
$authenticated = $authChain->authenticate($user, $password);
if ($authenticated) {
$auth->setAuthenticated($user);
$this->getResponse()->setRerenderLayout(true);
return true;
}
switch ($authChain->getError()) {
case $authChain::EEMPTY:
$this->addError($this->translate(
'No authentication methods available.'
. ' Did you create authentication.ini when setting up Icinga Web 2?'
));
break;
case $authChain::EFAIL:
$this->addError($this->translate(
'All configured authentication methods failed.'
. ' Please check the system log or Icinga Web 2 log for more information.'
));
break;
/** @noinspection PhpMissingBreakStatementInspection */
case $authChain::ENOTALL:
$this->addError($this->translate(
'Please note that not all authentication methods were available.'
. ' Check the system log or Icinga Web 2 log for more information.'
));
// Move to default
default:
$this->getElement('password')->addError($this->translate('Incorrect username or password'));
break;
}
return false;
}
/**
* {@inheritdoc}
*/
public function onRequest()
{
$auth = Auth::getInstance();
$onlyExternal = true;
// TODO(el): This may be set on the auth chain once iterated. See Auth::authExternal().
foreach ($auth->getAuthChain() as $backend) {
if (! $backend instanceof ExternalBackend) {
$onlyExternal = false;
}
}
if ($onlyExternal) {
$this->addError($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.'
));
}
}
} }

View File

@ -6,7 +6,7 @@ namespace Icinga\Forms;
use Exception; use Exception;
use DateTimeZone; use DateTimeZone;
use Icinga\Application\Logger; use Icinga\Application\Logger;
use Icinga\Authentication\Manager; use Icinga\Authentication\Auth;
use Icinga\User\Preferences; use Icinga\User\Preferences;
use Icinga\User\Preferences\PreferencesStore; use Icinga\User\Preferences\PreferencesStore;
use Icinga\Util\TimezoneDetect; use Icinga\Util\TimezoneDetect;
@ -123,7 +123,7 @@ class PreferenceForm extends Form
*/ */
public function onRequest() public function onRequest()
{ {
$auth = Manager::getInstance(); $auth = Auth::getInstance();
$values = $auth->getUser()->getPreferences()->get('icingaweb'); $values = $auth->getUser()->getPreferences()->get('icingaweb');
if (! isset($values['language'])) { if (! isset($values['language'])) {

View File

@ -2,7 +2,7 @@
use Icinga\Web\Url; use Icinga\Web\Url;
use Icinga\Web\Notification; use Icinga\Web\Notification;
use Icinga\Authentication\Manager as Auth; use Icinga\Authentication\Auth;
if (Auth::getInstance()->isAuthenticated()): if (Auth::getInstance()->isAuthenticated()):
@ -51,7 +51,7 @@ if (Auth::getInstance()->isAuthenticated()):
<ul id="notifications"><?php <ul id="notifications"><?php
$notifications = Notification::getInstance(); $notifications = Notification::getInstance();
if ($notifications->hasMessages()) { if ($notifications->hasMessages()) {
foreach ($notifications->getMessages() as $m) { foreach ($notifications->popMessages() as $m) {
echo '<li class="' . $m->type . '">' . $this->escape($m->message) . '</li>'; echo '<li class="' . $m->type . '">' . $this->escape($m->message) . '</li>';
} }
} }

View File

@ -17,11 +17,6 @@
'<a href="' . $this->href('setup') . '" title="' . $this->translate('Icinga Web 2 Setup-Wizard') . '">', '<a href="' . $this->href('setup') . '" title="' . $this->translate('Icinga Web 2 Setup-Wizard') . '">',
'</a>' '</a>'
); ?></p> ); ?></p>
<?php elseif ($requiresExternalAuth): ?>
<p class="info-box"><?= $this->icon('info'); ?><?= $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 ?> <?php endif ?>
<?= $this->form ?> <?= $this->form ?>
<div class="footer">Icinga Web 2 &copy; 2013-2015<br><a href="https://www.icinga.org"><?= $this->translate('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>

View File

@ -1,4 +1,4 @@
<div class="controls" data-base-target="_main"> <div class="controls">
<?= $tabs; ?> <?= $tabs; ?>
</div> </div>
<div class="content" data-base-target="_next"> <div class="content" data-base-target="_next">

View File

@ -1,4 +1,4 @@
<div class="controls" data-base-target="_main"> <div class="controls">
<?= $tabs; ?> <?= $tabs; ?>
</div> </div>
<div class="content" data-base-target="_next"> <div class="content" data-base-target="_next">

View File

@ -16,7 +16,8 @@
array('backend' => $backendNames[$i]), array('backend' => $backendNames[$i]),
array( array(
'icon' => 'edit', 'icon' => 'edit',
'title' => sprintf($this->translate('Edit user backend %s'), $backendNames[$i]) 'class' => 'rowaction',
'title' => sprintf($this->translate('rEdit user backend %s'), $backendNames[$i])
) )
); ?> ); ?>
</td> </td>

View File

@ -1,4 +1,4 @@
<div class="controls" data-base-target="_main"> <div class="controls">
<?= $tabs ?> <?= $tabs ?>
</div> </div>
<div class="content"> <div class="content">

View File

@ -12,7 +12,7 @@ use Zend_Layout;
use Zend_Paginator; use Zend_Paginator;
use Zend_View_Helper_PaginationControl; use Zend_View_Helper_PaginationControl;
use Icinga\Application\Logger; use Icinga\Application\Logger;
use Icinga\Authentication\Manager; use Icinga\Authentication\Auth;
use Icinga\User; use Icinga\User;
use Icinga\Util\TimezoneDetect; use Icinga\Util\TimezoneDetect;
use Icinga\Util\Translator; use Icinga\Util\Translator;
@ -55,6 +55,13 @@ class Web extends ApplicationBootstrap
*/ */
private $request; private $request;
/**
* Response
*
* @var Response
*/
protected $response;
/** /**
* Session object * Session object
* *
@ -91,11 +98,12 @@ class Web extends ApplicationBootstrap
->setupResourceFactory() ->setupResourceFactory()
->setupSession() ->setupSession()
->setupNotifications() ->setupNotifications()
->setupRequest()
->setupResponse()
->setupUser() ->setupUser()
->setupTimezone() ->setupTimezone()
->setupLogger() ->setupLogger()
->setupInternationalization() ->setupInternationalization()
->setupRequest()
->setupZendMvc() ->setupZendMvc()
->setupFormNamespace() ->setupFormNamespace()
->setupModuleManager() ->setupModuleManager()
@ -137,6 +145,26 @@ class Web extends ApplicationBootstrap
return $this->frontController; return $this->frontController;
} }
/**
* Get the request
*
* @return Request
*/
public function getRequest()
{
return $this->request;
}
/**
* Get the response
*
* @return Response
*/
public function getResponse()
{
return $this->response;
}
/** /**
* Getter for view * Getter for view
* *
@ -152,7 +180,7 @@ class Web extends ApplicationBootstrap
*/ */
public function dispatch() public function dispatch()
{ {
$this->frontController->dispatch($this->request, new Response()); $this->frontController->dispatch($this->getRequest(), $this->getResponse());
} }
/** /**
@ -180,9 +208,11 @@ class Web extends ApplicationBootstrap
*/ */
private function setupUser() private function setupUser()
{ {
$auth = Manager::getInstance(); $auth = Auth::getInstance();
if ($auth->isAuthenticated()) { if ($auth->isAuthenticated()) {
$this->user = $auth->getUser(); $user = $auth->getUser();
$this->request->setUser($user);
$this->user = $user;
} }
return $this; return $this;
} }
@ -210,16 +240,24 @@ class Web extends ApplicationBootstrap
} }
/** /**
* Inject dependencies into request * Set the request
* *
* @return $this * @return $this
*/ */
private function setupRequest() private function setupRequest()
{ {
$this->request = new Request(); $this->request = new Request();
if ($this->user instanceof User) { return $this;
$this->request->setUser($this->user);
} }
/**
* Set the response
*
* @return $this
*/
protected function setupResponse()
{
$this->response = new Response();
return $this; return $this;
} }
@ -283,7 +321,7 @@ class Web extends ApplicationBootstrap
*/ */
protected function detectTimezone() protected function detectTimezone()
{ {
$auth = Manager::getInstance(); $auth = Auth::getInstance();
if (! $auth->isAuthenticated() if (! $auth->isAuthenticated()
|| ($timezone = $auth->getUser()->getPreferences()->getValue('icingaweb', 'timezone')) === null || ($timezone = $auth->getUser()->getPreferences()->getValue('icingaweb', 'timezone')) === null
) { ) {
@ -304,7 +342,7 @@ class Web extends ApplicationBootstrap
*/ */
protected function detectLocale() protected function detectLocale()
{ {
$auth = Manager::getInstance(); $auth = Auth::getInstance();
if ($auth->isAuthenticated() if ($auth->isAuthenticated()
&& ($locale = $auth->getUser()->getPreferences()->getValue('icingaweb', 'language')) !== null && ($locale = $auth->getUser()->getPreferences()->getValue('icingaweb', 'language')) !== null
) { ) {

View File

@ -4,18 +4,20 @@
namespace Icinga\Authentication; namespace Icinga\Authentication;
use Exception; use Exception;
use Icinga\Authentication\UserGroup\UserGroupBackend;
use Icinga\Application\Config; use Icinga\Application\Config;
use Icinga\Application\Icinga;
use Icinga\Application\Logger;
use Icinga\Authentication\User\ExternalBackend;
use Icinga\Authentication\UserGroup\UserGroupBackend;
use Icinga\Data\ConfigObject; use Icinga\Data\ConfigObject;
use Icinga\Exception\IcingaException; use Icinga\Exception\IcingaException;
use Icinga\Exception\NotReadableError; use Icinga\Exception\NotReadableError;
use Icinga\Application\Logger;
use Icinga\User; use Icinga\User;
use Icinga\User\Preferences; use Icinga\User\Preferences;
use Icinga\User\Preferences\PreferencesStore; use Icinga\User\Preferences\PreferencesStore;
use Icinga\Web\Session; use Icinga\Web\Session;
class Manager class Auth
{ {
/** /**
* Singleton instance * Singleton instance
@ -24,6 +26,20 @@ class Manager
*/ */
private static $instance; private static $instance;
/**
* Request
*
* @var \Icinga\Web\Request
*/
protected $request;
/**
* Response
*
* @var \Icinga\Web\Response
*/
protected $response;
/** /**
* Authenticated user * Authenticated user
* *
@ -32,6 +48,9 @@ class Manager
private $user; private $user;
/**
* @see getInstance()
*/
private function __construct() private function __construct()
{ {
} }
@ -44,11 +63,39 @@ class Manager
public static function getInstance() public static function getInstance()
{ {
if (self::$instance === null) { if (self::$instance === null) {
self::$instance = new static(); self::$instance = new self();
} }
return self::$instance; return self::$instance;
} }
/**
* Get the auth chain
*
* @return AuthChain
*/
public function getAuthChain()
{
return new AuthChain();
}
/**
* Whether the user is authenticated
*
* @param bool $ignoreSession True to prevent session authentication
*
* @return bool
*/
public function isAuthenticated($ignoreSession = false)
{
if ($this->user === null && ! $ignoreSession) {
$this->authenticateFromSession();
}
if ($this->user === null && ! $this->authExternal()) {
return $this->authHttp();
}
return true;
}
public function setAuthenticated(User $user, $persist = true) public function setAuthenticated(User $user, $persist = true)
{ {
$username = $user->getUsername(); $username = $user->getUsername();
@ -121,58 +168,40 @@ class Manager
} }
/** /**
* Writes the current user to the session * Getter for groups belonged to authenticated user
*
* @return array
* @see User::getGroups
*/ */
public function persistCurrentUser() public function getGroups()
{ {
Session::getSession()->set('user', $this->user)->refreshId(); return $this->user->getGroups();
} }
/** /**
* Try to authenticate the user with the current session * Get the request
* *
* Authentication for externally-authenticated users will be revoked if the username changed or external * @return \Icinga\Web\Request
* authentication is no longer in effect
*/ */
public function authenticateFromSession() public function getRequest()
{ {
$this->user = Session::getSession()->get('user'); if ($this->request === null) {
if ($this->user !== null && $this->user->isRemoteUser() === true) { $this->request = Icinga::app()->getRequest();
list($originUsername, $field) = $this->user->getRemoteUserInformation();
if (! array_key_exists($field, $_SERVER) || $_SERVER[$field] !== $originUsername) {
$this->removeAuthorization();
}
} }
return $this->request;
} }
/** /**
* Whether the user is authenticated * Get the response
* *
* @param bool $ignoreSession True to prevent session authentication * @return \Icinga\Web\Response
*
* @return bool
*/ */
public function isAuthenticated($ignoreSession = false) public function getResponse()
{ {
if ($this->user === null && ! $ignoreSession) { if ($this->response === null) {
$this->authenticateFromSession(); $this->response = Icinga::app()->getResponse();
} }
return is_object($this->user); return $this->response;
}
/**
* Whether an authenticated user has a given permission
*
* @param string $permission Permission name
*
* @return bool True if the user owns the given permission, false if not or if not authenticated
*/
public function hasPermission($permission)
{
if (! $this->isAuthenticated()) {
return false;
}
return $this->user->can($permission);
} }
/** /**
@ -192,15 +221,6 @@ class Manager
return $this->user->getRestrictions($restriction); return $this->user->getRestrictions($restriction);
} }
/**
* Purges the current authorization information and session
*/
public function removeAuthorization()
{
$this->user = null;
Session::getSession()->purge();
}
/** /**
* Returns the current user or null if no user is authenticated * Returns the current user or null if no user is authenticated
* *
@ -212,13 +232,124 @@ class Manager
} }
/** /**
* Getter for groups belonged to authenticated user * Try to authenticate the user with the current session
* *
* @return array * Authentication for externally-authenticated users will be revoked if the username changed or external
* @see User::getGroups * authentication is no longer in effect
*/ */
public function getGroups() public function authenticateFromSession()
{ {
return $this->user->getGroups(); $this->user = Session::getSession()->get('user');
if ($this->user !== null && $this->user->isExternalUser() === true) {
list($originUsername, $field) = $this->user->getExternalUserInformation();
if (! array_key_exists($field, $_SERVER) || $_SERVER[$field] !== $originUsername) {
$this->removeAuthorization();
}
}
}
/**
* Attempt to authenticate a user from external user backends
*
* @return bool
*/
protected function authExternal()
{
$user = new User('');
foreach ($this->getAuthChain() as $userBackend) {
if ($userBackend instanceof ExternalBackend) {
if ($userBackend->authenticate($user)) {
$this->setAuthenticated($user);
return true;
}
}
}
return false;
}
/**
* Attempt to authenticate a user using HTTP authentication
*
* Supports only the Basic HTTP authentication scheme. XHR will be ignored.
*
* @return bool
*/
protected function authHttp()
{
if ($this->getRequest()->isXmlHttpRequest()) {
return false;
}
if (($header = $this->getRequest()->getHeader('Authorization')) === false) {
return false;
}
if (empty($header)) {
$this->challengeHttp();
}
list($scheme) = explode(' ', $header, 2);
if ($scheme !== 'Basic') {
$this->challengeHttp();
}
$authorization = substr($header, strlen('Basic '));
$credentials = base64_decode($authorization);
$credentials = array_filter(explode(':', $credentials, 2));
if (count($credentials) !== 2) {
// Deny empty username and/or password
$this->challengeHttp();
}
$user = new User($credentials[0]);
$password = $credentials[1];
if ($this->getAuthChain()->setSkipExternalBackends(true)->authenticate($user, $password)) {
$this->setAuthenticated($user, false);
$user->setIsHttpUser(true);
return true;
} else {
$this->challengeHttp();
}
}
/**
* Challenge client immediately for HTTP authentication
*
* Sends the response w/ the 401 Unauthorized status code and WWW-Authenticate header.
*/
protected function challengeHttp()
{
$response = $this->getResponse();
$response->setHttpResponseCode(401);
$response->setHeader('WWW-Authenticate', 'Basic realm="Icinga Web 2"');
$response->sendHeaders();
exit();
}
/**
* Whether an authenticated user has a given permission
*
* @param string $permission Permission name
*
* @return bool True if the user owns the given permission, false if not or if not authenticated
*/
public function hasPermission($permission)
{
if (! $this->isAuthenticated()) {
return false;
}
return $this->user->can($permission);
}
/**
* Writes the current user to the session
*/
public function persistCurrentUser()
{
Session::getSession()->set('user', $this->user)->refreshId();
}
/**
* Purges the current authorization information and session
*/
public function removeAuthorization()
{
$this->user = null;
Session::getSession()->purge();
} }
} }

View File

@ -4,41 +4,175 @@
namespace Icinga\Authentication; namespace Icinga\Authentication;
use Iterator; use Iterator;
use Icinga\Data\ConfigObject;
use Icinga\Authentication\User\UserBackend;
use Icinga\Authentication\User\UserBackendInterface;
use Icinga\Application\Config; use Icinga\Application\Config;
use Icinga\Application\Logger; use Icinga\Application\Logger;
use Icinga\Authentication\User\ExternalBackend;
use Icinga\Authentication\User\UserBackend;
use Icinga\Authentication\User\UserBackendInterface;
use Icinga\Data\ConfigObject;
use Icinga\Exception\AuthenticationException;
use Icinga\Exception\ConfigurationError; use Icinga\Exception\ConfigurationError;
use Icinga\Exception\NotReadableError;
use Icinga\User;
/** /**
* Iterate user backends created from config * Iterate user backends created from config
*/ */
class AuthChain implements Iterator class AuthChain implements Authenticatable, Iterator
{ {
/**
* Authentication config file
*
* @var string
*/
const AUTHENTICATION_CONFIG = 'authentication';
/**
* Error code if the authentication configuration was not readable
*
* @var int
*/
const EPERM = 1;
/**
* Error code if the authentication configuration is empty
*/
const EEMPTY = 2;
/**
* Error code if all authentication methods failed
*
* @var int
*/
const EFAIL = 3;
/**
* Error code if not all authentication methods were available
*
* @var int
*/
const ENOTALL = 4;
/** /**
* User backends configuration * User backends configuration
* *
* @var Config * @var Config
*/ */
private $config; protected $config;
/** /**
* The consecutive user backend while looping * The consecutive user backend while looping
* *
* @var UserBackendInterface * @var UserBackendInterface
*/ */
private $currentBackend; protected $currentBackend;
/**
* Last error code
*
* @var int|null
*/
protected $error;
/**
* Whether external user backends should be skipped on iteration
*
* @var bool
*/
protected $skipExternalBackends = false;
/** /**
* Create a new authentication chain from config * Create a new authentication chain from config
* *
* @param Config $config User backends configuration * @param Config $config User backends configuration
*/ */
public function __construct(Config $config) public function __construct(Config $config = null)
{ {
if ($config === null) {
try {
$this->config = Config::app(static::AUTHENTICATION_CONFIG);
} catch (NotReadableError $e) {
$this->config = new Config();
$this->error = static::EPERM;
}
} else {
$this->config = $config; $this->config = $config;
} }
}
/**
* {@inheritdoc}
*/
public function authenticate(User $user, $password)
{
$this->error = null;
$backendsTried = 0;
$backendsWithError = 0;
foreach ($this as $backend) {
++$backendsTried;
try {
$authenticated = $backend->authenticate($user, $password);
} catch (AuthenticationException $e) {
Logger::error($e);
++$backendsWithError;
continue;
}
if ($authenticated) {
return true;
}
}
if ($backendsTried === 0) {
$this->error = static::EEMPTY;
} elseif ($backendsTried === $backendsWithError) {
$this->error = static::EFAIL;
} elseif ($backendsWithError) {
$this->error = static::ENOTALL;
}
return false;
}
/**
* Get the last error code
*
* @return int|null
*/
public function getError()
{
return $this->error;
}
/**
* Whether authentication had errors
*
* @return bool
*/
public function hasError()
{
return $this->error !== null;
}
/**
* Get whether to skip external user backends on iteration
*
* @return bool
*/
public function getSkipExternalBackends()
{
return $this->skipExternalBackends;
}
/**
* Set whether to skip external user backends on iteration
*
* @param bool $skipExternalBackends
*
* @return $this
*/
public function setSkipExternalBackends($skipExternalBackends = true)
{
$this->skipExternalBackends = (bool) $skipExternalBackends;
return $this;
}
/** /**
* Rewind the chain * Rewind the chain
@ -52,7 +186,7 @@ class AuthChain implements Iterator
} }
/** /**
* Return the current user backend * Get the current user backend
* *
* @return UserBackendInterface * @return UserBackendInterface
*/ */
@ -62,7 +196,7 @@ class AuthChain implements Iterator
} }
/** /**
* Return the key of the current user backend config * Get the key of the current user backend config
* *
* @return string * @return string
*/ */
@ -82,7 +216,8 @@ class AuthChain implements Iterator
} }
/** /**
* Check if the current user backend is valid, i.e. it's enabled and the config is valid * Check whether the current user backend is valid, i.e. it's enabled, not an external user backend and whether its
* config is valid
* *
* @return bool * @return bool
*/ */
@ -94,7 +229,7 @@ class AuthChain implements Iterator
} }
$backendConfig = $this->config->current(); $backendConfig = $this->config->current();
if ((bool) $backendConfig->get('disabled', false) === true) { if ((bool) $backendConfig->get('disabled', false)) {
$this->next(); $this->next();
return $this->valid(); return $this->valid();
} }
@ -105,15 +240,20 @@ class AuthChain implements Iterator
} catch (ConfigurationError $e) { } catch (ConfigurationError $e) {
Logger::error( Logger::error(
new ConfigurationError( new ConfigurationError(
'Cannot create authentication backend "%s". An exception was thrown:', 'Can\'t create authentication backend "%s". An exception was thrown:', $name, $e
$name,
$e
) )
); );
$this->next(); $this->next();
return $this->valid(); return $this->valid();
} }
if ($this->getSkipExternalBackends()
&& $backend instanceof ExternalBackend
) {
$this->next();
return $this->valid();
}
$this->currentBackend = $backend; $this->currentBackend = $backend;
return true; return true;
} }

View File

@ -0,0 +1,21 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Authentication;
use Icinga\User;
interface Authenticatable
{
/**
* Authenticate a user
*
* @param User $user
* @param string $password
*
* @return bool
*
* @throws \Icinga\Exception\AuthenticationException If authentication errors
*/
public function authenticate(User $user, $password);
}

View File

@ -36,11 +36,7 @@ class ExternalBackend implements UserBackendInterface
} }
/** /**
* Set this backend's name * {@inheritdoc}
*
* @param string $name
*
* @return $this
*/ */
public function setName($name) public function setName($name)
{ {
@ -49,30 +45,22 @@ class ExternalBackend implements UserBackendInterface
} }
/** /**
* Return this backend's name * {@inheritdoc}
*
* @return string
*/ */
public function getName() public function getName()
{ {
return $this->name; return $this->name;
} }
/** /**
* Authenticate the given user * {@inheritdoc}
*
* @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 = null) public function authenticate(User $user, $password = null)
{ {
if (isset($_SERVER['REMOTE_USER'])) { if (isset($_SERVER['REMOTE_USER'])) {
$username = $_SERVER['REMOTE_USER']; $username = $_SERVER['REMOTE_USER'];
$user->setRemoteUserInformation($username, 'REMOTE_USER'); $user->setExternalUserInformation($username, 'REMOTE_USER');
if ($this->stripUsernameRegexp) { if ($this->stripUsernameRegexp) {
$stripped = preg_replace($this->stripUsernameRegexp, '', $username); $stripped = preg_replace($this->stripUsernameRegexp, '', $username);

View File

@ -3,13 +3,13 @@
namespace Icinga\Authentication\User; namespace Icinga\Authentication\User;
use Icinga\Exception\AuthenticationException; use Icinga\Authentication\Authenticatable;
use Icinga\User; use Icinga\User;
/** /**
* Interface for user backends * Interface for user backends
*/ */
interface UserBackendInterface interface UserBackendInterface extends Authenticatable
{ {
/** /**
* Set this backend's name * Set this backend's name
@ -26,16 +26,4 @@ interface UserBackendInterface
* @return string * @return string
*/ */
public function getName(); public function getName();
/**
* 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);
} }

View File

@ -111,7 +111,7 @@ class PivotTable
*/ */
protected function getPaginationParameter($axis, $param, $default = null) protected function getPaginationParameter($axis, $param, $default = null)
{ {
$request = Icinga::app()->getFrontController()->getRequest(); $request = Icinga::app()->getRequest();
$value = $request->getParam($param, ''); $value = $request->getParam($param, '');
if (strpos($value, ',') > 0) { if (strpos($value, ',') > 0) {

View File

@ -435,7 +435,7 @@ class SimpleQuery implements QueryInterface, Queryable, Iterator
if ($itemsPerPage === null || $pageNumber === null) { if ($itemsPerPage === null || $pageNumber === null) {
// Detect parameters from request // Detect parameters from request
$request = Icinga::app()->getFrontController()->getRequest(); $request = Icinga::app()->getRequest();
if ($itemsPerPage === null) { if ($itemsPerPage === null) {
$itemsPerPage = $request->getParam('limit', 25); $itemsPerPage = $request->getParam('limit', 25);
} }

View File

@ -180,7 +180,7 @@ namespace Icinga\Test {
*/ */
public function getRequestMock() public function getRequestMock()
{ {
return Icinga::app()->getFrontController()->getRequest(); return Icinga::app()->getRequest();
} }
/** /**

View File

@ -57,7 +57,7 @@ class User
protected $additionalInformation = array(); protected $additionalInformation = array();
/** /**
* Information if the user is external authenticated * Information if the user is externally authenticated
* *
* Keys: * Keys:
* *
@ -66,7 +66,7 @@ class User
* *
* @var array * @var array
*/ */
protected $remoteUserInformation = array(); protected $externalUserInformation = array();
/** /**
* Set of permissions * Set of permissions
@ -96,6 +96,13 @@ class User
*/ */
protected $preferences; protected $preferences;
/**
* Whether the user is authenticated using a HTTP authentication mechanism
*
* @var bool
*/
protected $isHttpUser = false;
/** /**
* Creates a user object given the provided information * Creates a user object given the provided information
* *
@ -380,34 +387,57 @@ class User
} }
/** /**
* Set additional remote user information * Set additional external user information
* *
* @param stirng $username * @param string $username
* @param string $field * @param string $field
*/ */
public function setRemoteUserInformation($username, $field) public function setExternalUserInformation($username, $field)
{ {
$this->remoteUserInformation = array($username, $field); $this->externalUserInformation = array($username, $field);
} }
/** /**
* Get additional remote user information * Get additional external user information
* *
* @return array * @return array
*/ */
public function getRemoteUserInformation() public function getExternalUserInformation()
{ {
return $this->remoteUserInformation; return $this->externalUserInformation;
} }
/** /**
* Return true if user has remote user information set * Return true if user has external user information set
* *
* @return bool * @return bool
*/ */
public function isRemoteUser() public function isExternalUser()
{ {
return ! empty($this->remoteUserInformation); return ! empty($this->externalUserInformation);
}
/**
* Get whether the user is authenticated using a HTTP authentication mechanism
*
* @return bool
*/
public function getIsHttpUser()
{
return $this->isHttpUser;
}
/**
* Set whether the user is authenticated using a HTTP authentication mechanism
*
* @param bool $isHttpUser
*
* @return $this
*/
public function setIsHttpUser($isHttpUser = true)
{
$this->isHttpUser = (bool) $isHttpUser;
return $this;
} }
/** /**

View File

@ -6,7 +6,7 @@ namespace Icinga\Web\Controller;
use Exception; use Exception;
use Icinga\Application\Benchmark; use Icinga\Application\Benchmark;
use Icinga\Application\Config; use Icinga\Application\Config;
use Icinga\Authentication\Manager; use Icinga\Authentication\Auth;
use Icinga\Exception\Http\HttpMethodNotAllowedException; use Icinga\Exception\Http\HttpMethodNotAllowedException;
use Icinga\Exception\IcingaException; use Icinga\Exception\IcingaException;
use Icinga\Exception\ProgrammingError; use Icinga\Exception\ProgrammingError;
@ -52,7 +52,7 @@ class ActionController extends Zend_Controller_Action
/** /**
* Authentication manager * Authentication manager
* *
* @var Manager|null * @var Auth|null
*/ */
private $auth; private $auth;
@ -124,12 +124,12 @@ class ActionController extends Zend_Controller_Action
/** /**
* Get the authentication manager * Get the authentication manager
* *
* @return Manager * @return Auth
*/ */
public function Auth() public function Auth()
{ {
if ($this->auth === null) { if ($this->auth === null) {
$this->auth = Manager::getInstance(); $this->auth = Auth::getInstance();
} }
return $this->auth; return $this->auth;
} }
@ -455,7 +455,7 @@ class ActionController extends Zend_Controller_Action
$notifications = Notification::getInstance(); $notifications = Notification::getInstance();
if ($notifications->hasMessages()) { if ($notifications->hasMessages()) {
$notificationList = array(); $notificationList = array();
foreach ($notifications->getMessages() as $m) { foreach ($notifications->popMessages() as $m) {
$notificationList[] = rawurlencode($m->type . ' ' . $m->message); $notificationList[] = rawurlencode($m->type . ' ' . $m->message);
} }
$resp->setHeader('X-Icinga-Notification', implode('&', $notificationList), true); $resp->setHeader('X-Icinga-Notification', implode('&', $notificationList), true);

View File

@ -77,6 +77,6 @@ class ModuleActionController extends ActionController
public function postDispatchXhr() public function postDispatchXhr()
{ {
parent::postDispatchXhr(); parent::postDispatchXhr();
$this->getResponse()->setHeader('X-Icinga-Module', $this->moduleName); $this->getResponse()->setHeader('X-Icinga-Module', $this->moduleName, true);
} }
} }

View File

@ -8,7 +8,7 @@ use Zend_Form;
use Zend_Form_Element; use Zend_Form_Element;
use Zend_View_Interface; use Zend_View_Interface;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
use Icinga\Authentication\Manager; use Icinga\Authentication\Auth;
use Icinga\Exception\ProgrammingError; use Icinga\Exception\ProgrammingError;
use Icinga\Security\SecurityException; use Icinga\Security\SecurityException;
use Icinga\Util\Translator; use Icinga\Util\Translator;
@ -179,7 +179,7 @@ class Form extends Zend_Form
/** /**
* Authentication manager * Authentication manager
* *
* @var Manager|null * @var Auth|null
*/ */
private $auth; private $auth;
@ -948,10 +948,17 @@ class Form extends Zend_Form
*/ */
public function addCsrfCounterMeasure() public function addCsrfCounterMeasure()
{ {
if (! $this->tokenDisabled && $this->getElement($this->tokenElementName) === null) { if (! $this->tokenDisabled) {
$request = $this->getRequest();
if (! $request->isXmlHttpRequest()
&& $request->getIsApiRequest()
) {
return $this;
}
if ($this->getElement($this->tokenElementName) === null) {
$this->addElement(new CsrfCounterMeasure($this->tokenElementName)); $this->addElement(new CsrfCounterMeasure($this->tokenElementName));
} }
}
return $this; return $this;
} }
@ -1245,7 +1252,7 @@ class Form extends Zend_Form
public function getRequest() public function getRequest()
{ {
if ($this->request === null) { if ($this->request === null) {
$this->request = Icinga::app()->getFrontController()->getRequest(); $this->request = Icinga::app()->getRequest();
} }
return $this->request; return $this->request;
@ -1344,12 +1351,12 @@ class Form extends Zend_Form
/** /**
* Get the authentication manager * Get the authentication manager
* *
* @return Manager * @return Auth
*/ */
public function Auth() public function Auth()
{ {
if ($this->auth === null) { if ($this->auth === null) {
$this->auth = Manager::getInstance(); $this->auth = Auth::getInstance();
} }
return $this->auth; return $this->auth;
} }

View File

@ -75,6 +75,6 @@ class Button extends FormElement
*/ */
protected function getRequest() protected function getRequest()
{ {
return Icinga::app()->getFrontController()->getRequest(); return Icinga::app()->getRequest();
} }
} }

View File

@ -7,7 +7,7 @@ use RecursiveIterator;
use Icinga\Application\Config; use Icinga\Application\Config;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
use Icinga\Application\Logger; use Icinga\Application\Logger;
use Icinga\Authentication\Manager; use Icinga\Authentication\Auth;
use Icinga\Data\ConfigObject; use Icinga\Data\ConfigObject;
use Icinga\Exception\ConfigurationError; use Icinga\Exception\ConfigurationError;
use Icinga\Exception\ProgrammingError; use Icinga\Exception\ProgrammingError;
@ -208,7 +208,7 @@ class Menu implements RecursiveIterator
{ {
$menu = new static('menu'); $menu = new static('menu');
$menu->addMainMenuItems(); $menu->addMainMenuItems();
$auth = Manager::getInstance(); $auth = Auth::getInstance();
$manager = Icinga::app()->getModuleManager(); $manager = Icinga::app()->getModuleManager();
foreach ($manager->getLoadedModules() as $module) { foreach ($manager->getLoadedModules() as $module) {
if ($auth->hasPermission($manager::MODULE_PERMISSION_NS . $module->getName())) { if ($auth->hasPermission($manager::MODULE_PERMISSION_NS . $module->getName())) {
@ -223,7 +223,7 @@ class Menu implements RecursiveIterator
*/ */
protected function addMainMenuItems() protected function addMainMenuItems()
{ {
$auth = Manager::getInstance(); $auth = Auth::getInstance();
if ($auth->isAuthenticated()) { if ($auth->isAuthenticated()) {

View File

@ -3,7 +3,7 @@
namespace Icinga\Web\Menu; namespace Icinga\Web\Menu;
use RecursiveFilterIterator; use RecursiveFilterIterator;
use Icinga\Authentication\Manager; use Icinga\Authentication\Auth;
use Icinga\Web\Menu; use Icinga\Web\Menu;
class PermittedMenuItemFilter extends RecursiveFilterIterator class PermittedMenuItemFilter extends RecursiveFilterIterator
@ -18,7 +18,7 @@ class PermittedMenuItemFilter extends RecursiveFilterIterator
$item = $this->current(); $item = $this->current();
/** @var Menu $item */ /** @var Menu $item */
if (($permission = $item->getPermission()) !== null) { if (($permission = $item->getPermission()) !== null) {
$auth = Manager::getInstance(); $auth = Auth::getInstance();
if (! $auth->isAuthenticated()) { if (! $auth->isAuthenticated()) {
// Don't accept menu item because user is not authenticated and the menu item requires a permission // Don't accept menu item because user is not authenticated and the menu item requires a permission
return false; return false;

View File

@ -17,86 +17,72 @@ use Icinga\Web\Session;
*/ */
class Notification class Notification
{ {
/**
* Notification type info
*
* @var string
*/
const INFO = 'info';
/**
* Notification type error
*
* @var string
*/
const ERROR = 'error';
/**
* Notification type success
*
* @var string
*/
const SUCCESS = 'success';
/**
* Notification type warning
*
* @var string
*/
const WARNING = 'warning';
/**
* Name of the session key for notification messages
*
* @var string
*/
const SESSION_KEY = 'session';
/**
* Singleton instance
*
* @var self
*/
protected static $instance; protected static $instance;
/**
* Whether the platform is CLI
*
* @var bool
*/
protected $isCli = false; protected $isCli = false;
protected $session; /**
* Notification messages
*
* @var array
*/
protected $messages = array(); protected $messages = array();
public static function info($msg) /**
{ * Session
self::getInstance()->addMessage($msg, 'info'); *
} * @var Session
*/
public static function success($msg) protected $session;
{
self::getInstance()->addMessage($msg, 'success');
}
public static function warning($msg)
{
self::getInstance()->addMessage($msg, 'warning');
}
public static function error($msg)
{
self::getInstance()->addMessage($msg, 'error');
}
protected function addMessage($message, $type = 'info')
{
if (! in_array(
$type,
array(
'info',
'error',
'warning',
'success'
)
)) {
throw new ProgrammingError(
'"%s" is not a valid notification type',
$type
);
}
if ($this->isCli) {
$msg = sprintf('[%s] %s', $type, $message);
switch ($type) {
case 'info':
case 'success':
Logger::info($msg);
break;
case 'warning':
Logger::warn($msg);
break;
case 'error':
Logger::error($msg);
break;
}
return;
}
$this->messages[] = (object) array(
'type' => $type,
'message' => $message,
);
}
public function hasMessages()
{
return false === empty($this->messages);
}
public function getMessages()
{
$messages = $this->messages;
$this->messages = array();
return $messages;
}
/**
* Create the notification instance
*/
final private function __construct() final private function __construct()
{ {
if (Platform::isCli()) { if (Platform::isCli()) {
@ -105,33 +91,130 @@ class Notification
} }
$this->session = Session::getSession(); $this->session = Session::getSession();
$messages = $this->session->get(self::SESSION_KEY);
$stored = $this->session->get('messages'); if (is_array($messages)) {
if (is_array($stored)) { $this->messages = $messages;
$this->messages = $stored; $this->session->delete(self::SESSION_KEY);
$this->session->set('messages', array());
$this->session->write(); $this->session->write();
} }
} }
/**
* Get the Notification instance
*
* @return Notification
*/
public static function getInstance() public static function getInstance()
{ {
if (self::$instance === null) { if (self::$instance === null) {
self::$instance = new Notification(); self::$instance = new self();
} }
return self::$instance; return self::$instance;
} }
/**
* Add info notification
*
* @param string $msg
*/
public static function info($msg)
{
self::getInstance()->addMessage($msg, self::INFO);
}
/**
* Add error notification
*
* @param string $msg
*/
public static function error($msg)
{
self::getInstance()->addMessage($msg, self::ERROR);
}
/**
* Add success notification
*
* @param string $msg
*/
public static function success($msg)
{
self::getInstance()->addMessage($msg, self::SUCCESS);
}
/**
* Add warning notification
*
* @param string $msg
*/
public static function warning($msg)
{
self::getInstance()->addMessage($msg, self::WARNING);
}
/**
* Add a notification message
*
* @param string $message
* @param string $type
*/
protected function addMessage($message, $type = self::INFO)
{
if ($this->isCli) {
$msg = sprintf('[%s] %s', $type, $message);
switch ($type) {
case self::INFO:
case self::SUCCESS:
Logger::info($msg);
break;
case self::ERROR:
Logger::error($msg);
break;
case self::WARNING:
Logger::warning($msg);
break;
}
} else {
$this->messages[] = (object) array(
'type' => $type,
'message' => $message,
);
}
}
/**
* Pop the notification messages
*
* @return array
*/
public function popMessages()
{
$messages = $this->messages;
$this->messages = array();
return $messages;
}
/**
* Get whether notification messages have been added
*
* @return bool
*/
public function hasMessages()
{
return ! empty($this->messages);
}
/**
* Destroy the notification instance
*/
final public function __destruct() final public function __destruct()
{ {
if ($this->isCli) { if ($this->isCli) {
return; return;
} }
if ($this->session->get('messages') !== $this->messages) { if ($this->hasMessages() && $this->session->get('messages') !== $this->messages) {
$this->session->set('messages', $this->messages); $this->session->set(self::SESSION_KEY, $this->messages);
}
$this->session->write(); $this->session->write();
}
unset($this->session);
} }
} }

View File

@ -7,24 +7,46 @@ use Zend_Controller_Request_Http;
use Icinga\User; use Icinga\User;
/** /**
* Request to handle special attributes * A request
*/ */
class Request extends Zend_Controller_Request_Http class Request extends Zend_Controller_Request_Http
{ {
/** /**
* User object * User if authenticated
* *
* @var User * @var User|null
*/ */
private $user; protected $user;
/** /**
* Unique identifier
*
* @var string * @var string
*/ */
private $uniqueId; protected $uniqueId;
private $url; /**
* Request URL
*
* @var Url
*/
protected $url;
/**
* Get whether the request seems to be an API request
*
* @return bool
*/
public function getIsApiRequest()
{
return $this->getHeader('Accept') === 'application/json';
}
/**
* Get the request URL
*
* @return Url
*/
public function getUrl() public function getUrl()
{ {
if ($this->url === null) { if ($this->url === null) {
@ -34,31 +56,38 @@ class Request extends Zend_Controller_Request_Http
} }
/** /**
* Setter for user * Get the user if authenticated
* *
* @param User $user * @return User|null
*/
public function setUser(User $user)
{
$this->user = $user;
}
/**
* Getter for user
*
* @return User
*/ */
public function getUser() public function getUser()
{ {
return $this->user; return $this->user;
} }
/**
* Set the authenticated user
*
* @param User $user
*
* @return $this
*/
public function setUser(User $user)
{
$this->user = $user;
return $this;
}
/** /**
* Makes an ID unique to this request, to prevent id collisions in different containers * Makes an ID unique to this request, to prevent id collisions in different containers
* *
* Call this whenever an ID might show up multiple times in different containers. This function is useful * Call this whenever an ID might show up multiple times in different containers. This function is useful
* for ensuring unique ids on sites, even if we combine the HTML of different requests into one site, * for ensuring unique ids on sites, even if we combine the HTML of different requests into one site,
* while still being able to reference elements uniquely in the same request. * while still being able to reference elements uniquely in the same request.
*
* @param string $id
*
* @return string The id suffixed w/ an identifier unique to this request
*/ */
public function protectId($id) public function protectId($id)
{ {

View File

@ -8,18 +8,125 @@ use Icinga\Application\Icinga;
class Response extends Zend_Controller_Response_Http class Response extends Zend_Controller_Response_Http
{ {
/**
* Redirect URL
*
* @var Url|null
*/
protected $redirectUrl;
/**
* Request
*
* @var Request
*/
protected $request;
/**
* Whether to send the rerender layout header on XHR
*
* @var bool
*/
protected $rerenderLayout = false;
/**
* Get the redirect URL
*
* @return Url|null
*/
protected function getRedirectUrl()
{
return $this->redirectUrl;
}
/**
* Set the redirect URL
*
* Unlike {@link setRedirect()} this method only sets a redirect URL on the response for later usage.
* {@link prepare()} will take care of the correct redirect handling and HTTP headers on XHR and "normal" browser
* requests.
*
* @param string|Url $redirectUrl
*
* @return $this
*/
protected function setRedirectUrl($redirectUrl)
{
if (! $redirectUrl instanceof Url) {
$redirectUrl = Url::fromPath((string) $redirectUrl);
}
$redirectUrl->getParams()->setSeparator('&');
$this->redirectUrl = $redirectUrl;
return $this;
}
/**
* Get the request
*
* @return Request
*/
public function getRequest()
{
if ($this->request === null) {
$this->request = Icinga::app()->getRequest();
}
return $this->request;
}
/**
* Get whether to send the rerender layout header on XHR
*
* @return bool
*/
public function getRerenderLayout()
{
return $this->rerenderLayout;
}
/**
* Get whether to send the rerender layout header on XHR
*
* @param bool $rerenderLayout
*
* @return $this
*/
public function setRerenderLayout($rerenderLayout = true)
{
$this->rerenderLayout = (bool) $rerenderLayout;
return $this;
}
/**
* Prepare the request before sending
*/
protected function prepare()
{
$redirectUrl = $this->getRedirectUrl();
if ($this->getRequest()->isXmlHttpRequest()) {
if ($redirectUrl !== null) {
$this->setHeader('X-Icinga-Redirect', rawurlencode($redirectUrl->getAbsoluteUrl()), true);
if ($this->getRerenderLayout()) {
$this->setHeader('X-Icinga-Rerender-Layout', 'yes', true);
}
}
if ($this->getRerenderLayout()) {
$this->setHeader('X-Icinga-Container', 'layout', true);
}
} else {
if ($redirectUrl !== null) {
$this->setRedirect($redirectUrl->getAbsoluteUrl());
}
}
}
/**
* Redirect to the given URL and exit immediately
*
* @param string|Url $url
*/
public function redirectAndExit($url) public function redirectAndExit($url)
{ {
if (! $url instanceof Url) { $this->setRedirectUrl($url);
$url = Url::fromPath($url);
}
$url->getParams()->setSeparator('&');
if (Icinga::app()->getFrontController()->getRequest()->isXmlHttpRequest()) {
$this->setHeader('X-Icinga-Redirect', rawurlencode($url->getAbsoluteUrl()));
} else {
$this->setRedirect($url->getAbsoluteUrl());
}
$session = Session::getSession(); $session = Session::getSession();
if ($session->hasChanged()) { if ($session->hasChanged()) {
@ -29,4 +136,13 @@ class Response extends Zend_Controller_Response_Http
$this->sendHeaders(); $this->sendHeaders();
exit; exit;
} }
/**
* {@inheritdoc}
*/
public function sendHeaders()
{
$this->prepare();
return parent::sendHeaders();
}
} }

View File

@ -102,7 +102,7 @@ class Url
if ($app->isCli()) { if ($app->isCli()) {
return new FakeRequest(); return new FakeRequest();
} else { } else {
return $app->getFrontController()->getRequest(); return $app->getRequest();
} }
} }

View File

@ -5,7 +5,7 @@ namespace Icinga\Web;
use Closure; use Closure;
use Zend_View_Abstract; use Zend_View_Abstract;
use Icinga\Authentication\Manager; use Icinga\Authentication\Auth;
use Icinga\Exception\ProgrammingError; use Icinga\Exception\ProgrammingError;
use Icinga\Util\Translator; use Icinga\Util\Translator;
@ -39,7 +39,7 @@ class View extends Zend_View_Abstract
/** /**
* Authentication manager * Authentication manager
* *
* @var \Icinga\Authentication\Manager|null * @var Auth|null
*/ */
private $auth; private $auth;
@ -164,12 +164,12 @@ class View extends Zend_View_Abstract
/** /**
* Get the authentication manager * Get the authentication manager
* *
* @return Manager * @return Auth
*/ */
public function Auth() public function Auth()
{ {
if ($this->auth === null) { if ($this->auth === null) {
$this->auth = Manager::getInstance(); $this->auth = Auth::getInstance();
} }
return $this->auth; return $this->auth;
} }

View File

@ -3,11 +3,11 @@
namespace Icinga\Web\View; namespace Icinga\Web\View;
use Icinga\Authentication\Manager; use Icinga\Authentication\Auth;
use Icinga\Web\Widget; use Icinga\Web\Widget;
$this->addHelperFunction('auth', function () { $this->addHelperFunction('auth', function () {
return Manager::getInstance(); return Auth::getInstance();
}); });
$this->addHelperFunction('widget', function ($name, $options = null) { $this->addHelperFunction('widget', function ($name, $options = null) {

View File

@ -540,11 +540,17 @@ class FilterEditor extends AbstractWidget
); );
} }
public function setColumns(array $columns)
{
$this->cachedColumnSelect = $this->arrayForSelect($columns);
return $this;
}
protected function selectColumn(Filter $filter = null) protected function selectColumn(Filter $filter = null)
{ {
$active = $filter === null ? null : $filter->getColumn(); $active = $filter === null ? null : $filter->getColumn();
if ($this->query === null) { if ($this->cachedColumnSelect === null && $this->query === null) {
return sprintf( return sprintf(
'<input type="text" name="%s" value="%s" />', '<input type="text" name="%s" value="%s" />',
$this->elementId('column', $filter), $this->elementId('column', $filter),

View File

@ -116,7 +116,7 @@ class SortBox extends AbstractWidget
{ {
if ($this->query !== null) { if ($this->query !== null) {
if ($request === null) { if ($request === null) {
$request = Icinga::app()->getFrontController()->getRequest(); $request = Icinga::app()->getRequest();
} }
if (($sort = $request->getParam('sort'))) { if (($sort = $request->getParam('sort'))) {

View File

@ -91,6 +91,13 @@ class Tab extends AbstractWidget
*/ */
private $targetBlank = false; private $targetBlank = false;
/**
* Data base target that determines if the link will be opened in a side-bar or in the main container
*
* @var null
*/
private $baseTarget = null;
/** /**
* Sets an icon image for this tab * Sets an icon image for this tab
* *
@ -210,6 +217,11 @@ class Tab extends AbstractWidget
$this->targetBlank = $value; $this->targetBlank = $value;
} }
public function setBaseTarget($value)
{
$this->baseTarget = $value;
}
/** /**
* Create a new Tab with the given properties * Create a new Tab with the given properties
* *
@ -274,6 +286,10 @@ class Tab extends AbstractWidget
} }
} }
if ($this->baseTarget !== null) {
$tagParams['data-base-target'] = $this->baseTarget;
}
if ($this->icon !== null) { if ($this->icon !== null) {
if (strpos($this->icon, '.') === false) { if (strpos($this->icon, '.') === false) {
$caption = $view->icon($this->icon) . $caption; $caption = $view->icon($this->icon) . $caption;

View File

@ -309,7 +309,7 @@ EOT;
private function renderRefreshTab() private function renderRefreshTab()
{ {
$url = Icinga::app()->getFrontController()->getRequest()->getUrl(); $url = Icinga::app()->getRequest()->getUrl();
$tab = $this->get($this->getActiveName()); $tab = $this->get($this->getActiveName());
if ($tab !== null) { if ($tab !== null) {

View File

@ -15,7 +15,7 @@ if (count($servicegroups) === 0) {
return; return;
} }
?> ?>
<table class="groupview" data-base-target="_next"> <table class="groupview action" data-base-target="_next">
<thead> <thead>
<th><?= $this->translate('Last Problem'); ?></th> <th><?= $this->translate('Last Problem'); ?></th>
<th><?= $this->translate('Service Group'); ?></th> <th><?= $this->translate('Service Group'); ?></th>

View File

@ -3,7 +3,7 @@
namespace Icinga\Module\Monitoring\Web\Menu; namespace Icinga\Module\Monitoring\Web\Menu;
use Icinga\Authentication\Manager; use Icinga\Authentication\Auth;
use Icinga\Data\Filter\Filter; use Icinga\Data\Filter\Filter;
use Icinga\Data\Filterable; use Icinga\Data\Filterable;
use Icinga\Web\Menu; use Icinga\Web\Menu;
@ -27,7 +27,7 @@ class MonitoringMenuItemRenderer extends MenuItemRenderer
protected static function applyRestriction($restriction, Filterable $filterable) protected static function applyRestriction($restriction, Filterable $filterable)
{ {
$restrictions = Filter::matchAny(); $restrictions = Filter::matchAny();
foreach (Manager::getInstance()->getRestrictions($restriction) as $filter) { foreach (Auth::getInstance()->getRestrictions($restriction) as $filter) {
$restrictions->addFilter(Filter::fromQueryString($filter)); $restrictions->addFilter(Filter::fromQueryString($filter));
} }
$filterable->applyFilter($restrictions); $filterable->applyFilter($restrictions);

View File

@ -19,7 +19,7 @@ $maxProgress = @max(array_keys(array_filter(
$notifications = Notification::getInstance(); $notifications = Notification::getInstance();
if ($notifications->hasMessages()) { if ($notifications->hasMessages()) {
foreach ($notifications->getMessages() as $m) { foreach ($notifications->popMessages() as $m) {
echo '<li class="' . $m->type . '">' . $this->escape($m->message) . '</li>'; echo '<li class="' . $m->type . '">' . $this->escape($m->message) . '</li>';
} }
} }

View File

@ -313,13 +313,16 @@
*/ */
ActionTable.prototype.onRowClicked = function (event) { ActionTable.prototype.onRowClicked = function (event) {
var self = event.data.self; var self = event.data.self;
var $tr = $(event.target).closest('tr'); var $target = $(event.target);
var $tr = $target.closest('tr');
var table = new Selection($tr.closest('table.action')[0], self.icinga); var table = new Selection($tr.closest('table.action')[0], self.icinga);
// allow form actions in table rows to pass through // some rows may contain form actions that trigger a different action, pass those through
if ($(event.target).closest('form').length) { if (!$target.hasClass('rowaction') && $target.closest('form').length &&
($target.closest('a').length || $target.closest('button').length)) {
return; return;
} }
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();

View File

@ -354,18 +354,16 @@
return true; return true;
} }
// Special checks for link clicks in multiselect rows // Special checks for link clicks in action tables
if (! $a.is('tr[href]') && $a.closest('tr[href]').length > 0 && $a.closest('table.multiselect').length > 0) { if (! $a.is('tr[href]') && $a.closest('table.action').length > 0) {
// ignoray clicks to ANY link with special key pressed // ignoray clicks to ANY link with special key pressed
if (event.ctrlKey || event.metaKey || event.shiftKey) if ($a.closest('table.multiselect').length > 0 && (event.ctrlKey || event.metaKey || event.shiftKey)) {
{
return true; return true;
} }
// ignore inner links matching the row URL // ignore inner links matching the row URL
if ($a.attr('href') === $a.closest('tr[href]').attr('href')) if ($a.attr('href') === $a.closest('tr[href]').attr('href')) {
{
return true; return true;
} }
} }

View File

@ -139,8 +139,10 @@
icinga.logger.debug('History state', event.originalEvent.state); icinga.logger.debug('History state', event.originalEvent.state);
} }
self.applyLocationBar(); // keep the last pushed url in sync with history changes
self.lastPushUrl = location.href;
self.applyLocationBar();
}, },
applyLocationBar: function (onload) { applyLocationBar: function (onload) {

View File

@ -587,8 +587,6 @@
if (req.$target[0].id === 'col1') { if (req.$target[0].id === 'col1') {
self.icinga.behaviors.navigation.resetActive(); self.icinga.behaviors.navigation.resetActive();
} }
} else if ($(el).closest('table.action').length) {
$(el).closest('table.action').find('.active').removeClass('active');
} }
}); });
@ -604,8 +602,6 @@
} }
// Interrupt .each, only on menu item shall be active // Interrupt .each, only on menu item shall be active
return false; return false;
} else if ($(el).closest('table.action').length) {
$el.addClass('active');
} }
}); });
} }