Merge branch 'feature/secure-modules-9644'

resolves #9644
This commit is contained in:
Eric Lippmann 2015-07-28 14:00:27 +02:00
commit d2467fee16
22 changed files with 417 additions and 365 deletions

View File

@ -95,6 +95,7 @@ class ConfigController extends Controller
*/
public function modulesAction()
{
$this->assertPermission('config/modules');
// Overwrite tabs created in init
// @TODO(el): This seems not natural to me. Module configuration should have its own controller.
$this->view->tabs = Widget::create('tabs')
@ -120,6 +121,7 @@ class ConfigController extends Controller
public function moduleAction()
{
$this->assertPermission('config/modules');
$app = Icinga::app();
$manager = $app->getModuleManager();
$name = $this->getParam('name');

View File

@ -12,7 +12,9 @@ class SearchController extends ActionController
{
public function indexAction()
{
$this->view->dashboard = SearchDashboard::search($this->params->get('q'));
$searchDashboard = new SearchDashboard();
$searchDashboard->setUser($this->Auth()->getUser());
$this->view->dashboard = $searchDashboard->search($this->params->get('q'));
// NOTE: This renders the dashboard twice. Remove this once we can catch exceptions thrown in view scripts.
$this->view->dashboard->render();

View File

@ -63,24 +63,34 @@ class RoleForm extends ConfigForm
public function init()
{
$helper = new Zend_Form_Element('bogus');
foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) {
$mm = Icinga::app()->getModuleManager();
foreach ($mm->listInstalledModules() as $moduleName) {
$modulePermission = $mm::MODULE_PERMISSION_NS . $moduleName;
$this->providedPermissions[$modulePermission] = sprintf(
$this->translate('Allow access to module %s') . ' (%s)',
$moduleName,
$modulePermission
);
$module = $mm->getModule($moduleName, false);
foreach ($module->getProvidedPermissions() as $permission) {
/** @var object $permission */
$this->providedPermissions[$permission->name] = $permission->description . ' (' . $permission->name . ')';
$this->providedPermissions[$permission->name] = $permission->description
. ' (' . $permission->name . ')';
}
foreach ($module->getProvidedRestrictions() as $restriction) {
/** @var object $restriction */
$name = $helper->filterName($restriction->name); // Zend only permits alphanumerics, the underscore,
// the circumflex and any ASCII character in range
// \x7f to \xff (127 to 255)
// Zend only permits alphanumerics, the underscore, the circumflex and any ASCII character in range
// \x7f to \xff (127 to 255)
$name = $helper->filterName($restriction->name);
while (isset($this->providedRestrictions[$name])) {
// Because Zend_Form_Element::filterName() replaces any not permitted character with the empty
// string we may have duplicate names, e.g. 're/striction' and 'restriction'
$name .= '_';
}
$this->providedRestrictions[$name] = array(
'description' => $restriction->description,
'name' => $restriction->name
'description' => $restriction->description,
'name' => $restriction->name
);
}
}

View File

@ -1,8 +1,11 @@
<?php
use Icinga\Web\Widget\SearchDashboard;
?>
<? if (SearchDashboard::search('dummy')->getPane('search')->hasDashlets()): ?>
use Icinga\Web\Widget\SearchDashboard;
$searchDashboard = new SearchDashboard();
$searchDashboard->setUser($this->Auth()->getUser());
if ($searchDashboard->search('dummy')->getPane('search')->hasDashlets()): ?>
<form action="<?= $this->href('search') ?>" method="get" role="search">
<input
type="text" name="q" id="search" class="search" placeholder="<?= $this->translate('Search') ?> &hellip;"

View File

@ -290,3 +290,9 @@ The first release candidate of Icinga Web 2 introduces the following non-backwar
the [EPEL repository](http://fedoraproject.org/wiki/EPEL). Before, Zend was installed as Icinga Web 2 vendor library
through the package `icingaweb2-vendor-zend`. After upgrading, please make sure to remove the package
`icingaweb2-vendor-zend`.
* Icinga Web 2 version 2.0.0 requires permissions for accessing modules. Those permissions are automatically generated
for each installed module in the format `module/<moduleName>`. Administrators have to grant the module permissions to
users and/or user groups in the roles configuration for permitting access to specific modules.
In addition, restrictions provided by modules are now configurable for each installed module too. Before,
a module had to be enabled before having the possibility to configure restrictions.

View File

@ -149,22 +149,26 @@ are in the namespace `config/modules`
The permission `config/*` would grant permission to all configuration actions,
while just specifying a wildcard `*` would give permission for all actions.
Access to modules is restricted to users who have the related module permission granted. Icinga Web 2 provides
a module permission in the format `module/<moduleName>` for each installed module.
When multiple roles assign permissions to the same user (either directly or indirectly
through a group) all permissions can simply be added together to get the users actual permission set.
through a group) all permissions are added together to get the users actual permission set.
#### Global permissions
### Global Permissions
Name | Permits
-------------------------------------|-----------------------------------------------------------------
* | Allow everything, including module-specific permissions
config/* | Allow all configuration actions
config/modules | Allow enabling or disabling modules
Name | Permits
--------------- ----|--------------------------------------------------------
* | Allow everything, including module-specific permissions
config/* | Allow all configuration actions
config/modules | Allow enabling or disabling modules
module/<moduleName> | Allow access to module <moduleName>
#### Monitoring module permissions
### Monitoring Module Permissions
The built-in monitoring module defines an additional set of permissions, that
is described in detail in [monitoring module documentation](/icingaweb2/doc/module/doc/chapter/monitoring-security#monitoring-security).
is described in detail in the [monitoring module documentation](/icingaweb2/doc/module/doc/chapter/monitoring-security#monitoring-security).
## <a id="restrictions"></a> Restrictions

View File

@ -24,6 +24,13 @@ use Icinga\Exception\NotReadableError;
*/
class Manager
{
/**
* Namespace for module permissions
*
* @var string
*/
const MODULE_PERMISSION_NS = 'module/';
/**
* Array of all installed module's base directories
*
@ -401,22 +408,25 @@ class Manager
}
/**
* Return the module instance of the given module when it is loaded
* Get a module
*
* @param string $name The module name to return
* @param string $name Name of the module
* @param bool $assertLoaded Whether or not to throw an exception if the module hasn't been loaded
*
* @return Module
* @throws ProgrammingError When the module hasn't been loaded
* @throws ProgrammingError If the module hasn't been loaded
*/
public function getModule($name)
public function getModule($name, $assertLoaded = true)
{
if (!$this->hasLoaded($name)) {
throw new ProgrammingError(
'Cannot access module %s as it hasn\'t been loaded',
$name
);
if ($this->hasLoaded($name)) {
return $this->loadedModules[$name];
} elseif (! (bool) $assertLoaded) {
return new Module($this->app, $name, $this->getModuleDir($name));
}
return $this->loadedModules[$name];
throw new ProgrammingError(
'Can\'t access module %s because it hasn\'t been loaded',
$name
);
}
/**

View File

@ -102,7 +102,7 @@ class Module
/**
* Module metadata (version...)
*
* @var stdClass
* @var object
*/
private $metadata;
@ -113,6 +113,13 @@ class Module
*/
private $triedToLaunchConfigScript = false;
/**
* Whether the module's namespaces have been registered on our autoloader
*
* @var bool
*/
protected $registeredAutoloader = false;
/**
* Whether this module has been registered
*
@ -199,87 +206,6 @@ class Module
*/
protected $userGroupBackends = array();
/**
* Provide a search URL
*
* @param string $title
* @param string $url
* @param int $priority
*/
public function provideSearchUrl($title, $url, $priority = 0)
{
$searchUrl = (object) array(
'title' => (string) $title,
'url' => (string) $url,
'priority' => (int) $priority
);
$this->searchUrls[] = $searchUrl;
}
/**
* Return this module's search urls
*
* @return array
*/
public function getSearchUrls()
{
$this->launchConfigScript();
return $this->searchUrls;
}
/**
* Get all Menu Items
*
* @return array
*/
public function getPaneItems()
{
$this->launchConfigScript();
return $this->paneItems;
}
/**
* Add a pane to dashboard
*
* @param $name
* @return Pane
*/
protected function dashboard($name)
{
$this->paneItems[$name] = new Pane($name);
return $this->paneItems[$name];
}
/**
* Get all Menu Items
*
* @return array
*/
public function getMenuItems()
{
$this->launchConfigScript();
return $this->menuItems;
}
/**
* Add a menu Section to the Sidebar menu
*
* @param $name
* @param array $properties
* @return mixed
*/
protected function menuSection($name, array $properties = array())
{
if (array_key_exists($name, $this->menuItems)) {
$this->menuItems[$name]->setProperties($properties);
} else {
$this->menuItems[$name] = new Menu($name, new ConfigObject($properties));
}
return $this->menuItems[$name];
}
/**
* Create a new module object
*
@ -304,6 +230,91 @@ class Module
$this->metadataFile = $basedir . '/module.info';
}
/**
* Provide a search URL
*
* @param string $title
* @param string $url
* @param int $priority
*
* @return $this
*/
public function provideSearchUrl($title, $url, $priority = 0)
{
$this->searchUrls[] = (object) array(
'title' => (string) $title,
'url' => (string) $url,
'priority' => (int) $priority
);
return $this;
}
/**
* Get this module's search urls
*
* @return array
*/
public function getSearchUrls()
{
$this->launchConfigScript();
return $this->searchUrls;
}
/**
* Get all pane items
*
* @return array
*/
public function getPaneItems()
{
$this->launchConfigScript();
return $this->paneItems;
}
/**
* Add a pane to dashboard
*
* @param string $name
*
* @return Pane
*/
protected function dashboard($name)
{
$this->paneItems[$name] = new Pane($name);
return $this->paneItems[$name];
}
/**
* Get all menu items
*
* @return array
*/
public function getMenuItems()
{
$this->launchConfigScript();
return $this->menuItems;
}
/**
* Add or get a menu section
*
* @param string $name
* @param array $properties
*
* @return Menu
*/
protected function menuSection($name, array $properties = array())
{
if (array_key_exists($name, $this->menuItems)) {
$this->menuItems[$name]->setProperties($properties);
} else {
$this->menuItems[$name] = new Menu($name, new ConfigObject($properties));
}
return $this->menuItems[$name];
}
/**
* Register module
*
@ -327,16 +338,16 @@ class Module
);
return false;
}
$this->registerWebIntegration();
$this->registered = true;
return true;
}
/**
* Return whether this module has been registered
* Get whether this module has been registered
*
* @return bool
* @return bool
*/
public function isRegistered()
{
@ -346,9 +357,9 @@ class Module
/**
* Test for an enabled module by name
*
* @param string $name
* @param string $name
*
* @return boolean
* @return bool
*/
public static function exists($name)
{
@ -356,7 +367,7 @@ class Module
}
/**
* Get module by name
* Get a module by name
*
* @param string $name
* @param bool $autoload
@ -418,7 +429,7 @@ class Module
}
/**
* Getter for module name
* Get the module name
*
* @return string
*/
@ -428,7 +439,7 @@ class Module
}
/**
* Getter for module version
* Get the module version
*
* @return string
*/
@ -438,7 +449,7 @@ class Module
}
/**
* Get module description
* Get the module description
*
* @return string
*/
@ -448,7 +459,7 @@ class Module
}
/**
* Get module title (short description)
* Get the module title (short description)
*
* @return string
*/
@ -458,9 +469,9 @@ class Module
}
/**
* Getter for module version
* Get the module dependencies
*
* @return Array
* @return array
*/
public function getDependencies()
{
@ -555,7 +566,7 @@ class Module
}
/**
* Getter for css file name
* Get the module's CSS directory
*
* @return string
*/
@ -565,17 +576,7 @@ class Module
}
/**
* Getter for base directory
*
* @return string
*/
public function getBaseDir()
{
return $this->basedir;
}
/**
* Get the controller directory
* Get the module's controller directory
*
* @return string
*/
@ -585,7 +586,17 @@ class Module
}
/**
* Getter for library directory
* Get the module's base directory
*
* @return string
*/
public function getBaseDir()
{
return $this->basedir;
}
/**
* Get the module's library directory
*
* @return string
*/
@ -595,7 +606,7 @@ class Module
}
/**
* Getter for configuration directory
* Get the module's configuration directory
*
* @return string
*/
@ -605,7 +616,7 @@ class Module
}
/**
* Getter for form directory
* Get the module's form directory
*
* @return string
*/
@ -615,11 +626,11 @@ class Module
}
/**
* Getter for module config object
* Get the module config
*
* @param string $file
* @param string $file
*
* @return Config
* @return Config
*/
public function getConfig($file = 'config')
{
@ -627,9 +638,7 @@ class Module
}
/**
* Retrieve provided permissions
*
* @param string $name Permission name
* Get provided permissions
*
* @return array
*/
@ -640,9 +649,8 @@ class Module
}
/**
* Retrieve provided restrictions
* Get provided restrictions
*
* @param string $name Restriction name
* @return array
*/
public function getProvidedRestrictions()
@ -652,24 +660,11 @@ class Module
}
/**
* Whether the given permission name is supported
* Whether the module provides the given restriction
*
* @param string $name Permission name
* @param string $name Restriction name
*
* @return bool
*/
public function providesPermission($name)
{
$this->launchConfigScript();
return array_key_exists($name, $this->permissionList);
}
/**
* Whether the given restriction name is supported
*
* @param string $name Restriction name
*
* @return bool
* @return bool
*/
public function providesRestriction($name)
{
@ -678,9 +673,22 @@ class Module
}
/**
* Retrieve this modules configuration tabs
* Whether the module provides the given permission
*
* @return Icinga\Web\Widget\Tabs
* @param string $name Permission name
*
* @return bool
*/
public function providesPermission($name)
{
$this->launchConfigScript();
return array_key_exists($name, $this->permissionList);
}
/**
* Get the module configuration tabs
*
* @return \Icinga\Web\Widget\Tabs
*/
public function getConfigTabs()
{
@ -698,9 +706,9 @@ class Module
}
/**
* Whether this module provides a setup wizard
* Whether the module provides a setup wizard
*
* @return bool
* @return bool
*/
public function providesSetupWizard()
{
@ -714,9 +722,9 @@ class Module
}
/**
* Return this module's setup wizard
* Get the module's setup wizard
*
* @return SetupWizard
* @return SetupWizard
*/
public function getSetupWizard()
{
@ -724,9 +732,9 @@ class Module
}
/**
* Return this module's user backends
* Get the module's user backends
*
* @return array
* @return array
*/
public function getUserBackends()
{
@ -735,9 +743,9 @@ class Module
}
/**
* Return this module's user group backends
* Get the module's user group backends
*
* @return array
* @return array
*/
public function getUserGroupBackends()
{
@ -748,10 +756,10 @@ class Module
/**
* Provide a named permission
*
* @param string $name Unique permission name
* @param string $name Permission description
* @param string $name Unique permission name
* @param string $description Permission description
*
* @return void
* @throws IcingaException If the permission is already provided
*/
protected function providePermission($name, $description)
{
@ -770,10 +778,10 @@ class Module
/**
* Provide a named restriction
*
* @param string $name Unique restriction name
* @param string $description Restriction description
* @param string $name Unique restriction name
* @param string $description Restriction description
*
* @return void
* @throws IcingaException If the restriction is already provided
*/
protected function provideRestriction($name, $description)
{
@ -792,15 +800,16 @@ class Module
/**
* Provide a module config tab
*
* @param string $name Unique tab name
* @param string $config Tab config
* @param string $name Unique tab name
* @param array $config Tab config
*
* @return $this
* @return $this
* @throws ProgrammingError If $config lacks the key 'url'
*/
protected function provideConfigTab($name, $config = array())
{
if (! array_key_exists('url', $config)) {
throw new ProgrammingError('A module config tab MUST provide and "url"');
throw new ProgrammingError('A module config tab MUST provide a "url"');
}
$config['url'] = $this->getName() . '/' . ltrim($config['url'], '/');
$this->configTabs[$name] = $config;
@ -810,7 +819,7 @@ class Module
/**
* Provide a setup wizard
*
* @param string $className The name of the class
* @param string $className The name of the class
*
* @return $this
*/
@ -823,8 +832,8 @@ class Module
/**
* Provide a user backend capable of authenticating users
*
* @param string $identifier The identifier of the new backend type
* @param string $className The name of the class
* @param string $identifier The identifier of the new backend type
* @param string $className The name of the class
*
* @return $this
*/
@ -837,8 +846,8 @@ class Module
/**
* Provide a user group backend
*
* @param string $identifier The identifier of the new backend type
* @param string $className The name of the class
* @param string $identifier The identifier of the new backend type
* @param string $className The name of the class
*
* @return $this
*/
@ -849,12 +858,16 @@ class Module
}
/**
* Register new namespaces on the autoloader
* Register module namespaces on the autoloader
*
* @return $this
*/
protected function registerAutoloader()
{
if ($this->registeredAutoloader) {
return $this;
}
$moduleName = ucfirst($this->getName());
$moduleLibraryDir = $this->getLibDir(). '/'. $moduleName;
@ -867,6 +880,8 @@ class Module
$this->app->getLoader()->registerNamespace('Icinga\\Module\\' . $moduleName. '\\Forms', $moduleFormDir);
}
$this->registeredAutoloader = true;
return $this;
}
@ -884,7 +899,7 @@ class Module
}
/**
* return bool Whether this module has translations
* Get whether the module has translations
*/
public function hasLocales()
{
@ -894,7 +909,7 @@ class Module
/**
* List all available locales
*
* return array Locale list
* @return array Locale list
*/
public function listLocales()
{
@ -941,10 +956,9 @@ class Module
}
/**
* Add routes for static content and any route added via addRoute() to the route chain
* Add routes for static content and any route added via {@link addRoute()} to the route chain
*
* @return $this
* @see addRoute()
* @return $this
*/
protected function registerRoutes()
{
@ -993,14 +1007,14 @@ class Module
/**
* Include a php script if it is readable
*
* @param string $file File to include
* @param string $file File to include
*
* @return $this
* @return $this
*/
protected function includeScript($file)
{
if (file_exists($file) && is_readable($file) === true) {
include($file);
if (file_exists($file) && is_readable($file)) {
include $file;
}
return $this;
@ -1008,28 +1022,27 @@ class Module
/**
* Run module config script
*
* @return $this
*/
protected function launchConfigScript()
{
if ($this->triedToLaunchConfigScript || !$this->registered) {
return;
if ($this->triedToLaunchConfigScript) {
return $this;
}
$this->triedToLaunchConfigScript = true;
if (! file_exists($this->configScript)
|| ! is_readable($this->configScript)) {
return;
}
include($this->configScript);
$this->registerAutoloader();
return $this->includeScript($this->configScript);
}
/**
* Register hook
*
* @param string $name
* @param string $class
* @param string $key
* @param string $name
* @param string $class
* @param string $key
*
* @return $this
* @return $this
*/
protected function registerHook($name, $class, $key = null)
{
@ -1058,12 +1071,8 @@ class Module
}
/**
* Translate a string with the global mt()
*
* @param $string
* @param null $context
*
* @return mixed|string
* (non-PHPDoc)
* @see Translator::translate() For the function documentation.
*/
protected function translate($string, $context = null)
{

View File

@ -4,17 +4,19 @@
namespace Icinga\Exception;
use Exception;
use ReflectionClass;
class IcingaException extends Exception
{
/**
* @param string $message format string for vsprintf()
* Any futher args: args for vsprintf()
* @see vsprintf
* Create a new exception
*
* If there is at least one exception, the last one will be also used for the exception chaining.
* @param string $message Exception message or exception format string
* @param mixed ...$arg Format string argument
*
* If there is at least one exception, the last one will be used for exception chaining.
*/
public function __construct($message = '')
public function __construct($message)
{
$args = array_slice(func_get_args(), 1);
$exc = null;
@ -26,6 +28,19 @@ class IcingaException extends Exception
parent::__construct(vsprintf($message, $args), 0, $exc);
}
/**
* Create the exception from an array of arguments
*
* @param array $args
*
* @return static
*/
public static function create(array $args)
{
$e = new ReflectionClass(get_called_class());
return $e->newInstanceArgs($args);
}
/**
* Return the given exception formatted as one-liner
*

View File

@ -53,13 +53,14 @@ class Controller extends ModuleActionController
/**
* Immediately respond w/ HTTP 404
*
* @param $message
* @param string $message Exception message or exception format string
* @param mixed ...$arg Format string argument
*
* @throws HttpNotFoundException
*/
public function httpNotFound($message)
{
throw new HttpNotFoundException($message);
throw HttpNotFoundException::create(func_get_args());
}
/**

View File

@ -155,7 +155,7 @@ class ActionController extends Zend_Controller_Action
*/
public function assertPermission($permission)
{
if (! $this->Auth()->hasPermission($permission)) {
if ($this->requiresAuthentication && ! $this->Auth()->hasPermission($permission)) {
throw new SecurityException('No permission for %s', $permission);
}
}

View File

@ -5,6 +5,7 @@ namespace Icinga\Web\Controller;
use Icinga\Application\Config;
use Icinga\Application\Icinga;
use Icinga\Application\Modules\Manager;
/**
* Base class for module action controllers
@ -34,6 +35,9 @@ class ModuleActionController extends ActionController
$this->_helper->layout()->moduleName = $this->moduleName;
$this->view->translationDomain = $this->moduleName;
$this->moduleInit();
if ($this->getFrontController()->getDefaultModule() !== $this->moduleName) {
$this->assertPermission(Manager::MODULE_PERMISSION_NS . $this->moduleName);
}
}
/**

View File

@ -206,13 +206,14 @@ class Menu implements RecursiveIterator
*/
public static function load()
{
/** @var $menu \Icinga\Web\Menu */
$menu = new static('menu');
$menu->addMainMenuItems();
$auth = Manager::getInstance();
$manager = Icinga::app()->getModuleManager();
foreach ($manager->getLoadedModules() as $module) {
/** @var $module \Icinga\Application\Modules\Module */
$menu->mergeSubMenus($module->getMenuItems());
if ($auth->hasPermission($manager::MODULE_PERMISSION_NS . $module->getName())) {
$menu->mergeSubMenus($module->getMenuItems());
}
}
return $menu->order();
}

View File

@ -70,13 +70,13 @@ class Dashboard extends AbstractWidget
{
$manager = Icinga::app()->getModuleManager();
foreach ($manager->getLoadedModules() as $module) {
/** @var $module \Icinga\Application\Modules\Module */
$this->mergePanes($module->getPaneItems());
if ($this->getUser()->can($manager::MODULE_PERMISSION_NS . $module->getName())) {
$this->mergePanes($module->getPaneItems());
}
}
if ($this->user !== null) {
$this->loadUserDashboards();
}
$this->loadUserDashboards();
return $this;
}
@ -90,11 +90,11 @@ class Dashboard extends AbstractWidget
{
$output = array();
foreach ($this->panes as $pane) {
if ($pane->isUserWidget() === true) {
if ($pane->isUserWidget()) {
$output[$pane->getName()] = $pane->toArray();
}
foreach ($pane->getDashlets() as $dashlet) {
if ($dashlet->isUserWidget() === true) {
if ($dashlet->isUserWidget()) {
$output[$pane->getName() . '.' . $dashlet->getTitle()] = $dashlet->toArray();
}
}

View File

@ -12,6 +12,11 @@ use Icinga\Web\Url;
*/
class SearchDashboard extends Dashboard
{
/**
* Name for the search pane
*
* @var string
*/
const SEARCH_PANE = 'search';
/**
@ -19,13 +24,39 @@ class SearchDashboard extends Dashboard
*
* @param string $searchString
*
* @return Dashboard|SearchDashboard
* @return $this
*/
public static function search($searchString = '')
public function search($searchString = '')
{
$dashboard = new static('searchDashboard');
$dashboard->loadSearchDashlets($searchString);
return $dashboard;
$pane = $this->createPane(self::SEARCH_PANE)->getPane(self::SEARCH_PANE)->setTitle(t('Search'));
$this->activate(self::SEARCH_PANE);
$manager = Icinga::app()->getModuleManager();
$searchUrls = array();
foreach ($manager->getLoadedModules() as $module) {
if ($this->getUser()->can($manager::MODULE_PERMISSION_NS . $module->getName())) {
$moduleSearchUrls = $module->getSearchUrls();
if (! empty($moduleSearchUrls)) {
if ($searchString === '') {
$pane->add(t('Ready to search'), 'search/hint');
return $this;
}
$searchUrls = array_merge($searchUrls, $moduleSearchUrls);
}
}
}
usort($searchUrls, array($this, 'compareSearchUrls'));
foreach (array_reverse($searchUrls) as $searchUrl) {
$pane->addDashlet(
$searchUrl->title . ': ' . $searchString,
Url::fromPath($searchUrl->url, array('q' => $searchString))
);
}
return $this;
}
/**
@ -43,40 +74,6 @@ class SearchDashboard extends Dashboard
return parent::render();
}
/**
* Loads search dashlets
*
* @param string $searchString
*/
protected function loadSearchDashlets($searchString)
{
$pane = $this->createPane(self::SEARCH_PANE)->getPane(self::SEARCH_PANE)->setTitle(t('Search'));
$this->activate(self::SEARCH_PANE);
$manager = Icinga::app()->getModuleManager();
$searchUrls = array();
foreach ($manager->getLoadedModules() as $module) {
$moduleSearchUrls = $module->getSearchUrls();
if (! empty($moduleSearchUrls)) {
if ($searchString === '') {
$pane->add(t('Ready to search'), 'search/hint');
return;
}
$searchUrls = array_merge($searchUrls, $moduleSearchUrls);
}
}
usort($searchUrls, array($this, 'compareSearchUrls'));
foreach (array_reverse($searchUrls) as $searchUrl) {
$pane->addDashlet(
$searchUrl->title . ': ' . $searchString,
Url::fromPath($searchUrl->url, array('q' => $searchString))
);
}
}
/**
* Compare search URLs based on their priority
*

View File

@ -38,17 +38,11 @@ class Doc_IcingawebController extends DocController
/**
* View a chapter of Icinga Web 2's documentation
*
* @throws Zend_Controller_Action_Exception If the required parameter 'chapterId' is missing
* @throws \Icinga\Exception\MissingParameterException If the required parameter 'chapter' is missing
*/
public function chapterAction()
{
$chapter = $this->getParam('chapter');
if ($chapter === null) {
throw new Zend_Controller_Action_Exception(
sprintf($this->translate('Missing parameter %s'), 'chapter'),
404
);
}
$chapter = $this->params->getRequired('chapter');
$this->renderChapter(
$this->getPath(),
$chapter,

View File

@ -10,16 +10,15 @@ class Doc_ModuleController extends DocController
/**
* Get the path to a module documentation
*
* @param string $module The name of the module
* @param string $default The default path
* @param bool $suppressErrors Whether to not throw an exception if the module documentation is not
* available
* @param string $module The name of the module
* @param string $default The default path
* @param bool $suppressErrors Whether to not throw an exception if the module documentation is not available
*
* @return string|null Path to the documentation or null if the module documentation is not
* available and errors are suppressed
* @return string|null Path to the documentation or null if the module documentation is not available
* and errors are suppressed
*
* @throws Zend_Controller_Action_Exception If the module documentation is not available and errors are not
* suppressed
* @throws \Icinga\Exception\Http\HttpNotFoundException If the module documentation is not available and errors
* are not suppressed
*/
protected function getPath($module, $default, $suppressErrors = false)
{
@ -35,10 +34,7 @@ class Doc_ModuleController extends DocController
if ($suppressErrors) {
return null;
}
throw new Zend_Controller_Action_Exception(
sprintf($this->translate('Documentation for module \'%s\' is not available'), $module),
404
);
$this->httpNotFound($this->translate('Documentation for module \'%s\' is not available'), $module);
}
/**
@ -48,55 +44,41 @@ class Doc_ModuleController extends DocController
{
$moduleManager = Icinga::app()->getModuleManager();
$modules = array();
foreach ($moduleManager->listEnabledModules() as $module) {
foreach ($moduleManager->listInstalledModules() as $module) {
$path = $this->getPath($module, $moduleManager->getModuleDir($module, '/doc'), true);
if ($path !== null) {
$modules[] = $moduleManager->getModule($module);
$modules[] = $moduleManager->getModule($module, false);
}
}
$this->view->modules = $modules;
}
/**
* Assert that the given module is enabled
* Assert that the given module is installed
*
* @param $moduleName
* @param string $moduleName
*
* @throws Zend_Controller_Action_Exception If the required parameter 'moduleName' is empty or either if the
* given module is neither installed nor enabled
* @throws \Icinga\Exception\Http\HttpNotFoundException If the given module is not installed
*/
protected function assertModuleEnabled($moduleName)
protected function assertModuleInstalled($moduleName)
{
if (empty($moduleName)) {
throw new Zend_Controller_Action_Exception(
sprintf($this->translate('Missing parameter \'%s\''), 'moduleName'),
404
);
}
$moduleManager = Icinga::app()->getModuleManager();
if (! $moduleManager->hasInstalled($moduleName)) {
throw new Zend_Controller_Action_Exception(
sprintf($this->translate('Module \'%s\' is not installed'), $moduleName),
404
);
}
if (! $moduleManager->hasEnabled($moduleName)) {
throw new Zend_Controller_Action_Exception(
sprintf($this->translate('Module \'%s\' is not enabled'), $moduleName),
404
);
$this->httpNotFound($this->translate('Module \'%s\' is not installed'), $moduleName);
}
}
/**
* View the toc of a module's documentation
*
* @see assertModuleEnabled()
* @throws \Icinga\Exception\MissingParameterException If the required parameter 'moduleName' is empty
* @throws \Icinga\Exception\Http\HttpNotFoundException If the given module is not installed
* @see assertModuleInstalled()
*/
public function tocAction()
{
$module = $this->getParam('moduleName');
$this->assertModuleEnabled($module);
$module = $this->params->getRequired('moduleName');
$this->assertModuleInstalled($module);
$this->view->moduleName = $module;
try {
$this->renderToc(
@ -106,28 +88,23 @@ class Doc_ModuleController extends DocController
array('moduleName' => $module)
);
} catch (DocException $e) {
throw new Zend_Controller_Action_Exception($e->getMessage(), 404);
$this->httpNotFound($e->getMessage());
}
}
/**
* View a chapter of a module's documentation
*
* @throws Zend_Controller_Action_Exception If the required parameter 'chapterId' is missing or if an error in
* the documentation module's library occurs
* @see assertModuleEnabled()
* @throws \Icinga\Exception\MissingParameterException If one of the required parameters 'moduleName' and
* 'chapter' is empty
* @throws \Icinga\Exception\Http\HttpNotFoundException If the given module is not installed
* @see assertModuleInstalled()
*/
public function chapterAction()
{
$module = $this->getParam('moduleName');
$this->assertModuleEnabled($module);
$chapter = $this->getParam('chapter');
if ($chapter === null) {
throw new Zend_Controller_Action_Exception(
sprintf($this->translate('Missing parameter %s'), 'chapter'),
404
);
}
$module = $this->params->getRequired('moduleName');
$this->assertModuleInstalled($module);
$chapter = $this->params->getRequired('chapter');
$this->view->moduleName = $module;
try {
$this->renderChapter(
@ -137,19 +114,21 @@ class Doc_ModuleController extends DocController
array('moduleName' => $module)
);
} catch (DocException $e) {
throw new Zend_Controller_Action_Exception($e->getMessage(), 404);
$this->httpNotFound($e->getMessage());
}
}
/**
* View a module's documentation as PDF
*
* @see assertModuleEnabled()
* @throws \Icinga\Exception\MissingParameterException If the required parameter 'moduleName' is empty
* @throws \Icinga\Exception\Http\HttpNotFoundException If the given module is not installed
* @see assertModuleInstalled()
*/
public function pdfAction()
{
$module = $this->getParam('moduleName');
$this->assertModuleEnabled($module);
$module = $this->params->getRequired('moduleName');
$this->assertModuleInstalled($module);
$this->renderPdf(
$this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')),
$module,

View File

@ -92,9 +92,6 @@ class Doc_SearchController extends DocController
return $path;
}
}
throw new Zend_Controller_Action_Exception(
$this->translate('Documentation for Icinga Web 2 is not available'),
404
);
$this->httpNotFound($this->translate('Documentation for Icinga Web 2 is not available'));
}
}

View File

@ -14,7 +14,7 @@
$this->translate('Module documentations'),
'doc/module/',
null,
array('title' => $this->translate('List all modifications for which documentation is available'))
array('title' => $this->translate('List all modules for which documentation is available'))
); ?></li>
</ul>
</div>

View File

@ -4,7 +4,7 @@
</div>
<div class="content">
<ul>
<?php foreach ($modules as $module): ?>
<?php foreach ($modules as $module): /** @var \Icinga\Application\Modules\Module $module */ ?>
<li><?= $this->qlink(
$module->getTitle(),
'doc/module/toc',

View File

@ -8,11 +8,11 @@ namespace Tests\Icinga\Web;
require_once realpath(dirname(__FILE__) . '/../../../../bootstrap.php');
use Mockery;
use Icinga\Application\Icinga;
use Icinga\Test\BaseTestCase;
use Icinga\User;
use Icinga\Web\Widget\Dashboard;
use Icinga\Web\Widget\Dashboard\Pane;
use Icinga\Web\Widget\Dashboard\Dashlet;
use Icinga\Test\BaseTestCase;
class DashletWithMockedView extends Dashlet
{
@ -52,6 +52,7 @@ class DashboardTest extends BaseTestCase
$moduleMock->shouldReceive('getPaneItems')->andReturn(array(
'test-pane' => new Pane('Test Pane')
));
$moduleMock->shouldReceive('getName')->andReturn('test');
$moduleManagerMock = Mockery::mock('Icinga\Application\Modules\Manager');
$moduleManagerMock->shouldReceive('getLoadedModules')->andReturn(array(
@ -130,7 +131,10 @@ class DashboardTest extends BaseTestCase
*/
public function testLoadPaneItemsProvidedByEnabledModules()
{
$user = new User('test');
$user->setPermissions(array('*' => '*'));
$dashboard = new Dashboard();
$dashboard->setUser($user);
$dashboard->load();
$this->assertCount(

View File

@ -5,6 +5,7 @@ namespace Tests\Icinga\Web;
use Mockery;
use Icinga\Test\BaseTestCase;
use Icinga\User;
use Icinga\Web\Widget\SearchDashboard;
class SearchDashboardTest extends BaseTestCase
@ -19,6 +20,7 @@ class SearchDashboardTest extends BaseTestCase
$moduleMock->shouldReceive('getSearchUrls')->andReturn(array(
$searchUrl
));
$moduleMock->shouldReceive('getName')->andReturn('test');
$moduleManagerMock = Mockery::mock('Icinga\Application\Modules\Manager');
$moduleManagerMock->shouldReceive('getLoadedModules')->andReturn(array(
@ -34,14 +36,22 @@ class SearchDashboardTest extends BaseTestCase
*/
public function testWhetherRenderThrowsAnExceptionWhenHasNoDashlets()
{
$dashboard = SearchDashboard::search('pending');
$user = new User('test');
$user->setPermissions(array('*' => '*'));
$dashboard = new SearchDashboard();
$dashboard->setUser($user);
$dashboard = $dashboard->search('pending');
$dashboard->getPane('search')->removeDashlets();
$dashboard->render();
}
public function testWhetherSearchLoadsSearchDashletsFromModules()
{
$dashboard = SearchDashboard::search('pending');
$user = new User('test');
$user->setPermissions(array('*' => '*'));
$dashboard = new SearchDashboard();
$dashboard->setUser($user);
$dashboard = $dashboard->search('pending');
$result = $dashboard->getPane('search')->hasDashlet('Hosts: pending');
@ -50,7 +60,11 @@ class SearchDashboardTest extends BaseTestCase
public function testWhetherSearchProvidesHintWhenSearchStringIsEmpty()
{
$dashboard = SearchDashboard::search();
$user = new User('test');
$user->setPermissions(array('*' => '*'));
$dashboard = new SearchDashboard();
$dashboard->setUser($user);
$dashboard = $dashboard->search();
$result = $dashboard->getPane('search')->hasDashlet('Ready to search');