summaryrefslogtreecommitdiffstats
path: root/modules/setup/library
diff options
context:
space:
mode:
Diffstat (limited to 'modules/setup/library')
-rw-r--r--modules/setup/library/Setup/Exception/SetupException.php22
-rw-r--r--modules/setup/library/Setup/Requirement.php343
-rw-r--r--modules/setup/library/Setup/Requirement/ClassRequirement.php48
-rw-r--r--modules/setup/library/Setup/Requirement/ConfigDirectoryRequirement.php42
-rw-r--r--modules/setup/library/Setup/Requirement/OSRequirement.php27
-rw-r--r--modules/setup/library/Setup/Requirement/PhpConfigRequirement.php22
-rw-r--r--modules/setup/library/Setup/Requirement/PhpModuleRequirement.php42
-rw-r--r--modules/setup/library/Setup/Requirement/PhpVersionRequirement.php28
-rw-r--r--modules/setup/library/Setup/Requirement/SetRequirement.php34
-rw-r--r--modules/setup/library/Setup/Requirement/WebLibraryRequirement.php24
-rw-r--r--modules/setup/library/Setup/Requirement/WebModuleRequirement.php31
-rw-r--r--modules/setup/library/Setup/RequirementSet.php335
-rw-r--r--modules/setup/library/Setup/RequirementsRenderer.php64
-rw-r--r--modules/setup/library/Setup/Setup.php99
-rw-r--r--modules/setup/library/Setup/SetupWizard.php24
-rw-r--r--modules/setup/library/Setup/Step.php31
-rw-r--r--modules/setup/library/Setup/Steps/AuthenticationStep.php238
-rw-r--r--modules/setup/library/Setup/Steps/DatabaseStep.php266
-rw-r--r--modules/setup/library/Setup/Steps/GeneralConfigStep.php131
-rw-r--r--modules/setup/library/Setup/Steps/ResourceStep.php199
-rw-r--r--modules/setup/library/Setup/Steps/UserGroupStep.php213
-rw-r--r--modules/setup/library/Setup/Utils/DbTool.php943
-rw-r--r--modules/setup/library/Setup/Utils/EnableModuleStep.php77
-rw-r--r--modules/setup/library/Setup/Web/Form/Validator/TokenValidator.php73
-rw-r--r--modules/setup/library/Setup/WebWizard.php752
-rw-r--r--modules/setup/library/Setup/Webserver.php233
-rw-r--r--modules/setup/library/Setup/Webserver/Apache.php142
-rw-r--r--modules/setup/library/Setup/Webserver/Nginx.php36
28 files changed, 4519 insertions, 0 deletions
diff --git a/modules/setup/library/Setup/Exception/SetupException.php b/modules/setup/library/Setup/Exception/SetupException.php
new file mode 100644
index 0000000..c3ae591
--- /dev/null
+++ b/modules/setup/library/Setup/Exception/SetupException.php
@@ -0,0 +1,22 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Exception;
+
+use Icinga\Exception\IcingaException;
+
+/**
+ * Class SetupException
+ *
+ * Used to indicate that a setup should be aborted.
+ */
+class SetupException extends IcingaException
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct()
+ {
+ parent::__construct('Setup abortion');
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement.php b/modules/setup/library/Setup/Requirement.php
new file mode 100644
index 0000000..fd16405
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement.php
@@ -0,0 +1,343 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup;
+
+use LogicException;
+
+abstract class Requirement
+{
+ /**
+ * The state of this requirement
+ *
+ * @var bool
+ */
+ protected $state;
+
+ /**
+ * A descriptive text representing the current state of this requirement
+ *
+ * @var string
+ */
+ protected $stateText;
+
+ /**
+ * The descriptions of this requirement
+ *
+ * @var array
+ */
+ protected $descriptions;
+
+ /**
+ * The title of this requirement
+ *
+ * @var string
+ */
+ protected $title;
+
+ /**
+ * The condition of this requirement
+ *
+ * @var mixed
+ */
+ protected $condition;
+
+ /**
+ * Whether this requirement is optional
+ *
+ * @var bool
+ */
+ protected $optional;
+
+ /**
+ * The alias to display the condition with in a human readable way
+ *
+ * @var string
+ */
+ protected $alias;
+
+ /**
+ * The text to display if the given requirement is fulfilled
+ *
+ * @var string
+ */
+ protected $textAvailable;
+
+ /**
+ * The text to display if the given requirement is not fulfilled
+ *
+ * @var string
+ */
+ protected $textMissing;
+
+ /**
+ * Create a new requirement
+ *
+ * @param array $options
+ *
+ * @throws LogicException In case there exists no setter for an option's key
+ */
+ public function __construct(array $options = array())
+ {
+ $this->optional = false;
+ $this->descriptions = array();
+
+ foreach ($options as $key => $value) {
+ $setMethod = 'set' . ucfirst($key);
+ $addMethod = 'add' . ucfirst($key);
+ if (method_exists($this, $setMethod)) {
+ $this->$setMethod($value);
+ } elseif (method_exists($this, $addMethod)) {
+ $this->$addMethod($value);
+ } else {
+ throw LogicException('No setter found for option key: ' . $key);
+ }
+ }
+ }
+
+ /**
+ * Set the state of this requirement
+ *
+ * @param bool $state
+ *
+ * @return Requirement
+ */
+ public function setState($state)
+ {
+ $this->state = (bool) $state;
+ return $this;
+ }
+
+ /**
+ * Return the state of this requirement
+ *
+ * Evaluates the requirement in case there is no state set yet.
+ *
+ * @return int
+ */
+ public function getState()
+ {
+ if ($this->state === null) {
+ $this->state = $this->evaluate();
+ }
+
+ return $this->state;
+ }
+
+ /**
+ * Set a descriptive text for this requirement's current state
+ *
+ * @param string $text
+ *
+ * @return Requirement
+ */
+ public function setStateText($text)
+ {
+ $this->stateText = $text;
+ return $this;
+ }
+
+ /**
+ * Return a descriptive text for this requirement's current state
+ *
+ * @return string
+ */
+ public function getStateText()
+ {
+ $state = $this->getState();
+ if ($this->stateText === null) {
+ return $state ? $this->getTextAvailable() : $this->getTextMissing();
+ }
+ return $this->stateText;
+ }
+
+ /**
+ * Add a description for this requirement
+ *
+ * @param string $description
+ *
+ * @return Requirement
+ */
+ public function addDescription($description)
+ {
+ $this->descriptions[] = $description;
+ return $this;
+ }
+
+ /**
+ * Return the descriptions of this wizard
+ *
+ * @return array
+ */
+ public function getDescriptions()
+ {
+ return $this->descriptions;
+ }
+
+ /**
+ * Set the title for this requirement
+ *
+ * @param string $title
+ *
+ * @return Requirement
+ */
+ public function setTitle($title)
+ {
+ $this->title = $title;
+ return $this;
+ }
+
+ /**
+ * Return the title of this requirement
+ *
+ * In case there is no title set the alias is returned instead.
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ if ($this->title === null) {
+ return $this->getAlias();
+ }
+
+ return $this->title;
+ }
+
+ /**
+ * Set the condition for this requirement
+ *
+ * @param mixed $condition
+ *
+ * @return Requirement
+ */
+ public function setCondition($condition)
+ {
+ $this->condition = $condition;
+ return $this;
+ }
+
+ /**
+ * Return the condition of this requirement
+ *
+ * @return mixed
+ */
+ public function getCondition()
+ {
+ return $this->condition;
+ }
+
+ /**
+ * Set whether this requirement is optional
+ *
+ * @param bool $state
+ *
+ * @return Requirement
+ */
+ public function setOptional($state = true)
+ {
+ $this->optional = (bool) $state;
+ return $this;
+ }
+
+ /**
+ * Return whether this requirement is optional
+ *
+ * @return bool
+ */
+ public function isOptional()
+ {
+ return $this->optional;
+ }
+
+ /**
+ * Set the alias to display the condition with in a human readable way
+ *
+ * @param string $alias
+ *
+ * @return Requirement
+ */
+ public function setAlias($alias)
+ {
+ $this->alias = $alias;
+ return $this;
+ }
+
+ /**
+ * Return the alias to display the condition with in a human readable way
+ *
+ * @return string
+ */
+ public function getAlias()
+ {
+ return $this->alias;
+ }
+
+ /**
+ * Set the text to display if the given requirement is fulfilled
+ *
+ * @param string $textAvailable
+ *
+ * @return Requirement
+ */
+ public function setTextAvailable($textAvailable)
+ {
+ $this->textAvailable = $textAvailable;
+ return $this;
+ }
+
+ /**
+ * Get the text to display if the given requirement is fulfilled
+ *
+ * @return string
+ */
+ public function getTextAvailable()
+ {
+ return $this->textAvailable;
+ }
+
+ /**
+ * Set the text to display if the given requirement is not fulfilled
+ *
+ * @param string $textMissing
+ *
+ * @return Requirement
+ */
+ public function setTextMissing($textMissing)
+ {
+ $this->textMissing = $textMissing;
+ return $this;
+ }
+
+ /**
+ * Get the text to display if the given requirement is not fulfilled
+ *
+ * @return string
+ */
+ public function getTextMissing()
+ {
+ return $this->textMissing;
+ }
+
+ /**
+ * Evaluate this requirement and return whether it is fulfilled
+ *
+ * @return bool
+ */
+ abstract protected function evaluate();
+
+ /**
+ * Return whether the given requirement equals this one
+ *
+ * @param Requirement $requirement
+ *
+ * @return bool
+ */
+ public function equals(Requirement $requirement)
+ {
+ if ($requirement instanceof static) {
+ return $this->getCondition() === $requirement->getCondition();
+ }
+
+ return false;
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement/ClassRequirement.php b/modules/setup/library/Setup/Requirement/ClassRequirement.php
new file mode 100644
index 0000000..d884c31
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement/ClassRequirement.php
@@ -0,0 +1,48 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Requirement;
+
+use Icinga\Application\Platform;
+use Icinga\Module\Setup\Requirement;
+
+class ClassRequirement extends Requirement
+{
+ protected function evaluate()
+ {
+ return Platform::classExists($this->getCondition());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStateText()
+ {
+ $stateText = parent::getStateText();
+ if ($stateText === null) {
+ $alias = $this->getAlias();
+ if ($this->getState()) {
+ $stateText = $alias === null
+ ? sprintf(
+ mt('setup', 'The %s class is available.', 'setup.requirement.class'),
+ $this->getCondition()
+ )
+ : sprintf(
+ mt('setup', 'The %s is available.', 'setup.requirement.class'),
+ $alias
+ );
+ } else {
+ $stateText = $alias === null
+ ? sprintf(
+ mt('setup', 'The %s class is missing.', 'setup.requirement.class'),
+ $this->getCondition()
+ )
+ : sprintf(
+ mt('setup', 'The %s is missing.', 'setup.requirement.class'),
+ $alias
+ );
+ }
+ }
+ return $stateText;
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement/ConfigDirectoryRequirement.php b/modules/setup/library/Setup/Requirement/ConfigDirectoryRequirement.php
new file mode 100644
index 0000000..7e9044c
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement/ConfigDirectoryRequirement.php
@@ -0,0 +1,42 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Requirement;
+
+use Icinga\Module\Setup\Requirement;
+
+class ConfigDirectoryRequirement extends Requirement
+{
+ public function getTitle()
+ {
+ $title = parent::getTitle();
+ if ($title === null) {
+ return mt('setup', 'Read- and writable configuration directory');
+ }
+
+ return $title;
+ }
+
+ protected function evaluate()
+ {
+ $path = $this->getCondition();
+ if (file_exists($path)) {
+ $readable = is_readable($path);
+ if ($readable && is_writable($path)) {
+ $this->setStateText(sprintf(mt('setup', 'The directory %s is read- and writable.'), $path));
+ return true;
+ } else {
+ $this->setStateText(sprintf(
+ $readable
+ ? mt('setup', 'The directory %s is not writable.')
+ : mt('setup', 'The directory %s is not readable.'),
+ $path
+ ));
+ return false;
+ }
+ } else {
+ $this->setStateText(sprintf(mt('setup', 'The directory %s does not exist.'), $path));
+ return false;
+ }
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement/OSRequirement.php b/modules/setup/library/Setup/Requirement/OSRequirement.php
new file mode 100644
index 0000000..760c97a
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement/OSRequirement.php
@@ -0,0 +1,27 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Requirement;
+
+use Icinga\Application\Platform;
+use Icinga\Module\Setup\Requirement;
+
+class OSRequirement extends Requirement
+{
+ public function getTitle()
+ {
+ $title = parent::getTitle();
+ if ($title === null) {
+ return sprintf(mt('setup', '%s Platform'), ucfirst($this->getCondition()));
+ }
+
+ return $title;
+ }
+
+ protected function evaluate()
+ {
+ $phpOS = Platform::getOperatingSystemName();
+ $this->setStateText(sprintf(mt('setup', 'You are running PHP on a %s system.'), ucfirst($phpOS)));
+ return strtolower($phpOS) === strtolower($this->getCondition());
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement/PhpConfigRequirement.php b/modules/setup/library/Setup/Requirement/PhpConfigRequirement.php
new file mode 100644
index 0000000..6c77af5
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement/PhpConfigRequirement.php
@@ -0,0 +1,22 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Requirement;
+
+use Icinga\Application\Platform;
+use Icinga\Module\Setup\Requirement;
+
+class PhpConfigRequirement extends Requirement
+{
+ protected function evaluate()
+ {
+ list($configDirective, $value) = $this->getCondition();
+ $configValue = Platform::getPhpConfig($configDirective);
+ $this->setStateText(
+ $configValue
+ ? sprintf(mt('setup', 'The PHP config `%s\' is set to "%s".'), $configDirective, $configValue)
+ : sprintf(mt('setup', 'The PHP config `%s\' is not defined.'), $configDirective)
+ );
+ return is_bool($value) ? $configValue == $value : $configValue === $value;
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement/PhpModuleRequirement.php b/modules/setup/library/Setup/Requirement/PhpModuleRequirement.php
new file mode 100644
index 0000000..f8ab129
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement/PhpModuleRequirement.php
@@ -0,0 +1,42 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Requirement;
+
+use Icinga\Application\Platform;
+use Icinga\Module\Setup\Requirement;
+
+class PhpModuleRequirement extends Requirement
+{
+ public function getTitle()
+ {
+ $title = parent::getTitle();
+ if ($title === $this->getAlias()) {
+ if ($title === null) {
+ $title = $this->getCondition();
+ }
+
+ return sprintf(mt('setup', 'PHP Module: %s'), $title);
+ }
+
+ return $title;
+ }
+
+ protected function evaluate()
+ {
+ $moduleName = $this->getCondition();
+ if (Platform::extensionLoaded($moduleName)) {
+ $this->setStateText(sprintf(
+ mt('setup', 'The PHP module %s is available.'),
+ $this->getAlias() ?: $moduleName
+ ));
+ return true;
+ } else {
+ $this->setStateText(sprintf(
+ mt('setup', 'The PHP module %s is missing.'),
+ $this->getAlias() ?: $moduleName
+ ));
+ return false;
+ }
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement/PhpVersionRequirement.php b/modules/setup/library/Setup/Requirement/PhpVersionRequirement.php
new file mode 100644
index 0000000..b811ca8
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement/PhpVersionRequirement.php
@@ -0,0 +1,28 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Requirement;
+
+use Icinga\Application\Platform;
+use Icinga\Module\Setup\Requirement;
+
+class PhpVersionRequirement extends Requirement
+{
+ public function getTitle()
+ {
+ $title = parent::getTitle();
+ if ($title === null) {
+ return mt('setup', 'PHP Version');
+ }
+
+ return $title;
+ }
+
+ protected function evaluate()
+ {
+ $phpVersion = Platform::getPhpVersion();
+ $this->setStateText(sprintf(mt('setup', 'You are running PHP version %s.'), $phpVersion));
+ list($operator, $requiredVersion) = $this->getCondition();
+ return version_compare($phpVersion, $requiredVersion, $operator);
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement/SetRequirement.php b/modules/setup/library/Setup/Requirement/SetRequirement.php
new file mode 100644
index 0000000..77cbaf0
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement/SetRequirement.php
@@ -0,0 +1,34 @@
+<?php
+/* Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Module\Setup\Requirement;
+
+use Icinga\Module\Setup\Requirement;
+
+/**
+ * Add requirement field
+ *
+ * @package Icinga\Module\Setup\Requirement
+ */
+class SetRequirement extends Requirement
+{
+ protected function evaluate()
+ {
+ $condition = $this->getCondition();
+
+ if ($condition->getState()) {
+ $this->setStateText(sprintf(
+ mt('setup', '%s is available.'),
+ $this->getAlias() ?: $this->getTitle()
+ ));
+ return true;
+ }
+
+ $this->setStateText(sprintf(
+ mt('setup', '%s is missing.'),
+ $this->getAlias() ?: $this->getTitle()
+ ));
+
+ return false;
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement/WebLibraryRequirement.php b/modules/setup/library/Setup/Requirement/WebLibraryRequirement.php
new file mode 100644
index 0000000..bab587a
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement/WebLibraryRequirement.php
@@ -0,0 +1,24 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Module\Setup\Requirement;
+
+use Icinga\Application\Icinga;
+use Icinga\Module\Setup\Requirement;
+
+class WebLibraryRequirement extends Requirement
+{
+ protected function evaluate()
+ {
+ list($name, $op, $version) = $this->getCondition();
+
+ $libs = Icinga::app()->getLibraries();
+ if (! $libs->has($name)) {
+ $this->setStateText(sprintf(mt('setup', '%s is not installed'), $this->getAlias()));
+ return false;
+ }
+
+ $this->setStateText(sprintf(mt('setup', '%s version: %s'), $this->getAlias(), $libs->get($name)->getVersion()));
+ return $libs->has($name, $op . $version);
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement/WebModuleRequirement.php b/modules/setup/library/Setup/Requirement/WebModuleRequirement.php
new file mode 100644
index 0000000..ad600e1
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement/WebModuleRequirement.php
@@ -0,0 +1,31 @@
+<?php
+/* Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Module\Setup\Requirement;
+
+use Icinga\Application\Icinga;
+use Icinga\Module\Setup\Requirement;
+
+class WebModuleRequirement extends Requirement
+{
+ protected function evaluate()
+ {
+ list($name, $op, $version) = $this->getCondition();
+
+ $mm = Icinga::app()->getModuleManager();
+ if (! $mm->hasInstalled($name)) {
+ $this->setStateText(sprintf(mt('setup', '%s is not installed'), $this->getAlias()));
+ return false;
+ }
+
+ $module = $mm->getModule($name, false);
+
+ $moduleVersion = $module->getVersion();
+ if ($moduleVersion[0] === 'v') {
+ $moduleVersion = substr($moduleVersion, 1);
+ }
+
+ $this->setStateText(sprintf(mt('setup', '%s version: %s'), $this->getAlias(), $moduleVersion));
+ return version_compare($moduleVersion, $version, $op);
+ }
+}
diff --git a/modules/setup/library/Setup/RequirementSet.php b/modules/setup/library/Setup/RequirementSet.php
new file mode 100644
index 0000000..672fad4
--- /dev/null
+++ b/modules/setup/library/Setup/RequirementSet.php
@@ -0,0 +1,335 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup;
+
+use LogicException;
+use RecursiveIterator;
+use Traversable;
+
+/**
+ * Container to store and handle requirements
+ */
+class RequirementSet implements RecursiveIterator
+{
+ /**
+ * Mode AND (all requirements must be met)
+ */
+ const MODE_AND = 0;
+
+ /**
+ * Mode OR (at least one requirement must be met)
+ */
+ const MODE_OR = 1;
+
+ /**
+ * Whether all requirements meet their condition
+ *
+ * @var bool
+ */
+ protected $state;
+
+ /**
+ * Whether this set is optional
+ *
+ * @var bool
+ */
+ protected $optional;
+
+ /**
+ * The mode by which the requirements are evaluated
+ *
+ * @var string
+ */
+ protected $mode;
+
+ /**
+ * The registered requirements
+ *
+ * @var array
+ */
+ protected $requirements;
+
+ /**
+ * The raw state of this set's requirements
+ *
+ * @var bool
+ */
+ private $forcedState;
+
+ /**
+ * Initialize a new set of requirements
+ *
+ * @param bool $optional Whether this set is optional
+ * @param int $mode The mode by which to evaluate this set
+ */
+ public function __construct($optional = false, $mode = null)
+ {
+ $this->optional = $optional;
+ $this->requirements = array();
+ $this->setMode($mode ?: static::MODE_AND);
+ }
+
+ /**
+ * Set the state of this set
+ *
+ * @param bool $state
+ *
+ * @return RequirementSet
+ */
+ public function setState($state)
+ {
+ $this->state = (bool) $state;
+ return $this;
+ }
+
+ /**
+ * Return the state of this set
+ *
+ * Alias for RequirementSet::fulfilled(true).
+ *
+ * @return bool
+ */
+ public function getState()
+ {
+ return $this->fulfilled(true);
+ }
+
+ /**
+ * Set whether this set of requirements should be optional
+ *
+ * @param bool $state
+ *
+ * @return RequirementSet
+ */
+ public function setOptional($state = true)
+ {
+ $this->optional = (bool) $state;
+ return $this;
+ }
+
+ /**
+ * Return whether this set of requirements is optional
+ *
+ * @return bool
+ */
+ public function isOptional()
+ {
+ return $this->optional;
+ }
+
+ /**
+ * Set the mode by which to evaluate the requirements
+ *
+ * @param int $mode
+ *
+ * @return RequirementSet
+ *
+ * @throws LogicException In case the given mode is invalid
+ */
+ public function setMode($mode)
+ {
+ if ($mode !== static::MODE_AND && $mode !== static::MODE_OR) {
+ throw new LogicException(sprintf('Invalid mode %u given.'), $mode);
+ }
+
+ $this->mode = $mode;
+ return $this;
+ }
+
+ /**
+ * Return the mode by which the requirements are evaluated
+ *
+ * @return int
+ */
+ public function getMode()
+ {
+ return $this->mode;
+ }
+
+ /**
+ * Register a requirement
+ *
+ * @param Requirement $requirement The requirement to add
+ *
+ * @return RequirementSet
+ */
+ public function add(Requirement $requirement)
+ {
+ $merged = false;
+ foreach ($this->requirements as $knownRequirement) {
+ if ($knownRequirement instanceof Requirement && $requirement->equals($knownRequirement)) {
+ $knownRequirement->setOptional($requirement->isOptional());
+ foreach ($requirement->getDescriptions() as $description) {
+ $knownRequirement->addDescription($description);
+ }
+
+ $merged = true;
+ break;
+ }
+ }
+
+ if (! $merged) {
+ $this->requirements[] = $requirement;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return all registered requirements
+ *
+ * @return array
+ */
+ public function getAll()
+ {
+ return $this->requirements;
+ }
+
+ /**
+ * Register the given set of requirements
+ *
+ * @param RequirementSet $set The set to register
+ *
+ * @return RequirementSet
+ */
+ public function merge(RequirementSet $set)
+ {
+ if ($this->getMode() === $set->getMode() && $this->isOptional() === $set->isOptional()) {
+ foreach ($set->getAll() as $requirement) {
+ if ($requirement instanceof static) {
+ $this->merge($requirement);
+ } else {
+ $this->add($requirement);
+ }
+ }
+ } else {
+ $this->requirements[] = $set;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return whether all requirements can successfully be evaluated based on the current mode
+ *
+ * In case this is a optional set of requirements (and $force is false), true is returned immediately.
+ *
+ * @param bool $force Whether to ignore the optionality of a set or single requirement
+ *
+ * @return bool
+ */
+ public function fulfilled($force = false)
+ {
+ $state = $this->isOptional();
+ if (! $force && $state) {
+ return true;
+ }
+
+ if (! $force && $this->state !== null) {
+ return $this->state;
+ } elseif ($force && $this->forcedState !== null) {
+ return $this->forcedState;
+ }
+
+ $self = $this->requirements;
+ foreach ($self as $requirement) {
+ if ($requirement->getState()) {
+ $state = true;
+ if ($this->getMode() === static::MODE_OR) {
+ break;
+ }
+ } elseif ($force || !$requirement->isOptional()) {
+ $state = false;
+ if ($this->getMode() === static::MODE_AND) {
+ break;
+ }
+ }
+ }
+
+ if ($force) {
+ return $this->forcedState = $state;
+ }
+
+ return $this->state = $state;
+ }
+
+ /**
+ * Return whether the current element represents a nested set of requirements
+ *
+ * @return bool
+ */
+ public function hasChildren(): bool
+ {
+ $current = $this->current();
+ return $current instanceof static;
+ }
+
+ /**
+ * Return a iterator for the current nested set of requirements
+ *
+ * @return ?RecursiveIterator
+ */
+ public function getChildren(): ?RecursiveIterator
+ {
+ return $this->current();
+ }
+
+ /**
+ * Rewind the iterator to its first element
+ */
+ public function rewind(): void
+ {
+ reset($this->requirements);
+ }
+
+ /**
+ * Return whether the current iterator position is valid
+ *
+ * @return bool
+ */
+ public function valid(): bool
+ {
+ return key($this->requirements) !== null;
+ }
+
+ /**
+ * Return the current element in the iteration
+ *
+ * @return Requirement|RequirementSet
+ */
+ #[\ReturnTypeWillChange]
+ public function current()
+ {
+ return current($this->requirements);
+ }
+
+ /**
+ * Return the position of the current element in the iteration
+ *
+ * @return int
+ */
+ public function key(): int
+ {
+ return key($this->requirements);
+ }
+
+ /**
+ * Advance the iterator to the next element
+ */
+ public function next(): void
+ {
+ next($this->requirements);
+ }
+
+ /**
+ * Return this set of requirements rendered as HTML
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ $renderer = new RequirementsRenderer($this);
+ return (string) $renderer;
+ }
+}
diff --git a/modules/setup/library/Setup/RequirementsRenderer.php b/modules/setup/library/Setup/RequirementsRenderer.php
new file mode 100644
index 0000000..cc9392a
--- /dev/null
+++ b/modules/setup/library/Setup/RequirementsRenderer.php
@@ -0,0 +1,64 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup;
+
+use RecursiveIteratorIterator;
+
+class RequirementsRenderer extends RecursiveIteratorIterator
+{
+ public function beginIteration(): void
+ {
+ $this->tags[] = '<ul class="requirements">';
+ }
+
+ public function endIteration(): void
+ {
+ $this->tags[] = '</ul>';
+ }
+
+ public function beginChildren(): void
+ {
+ $this->tags[] = '<li>';
+ $currentSet = $this->getSubIterator();
+ $state = $currentSet->getState() ? 'fulfilled' : ($currentSet->isOptional() ? 'not-available' : 'missing');
+ $this->tags[] = '<ul class="set-state ' . $state . '">';
+ }
+
+ public function endChildren(): void
+ {
+ $this->tags[] = '</ul>';
+ $this->tags[] = '</li>';
+ }
+
+ public function render()
+ {
+ foreach ($this as $requirement) {
+ $this->tags[] = '<li class="clearfix">';
+ $this->tags[] = '<div class="title"><h2>' . $requirement->getTitle() . '</h2></div>';
+ $this->tags[] = '<div class="description">';
+ $descriptions = $requirement->getDescriptions();
+ if (count($descriptions) > 1) {
+ $this->tags[] = '<ul>';
+ foreach ($descriptions as $d) {
+ $this->tags[] = '<li>' . $d . '</li>';
+ }
+ $this->tags[] = '</ul>';
+ } elseif (! empty($descriptions)) {
+ $this->tags[] = $descriptions[0];
+ }
+ $this->tags[] = '</div>';
+ $this->tags[] = '<div class="state ' . ($requirement->getState() ? 'fulfilled' : (
+ $requirement->isOptional() ? 'not-available' : 'missing'
+ )) . '">' . $requirement->getStateText() . '</div>';
+ $this->tags[] = '</li>';
+ }
+
+ return implode("\n", $this->tags);
+ }
+
+ public function __toString()
+ {
+ return $this->render();
+ }
+}
diff --git a/modules/setup/library/Setup/Setup.php b/modules/setup/library/Setup/Setup.php
new file mode 100644
index 0000000..7b0baed
--- /dev/null
+++ b/modules/setup/library/Setup/Setup.php
@@ -0,0 +1,99 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup;
+
+use ArrayIterator;
+use IteratorAggregate;
+use Icinga\Module\Setup\Exception\SetupException;
+use Traversable;
+
+/**
+ * Container for multiple configuration steps
+ */
+class Setup implements IteratorAggregate
+{
+ protected $steps;
+
+ protected $state;
+
+ public function __construct()
+ {
+ $this->steps = array();
+ }
+
+ public function getIterator(): Traversable
+ {
+ return new ArrayIterator($this->getSteps());
+ }
+
+ public function addStep(Step $step)
+ {
+ $this->steps[] = $step;
+ }
+
+ public function addSteps(array $steps)
+ {
+ foreach ($steps as $step) {
+ $this->addStep($step);
+ }
+ }
+
+ public function getSteps()
+ {
+ return $this->steps;
+ }
+
+ /**
+ * Run the configuration and return whether it succeeded
+ *
+ * @return bool
+ */
+ public function run()
+ {
+ $this->state = true;
+
+ try {
+ foreach ($this->steps as $step) {
+ $this->state &= $step->apply();
+ }
+ } catch (SetupException $_) {
+ $this->state = false;
+ }
+
+ return $this->state;
+ }
+
+ /**
+ * Return a summary of all actions designated to run
+ *
+ * @return array An array of HTML strings
+ */
+ public function getSummary()
+ {
+ $summaries = array();
+ foreach ($this->steps as $step) {
+ $summaries[] = $step->getSummary();
+ }
+
+ return $summaries;
+ }
+
+ /**
+ * Return a report of all actions that were run
+ *
+ * @return array An array of arrays of strings
+ */
+ public function getReport()
+ {
+ $reports = array();
+ foreach ($this->steps as $step) {
+ $report = $step->getReport();
+ if (! empty($report)) {
+ $reports[] = $report;
+ }
+ }
+
+ return $reports;
+ }
+}
diff --git a/modules/setup/library/Setup/SetupWizard.php b/modules/setup/library/Setup/SetupWizard.php
new file mode 100644
index 0000000..c7ad0c3
--- /dev/null
+++ b/modules/setup/library/Setup/SetupWizard.php
@@ -0,0 +1,24 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup;
+
+/**
+ * Interface for wizards providing a setup and requirements
+ */
+interface SetupWizard
+{
+ /**
+ * Return the setup for this wizard
+ *
+ * @return Setup
+ */
+ public function getSetup();
+
+ /**
+ * Return the requirements of this wizard
+ *
+ * @return RequirementSet
+ */
+ public function getRequirements();
+}
diff --git a/modules/setup/library/Setup/Step.php b/modules/setup/library/Setup/Step.php
new file mode 100644
index 0000000..1d0797d
--- /dev/null
+++ b/modules/setup/library/Setup/Step.php
@@ -0,0 +1,31 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup;
+
+/**
+ * Class to implement functionality for a single setup step
+ */
+abstract class Step
+{
+ /**
+ * Apply this step's configuration changes
+ *
+ * @return bool
+ */
+ abstract public function apply();
+
+ /**
+ * Return a HTML representation of this step's configuration changes supposed to be made
+ *
+ * @return string
+ */
+ abstract public function getSummary();
+
+ /**
+ * Return a textual summary of all configuration changes made
+ *
+ * @return array
+ */
+ abstract public function getReport();
+}
diff --git a/modules/setup/library/Setup/Steps/AuthenticationStep.php b/modules/setup/library/Setup/Steps/AuthenticationStep.php
new file mode 100644
index 0000000..3c6c64a
--- /dev/null
+++ b/modules/setup/library/Setup/Steps/AuthenticationStep.php
@@ -0,0 +1,238 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Steps;
+
+use Exception;
+use Icinga\Application\Config;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\ResourceFactory;
+use Icinga\Exception\IcingaException;
+use Icinga\Authentication\User\DbUserBackend;
+use Icinga\Module\Setup\Step;
+
+class AuthenticationStep extends Step
+{
+ protected $data;
+
+ protected $dbError;
+
+ protected $authIniError;
+
+ protected $permIniError;
+
+ public function __construct(array $data)
+ {
+ $this->data = $data;
+ }
+
+ public function apply()
+ {
+ $success = $this->createAuthenticationIni();
+ if (isset($this->data['adminAccountData']['resourceConfig'])) {
+ $success &= $this->createAccount();
+ }
+
+ $success &= $this->createRolesIni();
+ return $success;
+ }
+
+ protected function createAuthenticationIni()
+ {
+ $config = array();
+ $backendConfig = $this->data['backendConfig'];
+ $backendName = $backendConfig['name'];
+ unset($backendConfig['name']);
+ $config[$backendName] = $backendConfig;
+ if (isset($this->data['resourceName'])) {
+ $config[$backendName]['resource'] = $this->data['resourceName'];
+ }
+
+ try {
+ Config::fromArray($config)
+ ->setConfigFile(Config::resolvePath('authentication.ini'))
+ ->saveIni();
+ } catch (Exception $e) {
+ $this->authIniError = $e;
+ return false;
+ }
+
+ $this->authIniError = false;
+ return true;
+ }
+
+ protected function createRolesIni()
+ {
+ if (isset($this->data['adminAccountData']['username'])) {
+ $config = array(
+ 'users' => $this->data['adminAccountData']['username'],
+ 'permissions' => '*'
+ );
+
+ if ($this->data['backendConfig']['backend'] === 'db') {
+ $config['groups'] = mt('setup', 'Administrators', 'setup.role.name');
+ }
+ } else { // isset($this->data['adminAccountData']['groupname'])
+ $config = array(
+ 'groups' => $this->data['adminAccountData']['groupname'],
+ 'permissions' => '*'
+ );
+ }
+
+ try {
+ Config::fromArray(array(mt('setup', 'Administrators', 'setup.role.name') => $config))
+ ->setConfigFile(Config::resolvePath('roles.ini'))
+ ->saveIni();
+ } catch (Exception $e) {
+ $this->permIniError = $e;
+ return false;
+ }
+
+ $this->permIniError = false;
+ return true;
+ }
+
+ protected function createAccount()
+ {
+ try {
+ $backend = new DbUserBackend(
+ ResourceFactory::createResource(new ConfigObject($this->data['adminAccountData']['resourceConfig']))
+ );
+
+ if ($backend->select()->where('user_name', $this->data['adminAccountData']['username'])->count() === 0) {
+ $backend->insert('user', array(
+ 'user_name' => $this->data['adminAccountData']['username'],
+ 'password' => $this->data['adminAccountData']['password'],
+ 'is_active' => true
+ ));
+ $this->dbError = false;
+ }
+ } catch (Exception $e) {
+ $this->dbError = $e;
+ return false;
+ }
+
+ return true;
+ }
+
+ public function getSummary()
+ {
+ $pageTitle = '<h2>' . mt('setup', 'Authentication', 'setup.page.title') . '</h2>';
+ $backendTitle = '<h3>' . mt('setup', 'Authentication Backend', 'setup.page.title') . '</h3>';
+ $adminTitle = '<h3>' . mt('setup', 'Administration', 'setup.page.title') . '</h3>';
+
+ $authType = $this->data['backendConfig']['backend'];
+ $backendDesc = '<p>' . sprintf(
+ mt('setup', 'Users will authenticate using %s.', 'setup.summary.auth'),
+ $authType === 'db' ? mt('setup', 'a database', 'setup.summary.auth.type') : (
+ $authType === 'ldap' || $authType === 'msldap' ? 'LDAP' : (
+ mt('setup', 'webserver authentication', 'setup.summary.auth.type')
+ )
+ )
+ ) . '</p>';
+
+ $backendHtml = ''
+ . '<table>'
+ . '<tbody>'
+ . '<tr>'
+ . '<td><strong>' . t('Backend Name') . '</strong></td>'
+ . '<td>' . $this->data['backendConfig']['name'] . '</td>'
+ . '</tr>'
+ . ($authType === 'ldap' || $authType === 'msldap' ? (
+ '<tr>'
+ . '<td><strong>' . mt('setup', 'User Object Class') . '</strong></td>'
+ . '<td>' . ($authType === 'msldap' ? 'user' : $this->data['backendConfig']['user_class']) . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . mt('setup', 'Custom Filter') . '</strong></td>'
+ . '<td>' . (trim($this->data['backendConfig']['filter']) ?: t('None', 'auth.ldap.filter')) . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . mt('setup', 'User Name Attribute') . '</strong></td>'
+ . '<td>' . ($authType === 'msldap'
+ ? 'sAMAccountName'
+ : $this->data['backendConfig']['user_name_attribute']) . '</td>'
+ . '</tr>'
+ ) : ($authType === 'external' ? (
+ '<tr>'
+ . '<td><strong>' . t('Filter Pattern') . '</strong></td>'
+ . '<td>' . $this->data['backendConfig']['strip_username_regexp'] . '</td>'
+ . '</tr>'
+ ) : ''))
+ . '</tbody>'
+ . '</table>';
+
+ if (isset($this->data['adminAccountData']['username'])) {
+ $adminHtml = '<p>' . (isset($this->data['adminAccountData']['resourceConfig']) ? sprintf(
+ mt('setup', 'Administrative rights will initially be granted to a new account called "%s".'),
+ $this->data['adminAccountData']['username']
+ ) : sprintf(
+ mt('setup', 'Administrative rights will initially be granted to an existing account called "%s".'),
+ $this->data['adminAccountData']['username']
+ )) . '</p>';
+ } else { // isset($this->data['adminAccountData']['groupname'])
+ $adminHtml = '<p>' . sprintf(
+ mt('setup', 'Administrative rights will initially be granted to members of the user group "%s".'),
+ $this->data['adminAccountData']['groupname']
+ ) . '</p>';
+ }
+
+ return $pageTitle . '<div class="topic">' . $backendDesc . $backendTitle . $backendHtml . '</div>'
+ . '<div class="topic">' . $adminTitle . $adminHtml . '</div>';
+ }
+
+ public function getReport()
+ {
+ $report = array();
+
+ if ($this->authIniError === false) {
+ $report[] = sprintf(
+ mt('setup', 'Authentication configuration has been successfully written to: %s'),
+ Config::resolvePath('authentication.ini')
+ );
+ } elseif ($this->authIniError !== null) {
+ $report[] = sprintf(
+ mt('setup', 'Authentication configuration could not be written to: %s. An error occured:'),
+ Config::resolvePath('authentication.ini')
+ );
+ $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->authIniError));
+ }
+
+ if ($this->dbError === false) {
+ $report[] = sprintf(
+ mt('setup', 'Account "%s" has been successfully created.'),
+ $this->data['adminAccountData']['username']
+ );
+ } elseif ($this->dbError !== null) {
+ $report[] = sprintf(
+ mt('setup', 'Unable to create account "%s". An error occured:'),
+ $this->data['adminAccountData']['username']
+ );
+ $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->dbError));
+ }
+
+ if ($this->permIniError === false) {
+ $report[] = isset($this->data['adminAccountData']['username']) ? sprintf(
+ mt('setup', 'Account "%s" has been successfully defined as initial administrator.'),
+ $this->data['adminAccountData']['username']
+ ) : sprintf(
+ mt('setup', 'The members of the user group "%s" were successfully defined as initial administrators.'),
+ $this->data['adminAccountData']['groupname']
+ );
+ } elseif ($this->permIniError !== null) {
+ $report[] = isset($this->data['adminAccountData']['username']) ? sprintf(
+ mt('setup', 'Unable to define account "%s" as initial administrator. An error occured:'),
+ $this->data['adminAccountData']['username']
+ ) : sprintf(
+ mt(
+ 'setup',
+ 'Unable to define the members of the user group "%s" as initial administrators. An error occured:'
+ ),
+ $this->data['adminAccountData']['groupname']
+ );
+ $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->permIniError));
+ }
+
+ return $report;
+ }
+}
diff --git a/modules/setup/library/Setup/Steps/DatabaseStep.php b/modules/setup/library/Setup/Steps/DatabaseStep.php
new file mode 100644
index 0000000..32b2d15
--- /dev/null
+++ b/modules/setup/library/Setup/Steps/DatabaseStep.php
@@ -0,0 +1,266 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Steps;
+
+use Exception;
+use PDOException;
+use Icinga\Exception\IcingaException;
+use Icinga\Module\Setup\Step;
+use Icinga\Module\Setup\Utils\DbTool;
+use Icinga\Module\Setup\Exception\SetupException;
+
+class DatabaseStep extends Step
+{
+ protected $data;
+
+ protected $error;
+
+ protected $messages;
+
+ public function __construct(array $data)
+ {
+ $this->data = $data;
+ $this->messages = array();
+ }
+
+ public function apply()
+ {
+ $resourceConfig = $this->data['resourceConfig'];
+ if (isset($this->data['adminName'])) {
+ $resourceConfig['username'] = $this->data['adminName'];
+ if (isset($this->data['adminPassword'])) {
+ $resourceConfig['password'] = $this->data['adminPassword'];
+ }
+ }
+
+ $db = new DbTool($resourceConfig);
+
+ try {
+ if ($resourceConfig['db'] === 'mysql') {
+ $this->setupMysqlDatabase($db);
+ } elseif ($resourceConfig['db'] === 'pgsql') {
+ $this->setupPgsqlDatabase($db);
+ }
+ } catch (Exception $e) {
+ $this->error = $e;
+ throw new SetupException();
+ }
+
+ $this->error = false;
+ return true;
+ }
+
+ protected function setupMysqlDatabase(DbTool $db)
+ {
+ try {
+ $db->connectToDb();
+ $this->log(
+ mt('setup', 'Successfully connected to existing database "%s"...'),
+ $this->data['resourceConfig']['dbname']
+ );
+ } catch (PDOException $_) {
+ $db->connectToHost();
+ $this->log(mt('setup', 'Creating new database "%s"...'), $this->data['resourceConfig']['dbname']);
+ $db->exec('CREATE DATABASE ' . $db->quoteIdentifier($this->data['resourceConfig']['dbname']));
+ $db->reconnect($this->data['resourceConfig']['dbname']);
+ }
+
+ if (array_search(reset($this->data['tables']), $db->listTables(), true) !== false) {
+ $this->log(mt('setup', 'Database schema already exists...'));
+ } else {
+ $this->log(mt('setup', 'Creating database schema...'));
+ $db->import($this->data['schemaPath'] . '/mysql.schema.sql');
+ }
+
+ if ($db->hasLogin($this->data['resourceConfig']['username'])) {
+ $this->log(mt('setup', 'Login "%s" already exists...'), $this->data['resourceConfig']['username']);
+ } else {
+ $this->log(mt('setup', 'Creating login "%s"...'), $this->data['resourceConfig']['username']);
+ $db->addLogin($this->data['resourceConfig']['username'], $this->data['resourceConfig']['password']);
+ }
+
+ $username = $this->data['resourceConfig']['username'];
+ if ($db->checkPrivileges($this->data['privileges'], $this->data['tables'], $username)) {
+ $this->log(
+ mt('setup', 'Required privileges were already granted to login "%s".'),
+ $this->data['resourceConfig']['username']
+ );
+ } else {
+ $this->log(
+ mt('setup', 'Granting required privileges to login "%s"...'),
+ $this->data['resourceConfig']['username']
+ );
+ $db->grantPrivileges(
+ $this->data['privileges'],
+ $this->data['tables'],
+ $this->data['resourceConfig']['username']
+ );
+ }
+ }
+
+ protected function setupPgsqlDatabase(DbTool $db)
+ {
+ try {
+ $db->connectToDb();
+ $this->log(
+ mt('setup', 'Successfully connected to existing database "%s"...'),
+ $this->data['resourceConfig']['dbname']
+ );
+ } catch (PDOException $_) {
+ $db->connectToHost();
+ $this->log(mt('setup', 'Creating new database "%s"...'), $this->data['resourceConfig']['dbname']);
+ $db->exec(sprintf(
+ "CREATE DATABASE %s WITH ENCODING 'UTF-8'",
+ $db->quoteIdentifier($this->data['resourceConfig']['dbname'])
+ ));
+ $db->reconnect($this->data['resourceConfig']['dbname']);
+ }
+
+ if (array_search(reset($this->data['tables']), $db->listTables(), true) !== false) {
+ $this->log(mt('setup', 'Database schema already exists...'));
+ } else {
+ $this->log(mt('setup', 'Creating database schema...'));
+ $db->import($this->data['schemaPath'] . '/pgsql.schema.sql');
+ }
+
+ if ($db->hasLogin($this->data['resourceConfig']['username'])) {
+ $this->log(mt('setup', 'Login "%s" already exists...'), $this->data['resourceConfig']['username']);
+ } else {
+ $this->log(mt('setup', 'Creating login "%s"...'), $this->data['resourceConfig']['username']);
+ $db->addLogin($this->data['resourceConfig']['username'], $this->data['resourceConfig']['password']);
+ }
+
+ $username = $this->data['resourceConfig']['username'];
+ if ($db->checkPrivileges($this->data['privileges'], $this->data['tables'], $username)) {
+ $this->log(
+ mt('setup', 'Required privileges were already granted to login "%s".'),
+ $this->data['resourceConfig']['username']
+ );
+ } else {
+ $this->log(
+ mt('setup', 'Granting required privileges to login "%s"...'),
+ $this->data['resourceConfig']['username']
+ );
+ $db->grantPrivileges(
+ $this->data['privileges'],
+ $this->data['tables'],
+ $this->data['resourceConfig']['username']
+ );
+ }
+ }
+
+ public function getSummary()
+ {
+ $resourceConfig = $this->data['resourceConfig'];
+ if (isset($this->data['adminName'])) {
+ $resourceConfig['username'] = $this->data['adminName'];
+ if (isset($this->data['adminPassword'])) {
+ $resourceConfig['password'] = $this->data['adminPassword'];
+ }
+ }
+
+ $db = new DbTool($resourceConfig);
+
+ try {
+ $db->connectToDb();
+ if (array_search(reset($this->data['tables']), $db->listTables(), true) === false) {
+ if ($resourceConfig['username'] !== $this->data['resourceConfig']['username']) {
+ $message = sprintf(
+ mt(
+ 'setup',
+ 'The database user "%s" will be used to setup the missing schema required by Icinga'
+ . ' Web 2 in database "%s" and to grant access to it to a new login called "%s".'
+ ),
+ $resourceConfig['username'],
+ $resourceConfig['dbname'],
+ $this->data['resourceConfig']['username']
+ );
+ } else {
+ $message = sprintf(
+ mt(
+ 'setup',
+ 'The database user "%s" will be used to setup the missing'
+ . ' schema required by Icinga Web 2 in database "%s".'
+ ),
+ $resourceConfig['username'],
+ $resourceConfig['dbname']
+ );
+ }
+ } else {
+ $message = sprintf(
+ mt('setup', 'The database "%s" already seems to be fully set up. No action required.'),
+ $resourceConfig['dbname']
+ );
+ }
+ } catch (PDOException $_) {
+ try {
+ $db->connectToHost();
+ if ($resourceConfig['username'] !== $this->data['resourceConfig']['username']) {
+ if ($db->hasLogin($this->data['resourceConfig']['username'])) {
+ $message = sprintf(
+ mt(
+ 'setup',
+ 'The database user "%s" will be used to create the missing database'
+ . ' "%s" with the schema required by Icinga Web 2 and to grant'
+ . ' access to it to an existing login called "%s".'
+ ),
+ $resourceConfig['username'],
+ $resourceConfig['dbname'],
+ $this->data['resourceConfig']['username']
+ );
+ } else {
+ $message = sprintf(
+ mt(
+ 'setup',
+ 'The database user "%s" will be used to create the missing database'
+ . ' "%s" with the schema required by Icinga Web 2 and to grant'
+ . ' access to it to a new login called "%s".'
+ ),
+ $resourceConfig['username'],
+ $resourceConfig['dbname'],
+ $this->data['resourceConfig']['username']
+ );
+ }
+ } else {
+ $message = sprintf(
+ mt(
+ 'setup',
+ 'The database user "%s" will be used to create the missing'
+ . ' database "%s" with the schema required by Icinga Web 2.'
+ ),
+ $resourceConfig['username'],
+ $resourceConfig['dbname']
+ );
+ }
+ } catch (Exception $_) {
+ $message = mt(
+ 'setup',
+ 'No connection to database host possible. You\'ll need to setup the'
+ . ' database with the schema required by Icinga Web 2 manually.'
+ );
+ }
+ }
+
+ return '<h2>' . mt('setup', 'Database Setup', 'setup.page.title') . '</h2><p>' . $message . '</p>';
+ }
+
+ public function getReport()
+ {
+ if ($this->error === false) {
+ $report = $this->messages;
+ $report[] = mt('setup', 'The database has been fully set up!');
+ return $report;
+ } elseif ($this->error !== null) {
+ $report = $this->messages;
+ $report[] = mt('setup', 'Failed to fully setup the database. An error occured:');
+ $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->error));
+ return $report;
+ }
+ }
+
+ protected function log()
+ {
+ $this->messages[] = call_user_func_array('sprintf', func_get_args());
+ }
+}
diff --git a/modules/setup/library/Setup/Steps/GeneralConfigStep.php b/modules/setup/library/Setup/Steps/GeneralConfigStep.php
new file mode 100644
index 0000000..2c928f6
--- /dev/null
+++ b/modules/setup/library/Setup/Steps/GeneralConfigStep.php
@@ -0,0 +1,131 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Steps;
+
+use Exception;
+use Icinga\Application\Logger;
+use Icinga\Application\Config;
+use Icinga\Exception\IcingaException;
+use Icinga\Module\Setup\Step;
+
+class GeneralConfigStep extends Step
+{
+ protected $data;
+
+ protected $error;
+
+ public function __construct(array $data)
+ {
+ $this->data = $data;
+ }
+
+ public function apply()
+ {
+ $config = array();
+ foreach ($this->data['generalConfig'] as $sectionAndPropertyName => $value) {
+ list($section, $property) = explode('_', $sectionAndPropertyName, 2);
+ $config[$section][$property] = $value;
+ }
+
+ $config['global']['config_resource'] = $this->data['resourceName'];
+
+ try {
+ Config::fromArray($config)
+ ->setConfigFile(Config::resolvePath('config.ini'))
+ ->saveIni();
+ } catch (Exception $e) {
+ $this->error = $e;
+ return false;
+ }
+
+ $this->error = false;
+ return true;
+ }
+
+ public function getSummary()
+ {
+ $pageTitle = '<h2>' . mt('setup', 'Application Configuration', 'setup.page.title') . '</h2>';
+ $generalTitle = '<h3>' . t('General', 'app.config') . '</h3>';
+ $loggingTitle = '<h3>' . t('Logging', 'app.config') . '</h3>';
+
+ $generalHtml = ''
+ . '<ul>'
+ . '<li>' . ($this->data['generalConfig']['global_show_stacktraces']
+ ? t('An exception\'s stacktrace is shown to every user by default.')
+ : t('An exception\'s stacktrace is hidden from every user by default.')
+ ) . '</li>'
+ . '<li>' . t('Preferences will be stored using a database.') . '</li>'
+ . '</ul>';
+
+ $type = $this->data['generalConfig']['logging_log'];
+ if ($type === 'none') {
+ $loggingHtml = '<p>' . mt('setup', 'Logging will be disabled.') . '</p>';
+ } else {
+ $level = $this->data['generalConfig']['logging_level'];
+
+ switch ($type) {
+ case 'php':
+ $typeDescription = t('Webserver Log', 'app.config.logging.type');
+ $typeSpecificHtml = '';
+ break;
+
+ case 'syslog':
+ $typeDescription = 'Syslog';
+ $typeSpecificHtml = '<td><strong>' . t('Application Prefix') . '</strong></td>'
+ . '<td>' . $this->data['generalConfig']['logging_application'] . '</td>';
+ break;
+
+ case 'file':
+ $typeDescription = t('File', 'app.config.logging.type');
+ $typeSpecificHtml = '<td><strong>' . t('Filepath') . '</strong></td>'
+ . '<td>' . $this->data['generalConfig']['logging_file'] . '</td>';
+ break;
+ }
+
+ $loggingHtml = ''
+ . '<table>'
+ . '<tbody>'
+ . '<tr>'
+ . '<td><strong>' . t('Type', 'app.config.logging') . '</strong></td>'
+ . '<td>' . $typeDescription . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Level', 'app.config.logging') . '</strong></td>'
+ . '<td>' . ($level === Logger::$levels[Logger::ERROR] ? t('Error', 'app.config.logging.level') : (
+ $level === Logger::$levels[Logger::WARNING] ? t('Warning', 'app.config.logging.level') : (
+ $level === Logger::$levels[Logger::INFO] ? t('Information', 'app.config.logging.level') : (
+ t('Debug', 'app.config.logging.level')
+ )
+ )
+ )) . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . $typeSpecificHtml
+ . '</tr>'
+ . '</tbody>'
+ . '</table>';
+ }
+
+ return $pageTitle . '<div class="topic">' . $generalTitle . $generalHtml . '</div>'
+ . '<div class="topic">' . $loggingTitle . $loggingHtml . '</div>';
+ }
+
+ public function getReport()
+ {
+ if ($this->error === false) {
+ return array(sprintf(
+ mt('setup', 'General configuration has been successfully written to: %s'),
+ Config::resolvePath('config.ini')
+ ));
+ } elseif ($this->error !== null) {
+ return array(
+ sprintf(
+ mt('setup', 'General configuration could not be written to: %s. An error occured:'),
+ Config::resolvePath('config.ini')
+ ),
+ sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->error))
+ );
+ }
+ }
+}
diff --git a/modules/setup/library/Setup/Steps/ResourceStep.php b/modules/setup/library/Setup/Steps/ResourceStep.php
new file mode 100644
index 0000000..d9daf3b
--- /dev/null
+++ b/modules/setup/library/Setup/Steps/ResourceStep.php
@@ -0,0 +1,199 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Steps;
+
+use Exception;
+use Icinga\Application\Config;
+use Icinga\Exception\IcingaException;
+use Icinga\Module\Setup\Step;
+
+class ResourceStep extends Step
+{
+ protected $data;
+
+ protected $error;
+
+ public function __construct(array $data)
+ {
+ $this->data = $data;
+ }
+
+ public function apply()
+ {
+ $resourceConfig = array();
+ if (isset($this->data['dbResourceConfig'])) {
+ $dbConfig = $this->data['dbResourceConfig'];
+ $resourceName = $dbConfig['name'];
+ unset($dbConfig['name']);
+ $resourceConfig[$resourceName] = $dbConfig;
+ }
+
+ if (isset($this->data['ldapResourceConfig'])) {
+ $ldapConfig = $this->data['ldapResourceConfig'];
+ $resourceName = $ldapConfig['name'];
+ unset($ldapConfig['name']);
+ $resourceConfig[$resourceName] = $ldapConfig;
+ }
+
+ try {
+ Config::fromArray($resourceConfig)
+ ->setConfigFile(Config::resolvePath('resources.ini'))
+ ->saveIni();
+ } catch (Exception $e) {
+ $this->error = $e;
+ return false;
+ }
+
+ $this->error = false;
+ return true;
+ }
+
+ public function getSummary()
+ {
+ if (isset($this->data['dbResourceConfig']) && isset($this->data['ldapResourceConfig'])) {
+ $pageTitle = '<h2>' . mt('setup', 'Resources', 'setup.page.title') . '</h2>';
+ } else {
+ $pageTitle = '<h2>' . mt('setup', 'Resource', 'setup.page.title') . '</h2>';
+ }
+
+ if (isset($this->data['dbResourceConfig'])) {
+ $dbTitle = '<h3>' . mt('setup', 'Database', 'setup.page.title') . '</h3>';
+ $dbHtml = ''
+ . '<table>'
+ . '<tbody>'
+ . '<tr>'
+ . '<td><strong>' . t('Resource Name') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['name'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Database Type') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['db'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Host') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['host'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Port') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['port'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Database Name') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['dbname'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Username') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['username'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Password') . '</strong></td>'
+ . '<td>' . str_repeat('*', strlen($this->data['dbResourceConfig']['password'])) . '</td>'
+ . '</tr>';
+
+ if (defined('\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')
+ && isset($this->data['resourceConfig']['ssl_do_not_verify_server_cert'])
+ && $this->data['resourceConfig']['ssl_do_not_verify_server_cert']
+ ) {
+ $dbHtml .= ''
+ . '<tr>'
+ . '<td><strong>' . t('SSL Do Not Verify Server Certificate') . '</strong></td>'
+ . '<td>' . $this->data['resourceConfig']['ssl_do_not_verify_server_cert'] . '</td>'
+ . '</tr>';
+ }
+ if (isset($this->data['dbResourceConfig']['ssl_key']) && $this->data['dbResourceConfig']['ssl_key']) {
+ $dbHtml .= ''
+ .'<tr>'
+ . '<td><strong>' . t('SSL Key') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['ssl_key'] . '</td>'
+ . '</tr>';
+ }
+ if (isset($this->data['dbResourceConfig']['ssl_cert']) && $this->data['dbResourceConfig']['ssl_cert']) {
+ $dbHtml .= ''
+ . '<tr>'
+ . '<td><strong>' . t('SSL Cert') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['ssl_cert'] . '</td>'
+ . '</tr>';
+ }
+ if (isset($this->data['dbResourceConfig']['ssl_ca']) && $this->data['dbResourceConfig']['ssl_ca']) {
+ $dbHtml .= ''
+ . '<tr>'
+ . '<td><strong>' . t('CA') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['ssl_ca'] . '</td>'
+ . '</tr>';
+ }
+ if (isset($this->data['dbResourceConfig']['ssl_capath']) && $this->data['dbResourceConfig']['ssl_capath']) {
+ $dbHtml .= ''
+ . '<tr>'
+ . '<td><strong>' . t('CA Path') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['ssl_capath'] . '</td>'
+ . '</tr>';
+ }
+ if (isset($this->data['dbResourceConfig']['ssl_cipher']) && $this->data['dbResourceConfig']['ssl_cipher']) {
+ $dbHtml .= ''
+ . '<tr>'
+ . '<td><strong>' . t('Cipher') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['ssl_cipher'] . '</td>'
+ . '</tr>';
+ }
+
+ $dbHtml .= ''
+ . '</tbody>'
+ . '</table>';
+ }
+
+ if (isset($this->data['ldapResourceConfig'])) {
+ $ldapTitle = '<h3>LDAP</h3>';
+ $ldapHtml = ''
+ . '<table>'
+ . '<tbody>'
+ . '<tr>'
+ . '<td><strong>' . t('Resource Name') . '</strong></td>'
+ . '<td>' . $this->data['ldapResourceConfig']['name'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Host') . '</strong></td>'
+ . '<td>' . $this->data['ldapResourceConfig']['hostname'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Port') . '</strong></td>'
+ . '<td>' . $this->data['ldapResourceConfig']['port'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Root DN') . '</strong></td>'
+ . '<td>' . $this->data['ldapResourceConfig']['root_dn'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Bind DN') . '</strong></td>'
+ . '<td>' . $this->data['ldapResourceConfig']['bind_dn'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Bind Password') . '</strong></td>'
+ . '<td>' . str_repeat('*', strlen($this->data['ldapResourceConfig']['bind_pw'])) . '</td>'
+ . '</tr>'
+ . '</tbody>'
+ . '</table>';
+ }
+
+ return $pageTitle . (isset($dbTitle) ? '<div class="topic">' . $dbTitle . $dbHtml . '</div>' : '')
+ . (isset($ldapTitle) ? '<div class="topic">' . $ldapTitle . $ldapHtml . '</div>' : '');
+ }
+
+ public function getReport()
+ {
+ if ($this->error === false) {
+ return array(sprintf(
+ mt('setup', 'Resource configuration has been successfully written to: %s'),
+ Config::resolvePath('resources.ini')
+ ));
+ } elseif ($this->error !== null) {
+ return array(
+ sprintf(
+ mt('setup', 'Resource configuration could not be written to: %s. An error occured:'),
+ Config::resolvePath('resources.ini')
+ ),
+ sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->error))
+ );
+ }
+ }
+}
diff --git a/modules/setup/library/Setup/Steps/UserGroupStep.php b/modules/setup/library/Setup/Steps/UserGroupStep.php
new file mode 100644
index 0000000..4aab676
--- /dev/null
+++ b/modules/setup/library/Setup/Steps/UserGroupStep.php
@@ -0,0 +1,213 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Steps;
+
+use Exception;
+use Icinga\Application\Config;
+use Icinga\Authentication\UserGroup\DbUserGroupBackend;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\ResourceFactory;
+use Icinga\Exception\IcingaException;
+use Icinga\Module\Setup\Step;
+
+class UserGroupStep extends Step
+{
+ protected $data;
+
+ protected $groupError;
+
+ protected $memberError;
+
+ protected $groupIniError;
+
+ public function __construct(array $data)
+ {
+ $this->data = $data;
+ }
+
+ public function apply()
+ {
+ $success = $this->createGroupsIni();
+ if (isset($this->data['resourceConfig'])) {
+ $success &= $this->createUserGroup();
+ if ($success) {
+ $success &= $this->createMembership();
+ }
+ }
+
+ return $success;
+ }
+
+ protected function createGroupsIni()
+ {
+ $config = array();
+ if (isset($this->data['groupConfig'])) {
+ $backendConfig = $this->data['groupConfig'];
+ $backendName = $backendConfig['name'];
+ unset($backendConfig['name']);
+ $config[$backendName] = $backendConfig;
+ } else {
+ $backendConfig = array(
+ 'backend' => $this->data['backendConfig']['backend'], // "db" or "msldap"
+ 'resource' => $this->data['resourceName']
+ );
+
+ if ($backendConfig['backend'] === 'msldap') {
+ $backendConfig['user_backend'] = $this->data['backendConfig']['name'];
+ }
+
+ $config[$this->data['backendConfig']['name']] = $backendConfig;
+ }
+
+ try {
+ Config::fromArray($config)
+ ->setConfigFile(Config::resolvePath('groups.ini'))
+ ->saveIni();
+ } catch (Exception $e) {
+ $this->groupIniError = $e;
+ return false;
+ }
+
+ $this->groupIniError = false;
+ return true;
+ }
+
+ protected function createUserGroup()
+ {
+ try {
+ $backend = new DbUserGroupBackend(
+ ResourceFactory::createResource(new ConfigObject($this->data['resourceConfig']))
+ );
+
+ $groupName = mt('setup', 'Administrators', 'setup.role.name');
+ if ($backend->select()->where('group_name', $groupName)->count() === 0) {
+ $backend->insert('group', array(
+ 'group_name' => $groupName
+ ));
+ $this->groupError = false;
+ }
+ } catch (Exception $e) {
+ $this->groupError = $e;
+ return false;
+ }
+
+ return true;
+ }
+
+ protected function createMembership()
+ {
+ try {
+ $backend = new DbUserGroupBackend(
+ ResourceFactory::createResource(new ConfigObject($this->data['resourceConfig']))
+ );
+
+ $groupName = mt('setup', 'Administrators', 'setup.role.name');
+ $userName = $this->data['username'];
+ if ($backend
+ ->select()
+ ->from('group_membership')
+ ->where('group_name', $groupName)
+ ->where('user_name', $userName)
+ ->count() === 0
+ ) {
+ $backend->insert('group_membership', array(
+ 'group_name' => $groupName,
+ 'user_name' => $userName
+ ));
+ $this->memberError = false;
+ }
+ } catch (Exception $e) {
+ $this->memberError = $e;
+ return false;
+ }
+
+ return true;
+ }
+
+ public function getSummary()
+ {
+ if (! isset($this->data['groupConfig'])) {
+ return; // It's not necessary to show the user something he didn't configure..
+ }
+
+ $pageTitle = '<h2>' . mt('setup', 'User Groups', 'setup.page.title') . '</h2>';
+ $backendTitle = '<h3>' . mt('setup', 'User Group Backend', 'setup.page.title') . '</h3>';
+
+ $backendHtml = ''
+ . '<table>'
+ . '<tbody>'
+ . '<tr>'
+ . '<td><strong>' . t('Backend Name') . '</strong></td>'
+ . '<td>' . $this->data['groupConfig']['name'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . mt('setup', 'Group Object Class') . '</strong></td>'
+ . '<td>' . $this->data['groupConfig']['group_class'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . mt('setup', 'Custom Filter') . '</strong></td>'
+ . '<td>' . (trim($this->data['groupConfig']['group_filter']) ?: t('None', 'auth.ldap.filter')) . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . mt('setup', 'Group Name Attribute') . '</strong></td>'
+ . '<td>' . $this->data['groupConfig']['group_name_attribute'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . mt('setup', 'Group Member Attribute') . '</strong></td>'
+ . '<td>' . $this->data['groupConfig']['group_member_attribute'] . '</td>'
+ . '</tr>'
+ . '</tbody>'
+ . '</table>';
+
+ return $pageTitle . '<div class="topic">' . $backendTitle . $backendHtml . '</div>';
+ }
+
+ public function getReport()
+ {
+ $report = array();
+
+ if ($this->groupIniError === false) {
+ $report[] = sprintf(
+ mt('setup', 'User Group Backend configuration has been successfully written to: %s'),
+ Config::resolvePath('groups.ini')
+ );
+ } elseif ($this->groupIniError !== null) {
+ $report[] = sprintf(
+ mt('setup', 'User Group Backend configuration could not be written to: %s. An error occured:'),
+ Config::resolvePath('groups.ini')
+ );
+ $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->groupIniError));
+ }
+
+ if ($this->groupError === false) {
+ $report[] = sprintf(
+ mt('setup', 'User Group "%s" has been successfully created.'),
+ mt('setup', 'Administrators', 'setup.role.name')
+ );
+ } elseif ($this->groupError !== null) {
+ $report[] = sprintf(
+ mt('setup', 'Unable to create user group "%s". An error occured:'),
+ mt('setup', 'Administrators', 'setup.role.name')
+ );
+ $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->groupError));
+ }
+
+ if ($this->memberError === false) {
+ $report[] = sprintf(
+ mt('setup', 'Account "%s" has been successfully added as member to user group "%s".'),
+ $this->data['username'],
+ mt('setup', 'Administrators', 'setup.role.name')
+ );
+ } elseif ($this->memberError !== null) {
+ $report[] = sprintf(
+ mt('setup', 'Unable to add account "%s" as member to user group "%s". An error occured:'),
+ $this->data['username'],
+ mt('setup', 'Administrators', 'setup.role.name')
+ );
+ $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->memberError));
+ }
+
+ return $report;
+ }
+}
diff --git a/modules/setup/library/Setup/Utils/DbTool.php b/modules/setup/library/Setup/Utils/DbTool.php
new file mode 100644
index 0000000..5cf203e
--- /dev/null
+++ b/modules/setup/library/Setup/Utils/DbTool.php
@@ -0,0 +1,943 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Utils;
+
+use PDO;
+use PDOException;
+use LogicException;
+use Zend_Db_Adapter_Pdo_Mysql;
+use Zend_Db_Adapter_Pdo_Pgsql;
+use Icinga\Util\File;
+use Icinga\Exception\ConfigurationError;
+
+/**
+ * Utility class to ease working with databases when setting up Icinga Web 2 or one of its modules
+ */
+class DbTool
+{
+ /**
+ * The PDO database connection
+ *
+ * @var PDO
+ */
+ protected $pdoConn;
+
+ /**
+ * The Zend database adapter
+ *
+ * @var Zend_Db_Adapter_Pdo_Abstract
+ */
+ protected $zendConn;
+
+ /**
+ * The resource configuration
+ *
+ * @var array
+ */
+ protected $config;
+
+ /**
+ * Whether we are connected to the database from the resource configuration
+ *
+ * @var bool
+ */
+ protected $dbFromConfig = false;
+
+ /**
+ * GRANT privilege level identifiers
+ */
+ const GLOBAL_LEVEL = 1;
+ const PROCEDURE_LEVEL = 2;
+ const DATABASE_LEVEL = 4;
+ const TABLE_LEVEL = 8;
+ const COLUMN_LEVEL = 16;
+ const FUNCTION_LEVEL = 32;
+
+ /**
+ * All MySQL GRANT privileges with their respective level identifiers
+ *
+ * @var array
+ */
+ protected $mysqlGrantContexts = array(
+ 'ALL' => 31,
+ 'ALL PRIVILEGES' => 31,
+ 'ALTER' => 13,
+ 'ALTER ROUTINE' => 7,
+ 'CREATE' => 13,
+ 'CREATE ROUTINE' => 5,
+ 'CREATE TEMPORARY TABLES' => 5,
+ 'CREATE USER' => 1,
+ 'CREATE VIEW' => 13,
+ 'DELETE' => 13,
+ 'DROP' => 13,
+ 'EXECUTE' => 5, // MySQL reference states this also supports database level, 5.1.73 not though
+ 'FILE' => 1,
+ 'GRANT OPTION' => 15,
+ 'INDEX' => 13,
+ 'INSERT' => 29,
+ 'LOCK TABLES' => 5,
+ 'PROCESS' => 1,
+ 'REFERENCES' => 12,
+ 'RELOAD' => 1,
+ 'REPLICATION CLIENT' => 1,
+ 'REPLICATION SLAVE' => 1,
+ 'SELECT' => 29,
+ 'SHOW DATABASES' => 1,
+ 'SHOW VIEW' => 13,
+ 'SHUTDOWN' => 1,
+ 'SUPER' => 1,
+ 'UPDATE' => 29
+ );
+
+ /**
+ * All PostgreSQL GRANT privileges with their respective level identifiers
+ *
+ * @var array
+ */
+ protected $pgsqlGrantContexts = array(
+ 'ALL' => 63,
+ 'ALL PRIVILEGES' => 63,
+ 'SELECT' => 24,
+ 'INSERT' => 24,
+ 'UPDATE' => 24,
+ 'DELETE' => 8,
+ 'TRUNCATE' => 8,
+ 'REFERENCES' => 24,
+ 'TRIGGER' => 8,
+ 'CREATE' => 12,
+ 'CONNECT' => 4,
+ 'TEMPORARY' => 4,
+ 'TEMP' => 4,
+ 'EXECUTE' => 32,
+ 'USAGE' => 33,
+ 'CREATEROLE' => 1
+ );
+
+ /**
+ * Create a new DbTool
+ *
+ * @param array $config The resource configuration to use
+ */
+ public function __construct(array $config)
+ {
+ if (! isset($config['port'])) {
+ // TODO: This is not quite correct, but works as it previously did. Previously empty values were not
+ // transformed no NULL (now they are) so if the port is now null, it's been the empty string.
+ $config['port'] = '';
+ }
+
+ $this->config = $config;
+ }
+
+ /**
+ * Connect to the server
+ *
+ * @return $this
+ */
+ public function connectToHost()
+ {
+ $this->assertHostAccess();
+
+ if ($this->config['db'] == 'pgsql') {
+ // PostgreSQL requires us to specify a database on each connection and will use
+ // the current user name as default database in cases none is provided. If
+ // that database doesn't exist (which might be the case here) it will error.
+ // Therefore, we specify the maintenance database 'postgres' as database, which
+ // is most probably present and public. (http://stackoverflow.com/q/4483139)
+ $this->connect('postgres');
+ } else {
+ $this->connect();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Connect to the database
+ *
+ * @return $this
+ */
+ public function connectToDb()
+ {
+ $this->assertHostAccess();
+ $this->assertDatabaseAccess();
+ $this->connect($this->config['dbname']);
+ return $this;
+ }
+
+ /**
+ * Assert that all configuration values exist that are required to connect to a server
+ *
+ * @throws ConfigurationError
+ */
+ protected function assertHostAccess()
+ {
+ if (! isset($this->config['db'])) {
+ throw new ConfigurationError('Can\'t connect to database server of unknown type');
+ } elseif (! isset($this->config['host'])) {
+ throw new ConfigurationError('Can\'t connect to database server without a hostname or address');
+ } elseif (! isset($this->config['port'])) {
+ throw new ConfigurationError('Can\'t connect to database server without a port');
+ } elseif (! isset($this->config['username'])) {
+ throw new ConfigurationError('Can\'t connect to database server without a username');
+ } elseif (! isset($this->config['password'])) {
+ throw new ConfigurationError('Can\'t connect to database server without a password');
+ }
+ }
+
+ /**
+ * Assert that all configuration values exist that are required to connect to a database
+ *
+ * @throws ConfigurationError
+ */
+ protected function assertDatabaseAccess()
+ {
+ if (! isset($this->config['dbname'])) {
+ throw new ConfigurationError('Can\'t connect to database without a valid database name');
+ }
+ }
+
+ /**
+ * Assert that a connection with a database has been established
+ *
+ * @throws LogicException
+ */
+ protected function assertConnectedToDb()
+ {
+ if ($this->zendConn === null) {
+ throw new LogicException('Not connected to database');
+ }
+ }
+
+ /**
+ * Return whether a connection with the server has been established
+ *
+ * @return bool
+ */
+ public function isConnected()
+ {
+ return $this->pdoConn !== null;
+ }
+
+ /**
+ * Establish a connection with the database or just the server by omitting the database name
+ *
+ * @param string $dbname The name of the database to connect to
+ */
+ public function connect($dbname = null)
+ {
+ $this->pdoConnect($dbname);
+ if ($dbname !== null) {
+ $this->zendConnect($dbname);
+ $this->dbFromConfig = $dbname === $this->config['dbname'];
+ }
+ }
+
+ /**
+ * Reestablish a connection with the database or just the server by omitting the database name
+ *
+ * @param string $dbname The name of the database to connect to
+ */
+ public function reconnect($dbname = null)
+ {
+ $this->pdoConn = null;
+ $this->zendConn = null;
+ $this->connect($dbname);
+ }
+
+ /**
+ * Initialize Zend database adapter
+ *
+ * @param string $dbname The name of the database to connect with
+ *
+ * @throws ConfigurationError In case the resource type is not a supported PDO driver name
+ */
+ private function zendConnect($dbname)
+ {
+ if ($this->zendConn !== null) {
+ return;
+ }
+
+ $config = array(
+ 'dbname' => $dbname,
+ 'host' => $this->config['host'],
+ 'port' => $this->config['port'],
+ 'username' => $this->config['username'],
+ 'password' => $this->config['password']
+ );
+
+ if ($this->config['db'] === 'mysql') {
+ if (isset($this->config['use_ssl']) && $this->config['use_ssl']) {
+ $this->config['driver_options'] = array();
+ # The presence of these keys as empty strings or null cause non-ssl connections to fail
+ if ($this->config['ssl_key']) {
+ $config['driver_options'][PDO::MYSQL_ATTR_SSL_KEY] = $this->config['ssl_key'];
+ }
+ if ($this->config['ssl_cert']) {
+ $config['driver_options'][PDO::MYSQL_ATTR_SSL_CERT] = $this->config['ssl_cert'];
+ }
+ if ($this->config['ssl_ca']) {
+ $config['driver_options'][PDO::MYSQL_ATTR_SSL_CA] = $this->config['ssl_ca'];
+ }
+ if ($this->config['ssl_capath']) {
+ $config['driver_options'][PDO::MYSQL_ATTR_SSL_CAPATH] = $this->config['ssl_capath'];
+ }
+ if ($this->config['ssl_cipher']) {
+ $config['driver_options'][PDO::MYSQL_ATTR_SSL_CIPHER] = $this->config['ssl_cipher'];
+ }
+ if (defined('PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')
+ && $this->config['ssl_do_not_verify_server_cert']
+ ) {
+ $config['driver_options'][PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = false;
+ }
+ }
+ $this->zendConn = new Zend_Db_Adapter_Pdo_Mysql($config);
+ } elseif ($this->config['db'] === 'pgsql') {
+ $this->zendConn = new Zend_Db_Adapter_Pdo_Pgsql($config);
+ } else {
+ throw new ConfigurationError(
+ 'Failed to connect to database. Unsupported PDO driver "%s"',
+ $this->config['db']
+ );
+ }
+
+ $this->zendConn->getConnection(); // Force connection attempt
+ }
+
+ /**
+ * Initialize PDO connection
+ *
+ * @param string $dbname The name of the database to connect with
+ */
+ private function pdoConnect($dbname)
+ {
+ if ($this->pdoConn !== null) {
+ return;
+ }
+
+ $driverOptions = array(
+ PDO::ATTR_TIMEOUT => 1,
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
+ );
+
+ if ($this->config['db'] === 'mysql'
+ && isset($this->config['use_ssl'])
+ && $this->config['use_ssl']
+ ) {
+ # The presence of these keys as empty strings or null cause non-ssl connections to fail
+ if ($this->config['ssl_key']) {
+ $driverOptions[PDO::MYSQL_ATTR_SSL_KEY] = $this->config['ssl_key'];
+ }
+ if ($this->config['ssl_cert']) {
+ $driverOptions[PDO::MYSQL_ATTR_SSL_CERT] = $this->config['ssl_cert'];
+ }
+ if ($this->config['ssl_ca']) {
+ $driverOptions[PDO::MYSQL_ATTR_SSL_CA] = $this->config['ssl_ca'];
+ }
+ if ($this->config['ssl_capath']) {
+ $driverOptions[PDO::MYSQL_ATTR_SSL_CAPATH] = $this->config['ssl_capath'];
+ }
+ if ($this->config['ssl_cipher']) {
+ $driverOptions[PDO::MYSQL_ATTR_SSL_CIPHER] = $this->config['ssl_cipher'];
+ }
+ if (defined('PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')
+ && $this->config['ssl_do_not_verify_server_cert']
+ ) {
+ $driverOptions[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = false;
+ }
+ }
+
+ $this->pdoConn = new PDO(
+ $this->buildDsn($this->config['db'], $dbname),
+ $this->config['username'],
+ $this->config['password'],
+ $driverOptions
+ );
+ }
+
+ /**
+ * Return a datasource name for the given database type and name
+ *
+ * @param string $dbtype
+ * @param string $dbname
+ *
+ * @return string
+ *
+ * @throws ConfigurationError In case the passed database type is not supported
+ */
+ protected function buildDsn($dbtype, $dbname = null)
+ {
+ if ($dbtype === 'mysql') {
+ return 'mysql:host=' . $this->config['host'] . ';port=' . $this->config['port']
+ . ($dbname !== null ? ';dbname=' . $dbname : '');
+ } elseif ($dbtype === 'pgsql') {
+ return 'pgsql:host=' . $this->config['host'] . ';port=' . $this->config['port']
+ . ($dbname !== null ? ';dbname=' . $dbname : '');
+ } else {
+ throw new ConfigurationError(
+ 'Failed to build data source name. Unsupported PDO driver "%s"',
+ $dbtype
+ );
+ }
+ }
+
+ /**
+ * Try to connect to the server and throw an exception if this fails
+ *
+ * @throws PDOException In case an error occurs that does not indicate that authentication failed
+ */
+ public function checkConnectivity()
+ {
+ try {
+ $this->connectToHost();
+ } catch (PDOException $e) {
+ if ($this->config['db'] === 'mysql') {
+ $code = $e->getCode();
+ /*
+ * 1040 .. Too many connections
+ * 1045 .. Access denied for user '%s'@'%s' (using password: %s)
+ * 1698 .. Access denied for user '%s'@'%s'
+ */
+ if ($code !== 1040 && $code !== 1045 && $code !== 1698) {
+ throw $e;
+ }
+ } elseif ($this->config['db'] === 'pgsql') {
+ if (strpos($e->getMessage(), $this->config['username']) === false) {
+ throw $e;
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the given identifier escaped with backticks
+ *
+ * @param string $identifier The identifier to escape
+ *
+ * @return string
+ *
+ * @throws LogicException In case there is no behaviour implemented for the current PDO driver
+ */
+ public function quoteIdentifier($identifier)
+ {
+ if ($this->config['db'] === 'mysql') {
+ return '`' . str_replace('`', '``', $identifier) . '`';
+ } elseif ($this->config['db'] === 'pgsql') {
+ return '"' . str_replace('"', '""', $identifier) . '"';
+ } else {
+ throw new LogicException('Unable to quote identifier.');
+ }
+ }
+
+ /**
+ * Return the given table name with all wildcards being escaped
+ *
+ * @param string $tableName
+ *
+ * @return string
+ *
+ * @throws LogicException In case there is no behaviour implemented for the current PDO driver
+ */
+ public function escapeTableWildcards($tableName)
+ {
+ if ($this->config['db'] === 'mysql') {
+ return str_replace(array('_', '%'), array('\_', '\%'), $tableName);
+ }
+
+ throw new LogicException('Unable to escape table wildcards.');
+ }
+
+ /**
+ * Return the given value escaped as string
+ *
+ * @param mixed $value The value to escape
+ *
+ * @return string
+ *
+ * @throws LogicException In case there is no behaviour implemented for the current PDO driver
+ */
+ public function quote($value)
+ {
+ $quoted = $this->pdoConn->quote($value);
+ if ($quoted === false) {
+ throw new LogicException(sprintf('Unable to quote value: %s', $value));
+ }
+
+ return $quoted;
+ }
+
+ /**
+ * Execute a SQL statement and return the affected row count
+ *
+ * Use $params to use a prepared statement.
+ *
+ * @param string $statement The statement to execute
+ * @param array $params The params to bind
+ *
+ * @return int
+ */
+ public function exec($statement, $params = array())
+ {
+ if (empty($params)) {
+ return $this->pdoConn->exec($statement);
+ }
+
+ $stmt = $this->pdoConn->prepare($statement);
+ $stmt->execute($params);
+ return $stmt->rowCount();
+ }
+
+ /**
+ * Execute a SQL statement and return the result
+ *
+ * Use $params to use a prepared statement.
+ *
+ * @param string $statement The statement to execute
+ * @param array $params The params to bind
+ *
+ * @return mixed
+ */
+ public function query($statement, $params = array())
+ {
+ if ($this->zendConn !== null) {
+ return $this->zendConn->query($statement, $params);
+ }
+
+ if (empty($params)) {
+ return $this->pdoConn->query($statement);
+ }
+
+ $stmt = $this->pdoConn->prepare($statement);
+ $stmt->execute($params);
+ return $stmt;
+ }
+
+ /**
+ * Return the version of the server currently connected to
+ *
+ * @return string|null
+ */
+ public function getServerVersion()
+ {
+ if ($this->config['db'] === 'mysql') {
+ return $this->query('show variables like "version"')->fetchColumn(1) ?: null;
+ } elseif ($this->config['db'] === 'pgsql') {
+ return $this->query('show server_version')->fetchColumn() ?: null;
+ } else {
+ throw new LogicException(
+ sprintf('Unable to fetch the server\'s version. Unsupported PDO driver "%s"', $this->config['db'])
+ );
+ }
+ }
+
+ /**
+ * Import the given SQL file
+ *
+ * @param string $filepath The file to import
+ */
+ public function import($filepath)
+ {
+ $file = new File($filepath);
+ $content = join(PHP_EOL, iterator_to_array($file)); // There is no fread() before PHP 5.5 :(
+
+ foreach (preg_split('@;(?! \\\\)@', $content) as $statement) {
+ if (($statement = trim($statement)) !== '') {
+ $this->exec($statement);
+ }
+ }
+ }
+
+ /**
+ * Return whether the given privileges were granted
+ *
+ * @param array $privileges An array of strings with the required privilege names
+ * @param array $context An array describing the context for which the given privileges need to apply.
+ * Only one or more table names are currently supported
+ * @param string $username The login name for which to check the privileges,
+ * if NULL the current login is used
+ *
+ * @return bool
+ */
+ public function checkPrivileges(array $privileges, array $context = null, $username = null)
+ {
+ if ($this->config['db'] === 'mysql') {
+ return $this->checkMysqlPrivileges($privileges, false, $context, $username);
+ } elseif ($this->config['db'] === 'pgsql') {
+ return $this->checkPgsqlPrivileges($privileges, false, $context, $username);
+ }
+ }
+
+ /**
+ * Return whether the given privileges are grantable to other users
+ *
+ * @param array $privileges The privileges that should be grantable
+ *
+ * @return bool
+ */
+ public function isGrantable($privileges)
+ {
+ if ($this->config['db'] === 'mysql') {
+ return $this->checkMysqlPrivileges($privileges, true);
+ } elseif ($this->config['db'] === 'pgsql') {
+ return $this->checkPgsqlPrivileges($privileges, true);
+ }
+ }
+
+ /**
+ * Grant all given privileges to the given user
+ *
+ * @param array $privileges The privilege names to grant
+ * @param array $context An array describing the context for which the given privileges need to apply.
+ * Only one or more table names are currently supported
+ * @param string $username The username to grant the privileges to
+ */
+ public function grantPrivileges(array $privileges, array $context, $username)
+ {
+ if ($this->config['db'] === 'mysql') {
+ list($_, $host) = explode('@', $this->query('select current_user()')->fetchColumn());
+ $quotedDbName = $this->quoteIdentifier($this->config['dbname']);
+
+ $grant = 'GRANT %s';
+ $on = ' ON %s.%s';
+ $to = sprintf(
+ ' TO %s@%s',
+ $this->quoteIdentifier($username),
+ $this->quoteIdentifier($host)
+ );
+
+ $dbPrivileges = array();
+ $tablePrivileges = array();
+ foreach (array_intersect($privileges, array_keys($this->mysqlGrantContexts)) as $privilege) {
+ if (! empty($context) && $this->mysqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
+ $tablePrivileges[] = $privilege;
+ } elseif ($this->mysqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
+ $dbPrivileges[] = $privilege;
+ }
+ }
+
+ if (! empty($tablePrivileges)) {
+ $tableGrant = sprintf($grant, join(',', $tablePrivileges));
+ foreach ($context as $table) {
+ $this->exec($tableGrant . sprintf($on, $quotedDbName, $this->quoteIdentifier($table)) . $to);
+ }
+ }
+
+ if (! empty($dbPrivileges)) {
+ $this->exec(
+ sprintf($grant, join(',', $dbPrivileges))
+ . sprintf($on, $this->escapeTableWildcards($quotedDbName), '*')
+ . $to
+ );
+ }
+ } elseif ($this->config['db'] === 'pgsql') {
+ $dbPrivileges = array();
+ $tablePrivileges = array();
+ foreach (array_intersect($privileges, array_keys($this->pgsqlGrantContexts)) as $privilege) {
+ if (! empty($context) && $this->pgsqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
+ $tablePrivileges[] = $privilege;
+ } elseif ($this->pgsqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
+ $dbPrivileges[] = $privilege;
+ }
+ }
+
+ if (! empty($dbPrivileges)) {
+ $this->exec(sprintf(
+ 'GRANT %s ON DATABASE %s TO %s',
+ join(',', $dbPrivileges),
+ $this->config['dbname'],
+ $username
+ ));
+ }
+
+ if (! empty($tablePrivileges)) {
+ foreach ($context as $table) {
+ $this->exec(sprintf(
+ 'GRANT %s ON TABLE %s TO %s',
+ join(',', $tablePrivileges),
+ $table,
+ $username
+ ));
+ }
+ }
+ }
+ }
+
+ /**
+ * Return a list of all existing database tables
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $this->assertConnectedToDb();
+ return $this->zendConn->listTables();
+ }
+
+ /**
+ * Return whether the given database login exists
+ *
+ * @param string $username The username to search
+ *
+ * @return bool
+ */
+ public function hasLogin($username)
+ {
+ if ($this->config['db'] === 'mysql') {
+ $queryString = <<<EOD
+SELECT 1
+ FROM information_schema.user_privileges
+ WHERE grantee = REPLACE(CONCAT("'", REPLACE(CURRENT_USER(), '@', "'@'"), "'"), :current, :wanted)
+EOD;
+
+ $query = $this->query(
+ $queryString,
+ array(
+ ':current' => $this->config['username'],
+ ':wanted' => $username
+ )
+ );
+ return count($query->fetchAll()) > 0;
+ } elseif ($this->config['db'] === 'pgsql') {
+ $query = $this->query(
+ 'SELECT 1 FROM pg_catalog.pg_user WHERE usename = :ident LIMIT 1',
+ array(':ident' => $username)
+ );
+ return count($query->fetchAll()) === 1;
+ }
+ }
+
+ /**
+ * Add a new database login
+ *
+ * @param string $username The username of the new login
+ * @param string $password The password of the new login
+ */
+ public function addLogin($username, $password)
+ {
+ if ($this->config['db'] === 'mysql') {
+ list($_, $host) = explode('@', $this->query('select current_user()')->fetchColumn());
+ $this->exec(
+ 'CREATE USER :user@:host IDENTIFIED BY :passw',
+ array(':user' => $username, ':host' => $host, ':passw' => $password)
+ );
+ } elseif ($this->config['db'] === 'pgsql') {
+ $this->exec(sprintf(
+ 'CREATE USER %s WITH PASSWORD %s',
+ $this->quoteIdentifier($username),
+ $this->quote($password)
+ ));
+ }
+ }
+
+ /**
+ * Check whether the current user has the given privileges
+ *
+ * @param array $privileges The privilege names
+ * @param bool $requireGrants Only return true when all privileges can be granted to others
+ * @param array $context An array describing the context for which the given privileges need to apply.
+ * Only one or more table names are currently supported
+ * @param string $username The login name to which the passed privileges need to be granted
+ *
+ * @return bool
+ */
+ protected function checkMysqlPrivileges(
+ array $privileges,
+ $requireGrants = false,
+ array $context = null,
+ $username = null
+ ) {
+ $mysqlPrivileges = array_intersect($privileges, array_keys($this->mysqlGrantContexts));
+ list($_, $host) = explode('@', $this->query('select current_user()')->fetchColumn());
+ $grantee = "'" . ($username === null ? $this->config['username'] : $username) . "'@'" . $host . "'";
+
+ if (isset($this->config['dbname'])) {
+ $dbPrivileges = array();
+ $tablePrivileges = array();
+ foreach ($mysqlPrivileges as $privilege) {
+ if (! empty($context) && $this->mysqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
+ $tablePrivileges[] = $privilege;
+ }
+ if ($this->mysqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
+ $dbPrivileges[] = $privilege;
+ }
+ }
+
+ $dbPrivilegesGranted = true;
+ $tablePrivilegesGranted = true;
+
+ if (! empty($dbPrivileges)) {
+ $queryString = 'SELECT COUNT(*) as matches'
+ . ' FROM information_schema.schema_privileges'
+ . ' WHERE grantee = :grantee'
+ . ' AND table_schema = :dbname'
+ . ' AND privilege_type IN (%s)'
+ . ($requireGrants ? " AND is_grantable = 'YES'" : '');
+
+ $dbAndTableQuery = $this->query(
+ sprintf($queryString, join(',', array_map(array($this, 'quote'), $dbPrivileges))),
+ array(':grantee' => $grantee, ':dbname' => $this->escapeTableWildcards($this->config['dbname']))
+ );
+ $grantedDbAndTablePrivileges = (int) $dbAndTableQuery->fetchObject()->matches;
+ if ($grantedDbAndTablePrivileges === count($dbPrivileges)) {
+ $tableExclusivePrivileges = array_diff($tablePrivileges, $dbPrivileges);
+ if (! empty($tableExclusivePrivileges)) {
+ $tablePrivileges = $tableExclusivePrivileges;
+ $tablePrivilegesGranted = false;
+ }
+ } else {
+ $tablePrivilegesGranted = false;
+ $dbExclusivePrivileges = array_diff($dbPrivileges, $tablePrivileges);
+ if (! empty($dbExclusivePrivileges)) {
+ $dbExclusiveQuery = $this->query(
+ sprintf($queryString, join(',', array_map(array($this, 'quote'), $dbExclusivePrivileges))),
+ array(
+ ':grantee' => $grantee,
+ ':dbname' => $this->escapeTableWildcards($this->config['dbname'])
+ )
+ );
+ $dbPrivilegesGranted = (int) $dbExclusiveQuery->fetchObject()->matches === count(
+ $dbExclusivePrivileges
+ );
+ }
+ }
+ }
+
+ if (! $tablePrivilegesGranted && !empty($tablePrivileges)) {
+ $query = $this->query(
+ 'SELECT COUNT(*) as matches'
+ . ' FROM information_schema.table_privileges'
+ . ' WHERE grantee = :grantee'
+ . ' AND table_schema = :dbname'
+ . ' AND table_name IN (' . join(',', array_map(array($this, 'quote'), $context)) . ')'
+ . ' AND privilege_type IN (' . join(',', array_map(array($this, 'quote'), $tablePrivileges)) . ')'
+ . ($requireGrants ? " AND is_grantable = 'YES'" : ''),
+ array(':grantee' => $grantee, ':dbname' => $this->config['dbname'])
+ );
+ $expectedAmountOfMatches = count($context) * count($tablePrivileges);
+ $tablePrivilegesGranted = (int) $query->fetchObject()->matches === $expectedAmountOfMatches;
+ }
+
+ if ($dbPrivilegesGranted && $tablePrivilegesGranted) {
+ return true;
+ }
+ }
+
+ $query = $this->query(
+ 'SELECT COUNT(*) as matches FROM information_schema.user_privileges WHERE grantee = :grantee'
+ . ' AND privilege_type IN (' . join(',', array_map(array($this, 'quote'), $mysqlPrivileges)) . ')'
+ . ($requireGrants ? " AND is_grantable = 'YES'" : ''),
+ array(':grantee' => $grantee)
+ );
+ return (int) $query->fetchObject()->matches === count($mysqlPrivileges);
+ }
+
+ /**
+ * Check whether the current user has the given privileges
+ *
+ * Note that database and table specific privileges (i.e. not SUPER, CREATE and CREATEROLE) are ignored
+ * in case no connection to the database defined in the resource configuration has been established
+ *
+ * @param array $privileges The privilege names
+ * @param bool $requireGrants Only return true when all privileges can be granted to others
+ * @param array $context An array describing the context for which the given privileges need to apply.
+ * Only one or more table names are currently supported
+ * @param string $username The login name to which the passed privileges need to be granted
+ *
+ * @return bool
+ */
+ public function checkPgsqlPrivileges(
+ array $privileges,
+ $requireGrants = false,
+ array $context = null,
+ $username = null
+ ) {
+ $privilegesGranted = true;
+ if ($this->dbFromConfig) {
+ $dbPrivileges = array();
+ $tablePrivileges = array();
+ foreach (array_intersect($privileges, array_keys($this->pgsqlGrantContexts)) as $privilege) {
+ if (! empty($context) && $this->pgsqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
+ $tablePrivileges[] = $privilege;
+ }
+ if ($this->pgsqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
+ $dbPrivileges[] = $privilege;
+ }
+ }
+
+ if (! empty($dbPrivileges)) {
+ $dbExclusivesGranted = true;
+ foreach ($dbPrivileges as $dbPrivilege) {
+ $query = $this->query(
+ 'SELECT has_database_privilege(:user, :dbname, :privilege) AS db_privilege_granted',
+ array(
+ ':user' => $username !== null ? $username : $this->config['username'],
+ ':dbname' => $this->config['dbname'],
+ ':privilege' => $dbPrivilege . ($requireGrants ? ' WITH GRANT OPTION' : '')
+ )
+ );
+ if (! $query->fetchObject()->db_privilege_granted) {
+ $privilegesGranted = false;
+ if (! in_array($dbPrivilege, $tablePrivileges)) {
+ $dbExclusivesGranted = false;
+ }
+ }
+ }
+
+ if ($privilegesGranted) {
+ // Do not check privileges twice if they are already granted at database level
+ $tablePrivileges = array_diff($tablePrivileges, $dbPrivileges);
+ } elseif ($dbExclusivesGranted) {
+ $privilegesGranted = true;
+ }
+ }
+
+ if ($privilegesGranted && !empty($tablePrivileges)) {
+ foreach (array_intersect($context, $this->listTables()) as $table) {
+ foreach ($tablePrivileges as $tablePrivilege) {
+ $query = $this->query(
+ 'SELECT has_table_privilege(:user, :table, :privilege) AS table_privilege_granted',
+ array(
+ ':user' => $username !== null ? $username : $this->config['username'],
+ ':table' => $table,
+ ':privilege' => $tablePrivilege . ($requireGrants ? ' WITH GRANT OPTION' : '')
+ )
+ );
+ $privilegesGranted &= $query->fetchObject()->table_privilege_granted;
+ }
+ }
+ }
+ } else {
+ // In case we cannot check whether the user got the required db-/table-privileges due to not being
+ // connected to the database defined in the resource configuration it is safe to just ignore them
+ // as the chances are very high that the database is created later causing the current user being
+ // the owner with ALL privileges. (Which in turn can be granted to others.)
+
+ if (array_search('CREATE', $privileges, true) !== false) {
+ $query = $this->query(
+ 'select rolcreatedb from pg_roles where rolname = :user',
+ array(':user' => $username !== null ? $username : $this->config['username'])
+ );
+ $privilegesGranted &= $query->fetchColumn() !== false;
+ }
+ }
+
+ if (array_search('CREATEROLE', $privileges, true) !== false) {
+ $query = $this->query(
+ 'select rolcreaterole from pg_roles where rolname = :user',
+ array(':user' => $username !== null ? $username : $this->config['username'])
+ );
+ $privilegesGranted &= $query->fetchColumn() !== false;
+ }
+
+ if (array_search('SUPER', $privileges, true) !== false) {
+ $query = $this->query(
+ 'select rolsuper from pg_roles where rolname = :user',
+ array(':user' => $username !== null ? $username : $this->config['username'])
+ );
+ $privilegesGranted &= $query->fetchColumn() !== false;
+ }
+
+ return (bool) $privilegesGranted;
+ }
+}
diff --git a/modules/setup/library/Setup/Utils/EnableModuleStep.php b/modules/setup/library/Setup/Utils/EnableModuleStep.php
new file mode 100644
index 0000000..92af5b7
--- /dev/null
+++ b/modules/setup/library/Setup/Utils/EnableModuleStep.php
@@ -0,0 +1,77 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Utils;
+
+use Exception;
+use Icinga\Application\Icinga;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\IcingaException;
+use Icinga\Module\Setup\Step;
+
+class EnableModuleStep extends Step
+{
+ protected $modulePaths;
+
+ protected $moduleNames;
+
+ protected $errors;
+
+ protected $warnings;
+
+ public function __construct(array $moduleNames)
+ {
+ $this->moduleNames = $moduleNames;
+
+ $this->modulePaths = array();
+ if (($appModulePath = realpath(Icinga::app()->getApplicationDir() . '/../modules')) !== false) {
+ $this->modulePaths[] = $appModulePath;
+ }
+ }
+
+ public function apply()
+ {
+ $moduleManager = Icinga::app()->getModuleManager();
+ $moduleManager->detectInstalledModules($this->modulePaths);
+
+ $success = true;
+ foreach ($this->moduleNames as $moduleName) {
+ try {
+ $moduleManager->enableModule($moduleName);
+ } catch (ConfigurationError $e) {
+ $this->warnings[$moduleName] = $e;
+ } catch (Exception $e) {
+ $this->errors[$moduleName] = $e;
+ $success = false;
+ }
+ }
+
+ return $success;
+ }
+
+ public function getSummary()
+ {
+ // Enabling a module is like a implicit action, which does not need to be shown to the user...
+ }
+
+ public function getReport()
+ {
+ $okMessage = mt('setup', 'Module "%s" has been successfully enabled.');
+ $failMessage = mt('setup', 'Module "%s" could not be enabled. An error occured:');
+
+ $report = array();
+ foreach ($this->moduleNames as $moduleName) {
+ if (isset($this->errors[$moduleName])) {
+ $report[] = sprintf($failMessage, $moduleName);
+ $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->errors[$moduleName]));
+ } elseif (isset($this->warnings[$moduleName])) {
+ $report[] = sprintf($failMessage, $moduleName);
+ $report[] = sprintf(mt('setup', 'WARNING: %s'), $this->warnings[$moduleName]->getMessage());
+ } else {
+ $report[] = sprintf($okMessage, $moduleName);
+ }
+ }
+
+ return $report;
+ }
+}
diff --git a/modules/setup/library/Setup/Web/Form/Validator/TokenValidator.php b/modules/setup/library/Setup/Web/Form/Validator/TokenValidator.php
new file mode 100644
index 0000000..a3f218b
--- /dev/null
+++ b/modules/setup/library/Setup/Web/Form/Validator/TokenValidator.php
@@ -0,0 +1,73 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Web\Form\Validator;
+
+use Exception;
+use Zend_Validate_Abstract;
+use Icinga\Util\File;
+
+/**
+ * Validator that checks if a token matches with the contents of a corresponding token-file
+ */
+class TokenValidator extends Zend_Validate_Abstract
+{
+ /**
+ * The path to the token file
+ *
+ * @var string
+ */
+ protected $tokenPath;
+
+ /**
+ * Create a new TokenValidator
+ *
+ * @param string $tokenPath The path to the token-file
+ */
+ public function __construct($tokenPath)
+ {
+ $this->tokenPath = $tokenPath;
+ $this->_messageTemplates = array(
+ 'TOKEN_FILE_ERROR' => sprintf(
+ mt('setup', 'Cannot validate token: %s (%s)'),
+ $tokenPath,
+ '%value%'
+ ),
+ 'TOKEN_FILE_EMPTY' => sprintf(
+ mt('setup', 'Cannot validate token, file "%s" is empty. Please define a token.'),
+ $tokenPath
+ ),
+ 'TOKEN_INVALID' => mt('setup', 'Invalid token supplied.')
+ );
+ }
+
+ /**
+ * Validate the given token with the one in the token-file
+ *
+ * @param string $value The token to validate
+ * @param null $context The form context (ignored)
+ *
+ * @return bool
+ */
+ public function isValid($value, $context = null)
+ {
+ try {
+ $file = new File($this->tokenPath);
+ $expectedToken = trim($file->fgets());
+ } catch (Exception $e) {
+ $msg = $e->getMessage();
+ $this->_error('TOKEN_FILE_ERROR', substr($msg, strpos($msg, ']: ') + 3));
+ return false;
+ }
+
+ if (empty($expectedToken)) {
+ $this->_error('TOKEN_FILE_EMPTY');
+ return false;
+ } elseif ($value !== $expectedToken) {
+ $this->_error('TOKEN_INVALID');
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/modules/setup/library/Setup/WebWizard.php b/modules/setup/library/Setup/WebWizard.php
new file mode 100644
index 0000000..f25be55
--- /dev/null
+++ b/modules/setup/library/Setup/WebWizard.php
@@ -0,0 +1,752 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup;
+
+use Icinga\Application\Platform;
+use Icinga\Module\Setup\Requirement\SetRequirement;
+use Icinga\Module\Setup\Requirement\WebLibraryRequirement;
+use PDOException;
+use Icinga\Web\Form;
+use Icinga\Web\Wizard;
+use Icinga\Web\Request;
+use Icinga\Application\Config;
+use Icinga\Application\Icinga;
+use Icinga\Module\Setup\Forms\ModulePage;
+use Icinga\Module\Setup\Forms\WelcomePage;
+use Icinga\Module\Setup\Forms\SummaryPage;
+use Icinga\Module\Setup\Forms\DbResourcePage;
+use Icinga\Module\Setup\Forms\AuthBackendPage;
+use Icinga\Module\Setup\Forms\AdminAccountPage;
+use Icinga\Module\Setup\Forms\LdapDiscoveryPage;
+//use Icinga\Module\Setup\Forms\LdapDiscoveryConfirmPage;
+use Icinga\Module\Setup\Forms\LdapResourcePage;
+use Icinga\Module\Setup\Forms\RequirementsPage;
+use Icinga\Module\Setup\Forms\GeneralConfigPage;
+use Icinga\Module\Setup\Forms\AuthenticationPage;
+use Icinga\Module\Setup\Forms\DatabaseCreationPage;
+use Icinga\Module\Setup\Forms\UserGroupBackendPage;
+use Icinga\Module\Setup\Steps\DatabaseStep;
+use Icinga\Module\Setup\Steps\GeneralConfigStep;
+use Icinga\Module\Setup\Steps\ResourceStep;
+use Icinga\Module\Setup\Steps\AuthenticationStep;
+use Icinga\Module\Setup\Steps\UserGroupStep;
+use Icinga\Module\Setup\Utils\EnableModuleStep;
+use Icinga\Module\Setup\Utils\DbTool;
+use Icinga\Module\Setup\Requirement\OSRequirement;
+use Icinga\Module\Setup\Requirement\PhpModuleRequirement;
+use Icinga\Module\Setup\Requirement\PhpVersionRequirement;
+use Icinga\Module\Setup\Requirement\ConfigDirectoryRequirement;
+use Icinga\Module\Monitoring\Forms\Config\Transport\ApiTransportForm;
+
+/**
+ * Icinga Web 2 Setup Wizard
+ */
+class WebWizard extends Wizard implements SetupWizard
+{
+ /**
+ * The privileges required by Icinga Web 2 to create the database and a login
+ *
+ * @var array
+ */
+ protected $databaseCreationPrivileges = array(
+ 'CREATE',
+ 'CREATE USER', // MySQL
+ 'CREATEROLE' // PostgreSQL
+ );
+
+ /**
+ * The privileges required by Icinga Web 2 to setup the database
+ *
+ * @var array
+ */
+ protected $databaseSetupPrivileges = array(
+ 'CREATE',
+ 'ALTER', // MySQL only
+ 'REFERENCES'
+ );
+
+ /**
+ * The privileges required by Icinga Web 2 to operate the database
+ *
+ * @var array
+ */
+ protected $databaseUsagePrivileges = array(
+ 'SELECT',
+ 'INSERT',
+ 'UPDATE',
+ 'DELETE',
+ 'EXECUTE',
+ 'TEMPORARY', // PostgreSql
+ 'CREATE TEMPORARY TABLES' // MySQL
+ );
+
+ /**
+ * The database tables operated by Icinga Web 2
+ *
+ * @var array
+ */
+ protected $databaseTables = array(
+ 'icingaweb_group',
+ 'icingaweb_group_membership',
+ 'icingaweb_user',
+ 'icingaweb_user_preference',
+ 'icingaweb_rememberme'
+ );
+
+ /**
+ * Register all pages and module wizards for this wizard
+ */
+ protected function init()
+ {
+ $this->addPage(new WelcomePage());
+ $this->addPage(new ModulePage());
+ $this->addPage(new RequirementsPage());
+ $this->addPage(new AuthenticationPage());
+ $this->addPage(new DbResourcePage(array('name' => 'setup_auth_db_resource')));
+ $this->addPage(new DatabaseCreationPage(array('name' => 'setup_auth_db_creation')));
+ $this->addPage(new LdapDiscoveryPage());
+ //$this->addPage(new LdapDiscoveryConfirmPage());
+ $this->addPage(new LdapResourcePage());
+ $this->addPage(new AuthBackendPage());
+ $this->addPage(new UserGroupBackendPage());
+ $this->addPage(new AdminAccountPage());
+ $this->addPage(new GeneralConfigPage());
+ $this->addPage(new DbResourcePage(array('name' => 'setup_config_db_resource')));
+ $this->addPage(new DatabaseCreationPage(array('name' => 'setup_config_db_creation')));
+ $this->addPage(new SummaryPage(array('name' => 'setup_summary')));
+
+ if (($modulePageData = $this->getPageData('setup_modules')) !== null) {
+ $modulePage = $this->getPage('setup_modules')->populate($modulePageData);
+ foreach ($modulePage->getModuleWizards() as $moduleWizard) {
+ $this->addPage($moduleWizard);
+ }
+ }
+ }
+
+ /**
+ * Setup the given page that is either going to be displayed or validated
+ *
+ * @param Form $page The page to setup
+ * @param Request $request The current request
+ */
+ public function setupPage(Form $page, Request $request)
+ {
+ if ($page->getName() === 'setup_requirements') {
+ $page->setWizard($this);
+ } elseif ($page->getName() === 'setup_authentication_backend') {
+ /** @var AuthBackendPage $page */
+
+ $authData = $this->getPageData('setup_authentication_type');
+ if ($authData['type'] === 'db') {
+ $page->setResourceConfig($this->getPageData('setup_auth_db_resource'));
+ } elseif ($authData['type'] === 'ldap') {
+ $page->setResourceConfig($this->getPageData('setup_ldap_resource'));
+
+ $suggestions = $this->getPageData('setup_ldap_discovery');
+ if (isset($suggestions['backend'])) {
+ $page->setSuggestions($suggestions['backend']);
+ }
+
+ if ($this->getDirection() === static::FORWARD) {
+ $backendConfig = $this->getPageData('setup_authentication_backend');
+ if ($backendConfig !== null && $request->getPost('name') !== $backendConfig['name']) {
+ $pageData = & $this->getPageData();
+ unset($pageData['setup_usergroup_backend']);
+ }
+ }
+ }
+
+ if ($this->getDirection() === static::FORWARD) {
+ $backendConfig = $this->getPageData('setup_authentication_backend');
+ if ($backendConfig !== null && $request->getPost('backend') !== $backendConfig['backend']) {
+ $pageData = & $this->getPageData();
+ unset($pageData['setup_usergroup_backend']);
+ }
+ }
+ /*} elseif ($page->getName() === 'setup_ldap_discovery_confirm') {
+ $page->setResourceConfig($this->getPageData('setup_ldap_discovery'));*/
+ } elseif ($page->getName() === 'setup_auth_db_resource') {
+ $page->addDescription(mt(
+ 'setup',
+ 'Now please configure the database resource where to store users and user groups.'
+ ));
+ $page->addDescription(mt(
+ 'setup',
+ 'Note that the database itself does not need to exist at this time as'
+ . ' it is going to be created once the wizard is about to be finished.'
+ ));
+ } elseif ($page->getName() === 'setup_usergroup_backend') {
+ $page->setResourceConfig($this->getPageData('setup_ldap_resource'));
+ $page->setBackendConfig($this->getPageData('setup_authentication_backend'));
+ } elseif ($page->getName() === 'setup_admin_account') {
+ $page->setBackendConfig($this->getPageData('setup_authentication_backend'));
+ $page->setGroupConfig($this->getPageData('setup_usergroup_backend'));
+ $authData = $this->getPageData('setup_authentication_type');
+ if ($authData['type'] === 'db') {
+ $page->setResourceConfig($this->getPageData('setup_auth_db_resource'));
+ } elseif ($authData['type'] === 'ldap') {
+ $page->setResourceConfig($this->getPageData('setup_ldap_resource'));
+ }
+ } elseif ($page->getName() === 'setup_auth_db_creation' || $page->getName() === 'setup_config_db_creation') {
+ $page->setDatabaseSetupPrivileges(
+ array_unique(array_merge($this->databaseCreationPrivileges, $this->databaseSetupPrivileges))
+ );
+ $page->setDatabaseUsagePrivileges($this->databaseUsagePrivileges);
+ $page->setResourceConfig(
+ $this->getPageData('setup_auth_db_resource') ?: $this->getPageData('setup_config_db_resource')
+ );
+ } elseif ($page->getName() === 'setup_summary') {
+ $page->setSubjectTitle('Icinga Web 2');
+ $page->setSummary($this->getSetup()->getSummary());
+ } elseif ($page->getName() === 'setup_config_db_resource') {
+ $page->addDescription(mt(
+ 'setup',
+ 'Now please configure the database resource where to store user preferences.'
+ ));
+ $page->addDescription(mt(
+ 'setup',
+ 'Note that the database itself does not need to exist at this time as'
+ . ' it is going to be created once the wizard is about to be finished.'
+ ));
+
+ $ldapData = $this->getPageData('setup_ldap_resource');
+ if ($ldapData !== null && $request->getPost('name') === $ldapData['name']) {
+ $page->error(
+ mt('setup', 'The given resource name must be unique and is already in use by the LDAP resource')
+ );
+ }
+ } elseif ($page->getName() === 'setup_ldap_resource') {
+ $suggestion = $this->getPageData('setup_ldap_discovery');
+ if (isset($suggestion['resource'])) {
+ $page->populate($suggestion['resource']);
+ }
+
+ if ($this->getDirection() === static::FORWARD) {
+ $resourceConfig = $this->getPageData('setup_ldap_resource');
+ if ($resourceConfig !== null && $request->getPost('name') !== $resourceConfig['name']) {
+ $pageData = & $this->getPageData();
+ unset($pageData['setup_usergroup_backend']);
+ }
+ }
+ } elseif ($page->getName() === 'setup_authentication_type') {
+ $authData = $this->getPageData($page->getName());
+ $pageData = & $this->getPageData();
+
+ if ($authData !== null && $request->getPost('type') !== $authData['type']) {
+ // Drop any existing page data in case the authentication type has changed,
+ // otherwise it will conflict with other forms that depend on this one
+ unset($pageData['setup_admin_account']);
+ unset($pageData['setup_authentication_backend']);
+
+ if ($authData['type'] === 'db') {
+ unset($pageData['setup_auth_db_resource']);
+ unset($pageData['setup_auth_db_creation']);
+ } elseif ($request->getPost('type') === 'db') {
+ unset($pageData['setup_config_db_resource']);
+ unset($pageData['setup_config_db_creation']);
+ }
+ } elseif (isset($authData['type']) && $authData['type'] == 'external') {
+ // If you choose the authentication type external and validate the database and then come
+ // back to change the authentication type but do not change it, you will get an database configuration
+ // related error message on the next page. To avoid this error, the 'setup_config_db_resource'
+ // page must be unset.
+
+ unset($pageData['setup_config_db_resource']);
+ }
+ }
+ }
+
+ /**
+ * Return the new page to set as current page
+ *
+ * {@inheritdoc} Runs additional checks related to some registered pages.
+ *
+ * @param string $requestedPage The name of the requested page
+ * @param Form $originPage The origin page
+ *
+ * @return Form The new page
+ *
+ * @throws InvalidArgumentException In case the requested page does not exist or is not permitted yet
+ */
+ protected function getNewPage($requestedPage, Form $originPage)
+ {
+ $skip = false;
+ $newPage = parent::getNewPage($requestedPage, $originPage);
+ if ($newPage->getName() === 'setup_auth_db_resource') {
+ $authData = $this->getPageData('setup_authentication_type');
+ $skip = $authData['type'] !== 'db';
+ } elseif ($newPage->getname() === 'setup_ldap_discovery') {
+ $authData = $this->getPageData('setup_authentication_type');
+ $skip = $authData['type'] !== 'ldap';
+ /*} elseif ($newPage->getName() === 'setup_ldap_discovery_confirm') {
+ $skip = false === $this->hasPageData('setup_ldap_discovery');*/
+ } elseif ($newPage->getName() === 'setup_ldap_resource') {
+ $authData = $this->getPageData('setup_authentication_type');
+ $skip = $authData['type'] !== 'ldap';
+ } elseif ($newPage->getName() === 'setup_usergroup_backend') {
+ $backendConfig = $this->getPageData('setup_authentication_backend');
+ $skip = $backendConfig['backend'] !== 'ldap';
+ } elseif ($newPage->getName() === 'setup_config_db_resource') {
+ $authData = $this->getPageData('setup_authentication_type');
+ $skip = $authData['type'] === 'db';
+ } elseif (in_array($newPage->getName(), array('setup_auth_db_creation', 'setup_config_db_creation'))) {
+ if (($newPage->getName() === 'setup_auth_db_creation' || $this->hasPageData('setup_config_db_resource'))
+ && (($config = $this->getPageData('setup_auth_db_resource')) !== null
+ || ($config = $this->getPageData('setup_config_db_resource')) !== null)
+ && !$config['skip_validation'] && $this->getDirection() == static::FORWARD
+ ) {
+ // Execute this code only if the direction is forward.
+ // Otherwise, an error will be output when you go back.
+ $db = new DbTool($config);
+
+ try {
+ $db->connectToDb(); // Are we able to login on the database?
+
+ if (array_search(reset($this->databaseTables), $db->listTables(), true) === false) {
+ // In case the database schema does not yet exist the
+ // user needs the privileges to setup the database
+ $skip = $db->checkPrivileges($this->databaseSetupPrivileges, $this->databaseTables);
+ } else {
+ // In case the database schema exists the user needs the required privileges
+ // to operate the database, if those are missing we ask for another user
+ $skip = $db->checkPrivileges($this->databaseUsagePrivileges, $this->databaseTables);
+ }
+ } catch (PDOException $_) {
+ try {
+ $db->connectToHost(); // Are we able to login on the server?
+ // It is not possible to reliably determine whether a database exists or not if a user can't
+ // log in to the database, so we just require the user to be able to create the database
+ $skip = $db->checkPrivileges(
+ array_unique(
+ array_merge($this->databaseCreationPrivileges, $this->databaseSetupPrivileges)
+ ),
+ $this->databaseTables
+ );
+ } catch (PDOException $_) {
+ // We are NOT able to login on the server..
+ }
+ }
+ } else {
+ $skip = true;
+ }
+ }
+
+ return $skip ? $this->skipPage($newPage) : $newPage;
+ }
+
+ /**
+ * Add buttons to the given page based on its position in the page-chain
+ *
+ * @param Form $page The page to add the buttons to
+ */
+ protected function addButtons(Form $page)
+ {
+ parent::addButtons($page);
+
+ $pages = $this->getPages();
+ $index = array_search($page, $pages, true);
+ if ($index === 0) {
+ $page->getElement(static::BTN_NEXT)->setLabel(
+ mt('setup', 'Start', 'setup.welcome.btn.next')
+ );
+ } elseif ($index === count($pages) - 1) {
+ $page->getElement(static::BTN_NEXT)->setLabel(
+ mt('setup', 'Setup Icinga Web 2', 'setup.summary.btn.finish')
+ );
+ }
+
+ $authData = $this->getPageData('setup_authentication_type');
+ $veto = $page->getName() === 'setup_authentication_backend' && $authData['type'] === 'db';
+ if (! $veto && in_array($page->getName(), array(
+ 'setup_authentication_backend',
+ 'setup_auth_db_resource',
+ 'setup_config_db_resource',
+ 'setup_ldap_resource',
+ 'setup_monitoring_ido',
+ 'setup_icingadb_resource',
+ 'setup_icingadb_redis',
+ 'setup_icingadb_api_transport'
+ ))) {
+ $page->addElement(
+ 'submit',
+ 'backend_validation',
+ array(
+ 'ignore' => true,
+ 'label' => t('Validate Configuration'),
+ 'data-progress-label' => t('Validation In Progress'),
+ 'decorators' => array('ViewHelper'),
+ 'formnovalidate' => 'formnovalidate'
+ )
+ );
+ $page->getDisplayGroup('buttons')->addElement($page->getElement('backend_validation'));
+ }
+
+ if ($page->getName() === 'setup_command_transport') {
+ if ($page->getSubForm('transport_form')->getSubForm('transport_form') instanceof ApiTransportForm) {
+ $page->addElement(
+ 'submit',
+ 'transport_validation',
+ array(
+ 'ignore' => true,
+ 'label' => t('Validate Configuration'),
+ 'data-progress-label' => t('Validation In Progress'),
+ 'decorators' => array('ViewHelper'),
+ 'formnovalidate' => 'formnovalidate'
+ )
+ );
+ $page->getDisplayGroup('buttons')->addElement($page->getElement('transport_validation'));
+ }
+ }
+ }
+
+ /**
+ * Clear the session being used by this wizard
+ *
+ * @param bool $removeToken If true, the setup token will be removed
+ */
+ public function clearSession($removeToken = true)
+ {
+ parent::clearSession();
+
+ if ($removeToken) {
+ $tokenPath = Config::resolvePath('setup.token');
+ if (file_exists($tokenPath)) {
+ @unlink($tokenPath);
+ }
+ }
+ }
+
+ /**
+ * Return the setup for this wizard
+ *
+ * @return Setup
+ */
+ public function getSetup()
+ {
+ $pageData = $this->getPageData();
+ $setup = new Setup();
+
+ if (isset($pageData['setup_auth_db_resource'])
+ && !$pageData['setup_auth_db_resource']['skip_validation']
+ && (! isset($pageData['setup_auth_db_creation'])
+ || !$pageData['setup_auth_db_creation']['skip_validation']
+ )
+ ) {
+ $setup->addStep(
+ new DatabaseStep(array(
+ 'tables' => $this->databaseTables,
+ 'privileges' => $this->databaseUsagePrivileges,
+ 'resourceConfig' => $pageData['setup_auth_db_resource'],
+ 'adminName' => isset($pageData['setup_auth_db_creation']['username'])
+ ? $pageData['setup_auth_db_creation']['username']
+ : null,
+ 'adminPassword' => isset($pageData['setup_auth_db_creation']['password'])
+ ? $pageData['setup_auth_db_creation']['password']
+ : null,
+ 'schemaPath' => Config::module('setup')
+ ->get('schema', 'path', Icinga::app()->getBaseDir('schema'))
+ ))
+ );
+ } elseif (isset($pageData['setup_config_db_resource'])
+ && !$pageData['setup_config_db_resource']['skip_validation']
+ && (! isset($pageData['setup_config_db_creation'])
+ || !$pageData['setup_config_db_creation']['skip_validation']
+ )
+ ) {
+ $setup->addStep(
+ new DatabaseStep(array(
+ 'tables' => $this->databaseTables,
+ 'privileges' => $this->databaseUsagePrivileges,
+ 'resourceConfig' => $pageData['setup_config_db_resource'],
+ 'adminName' => isset($pageData['setup_config_db_creation']['username'])
+ ? $pageData['setup_config_db_creation']['username']
+ : null,
+ 'adminPassword' => isset($pageData['setup_config_db_creation']['password'])
+ ? $pageData['setup_config_db_creation']['password']
+ : null,
+ 'schemaPath' => Config::module('setup')
+ ->get('schema', 'path', Icinga::app()->getBaseDir('schema'))
+ ))
+ );
+ }
+
+ $setup->addStep(
+ new GeneralConfigStep(array(
+ 'generalConfig' => $pageData['setup_general_config'],
+ 'resourceName' => isset($pageData['setup_auth_db_resource']['name'])
+ ? $pageData['setup_auth_db_resource']['name']
+ : (isset($pageData['setup_config_db_resource']['name'])
+ ? $pageData['setup_config_db_resource']['name']
+ : null
+ )
+ ))
+ );
+
+ $adminAccountType = $pageData['setup_admin_account']['user_type'];
+ if ($adminAccountType === 'user_group') {
+ $adminAccountData = array('groupname' => $pageData['setup_admin_account'][$adminAccountType]);
+ } else {
+ $adminAccountData = array('username' => $pageData['setup_admin_account'][$adminAccountType]);
+ if ($adminAccountType === 'new_user' && !$pageData['setup_auth_db_resource']['skip_validation']
+ && (! isset($pageData['setup_auth_db_creation'])
+ || !$pageData['setup_auth_db_creation']['skip_validation']
+ )
+ ) {
+ $adminAccountData['resourceConfig'] = $pageData['setup_auth_db_resource'];
+ $adminAccountData['password'] = $pageData['setup_admin_account']['new_user_password'];
+ }
+ }
+ $authType = $pageData['setup_authentication_type']['type'];
+ $setup->addStep(
+ new AuthenticationStep(array(
+ 'adminAccountData' => $adminAccountData,
+ 'backendConfig' => $pageData['setup_authentication_backend'],
+ 'resourceName' => $authType === 'db' ? $pageData['setup_auth_db_resource']['name'] : (
+ $authType === 'ldap' ? $pageData['setup_ldap_resource']['name'] : null
+ )
+ ))
+ );
+
+ if ($authType !== 'external') {
+ $setup->addStep(
+ new UserGroupStep(array(
+ 'backendConfig' => $pageData['setup_authentication_backend'],
+ 'groupConfig' => isset($pageData['setup_usergroup_backend'])
+ ? $pageData['setup_usergroup_backend']
+ : null,
+ 'resourceName' => $authType === 'db'
+ ? $pageData['setup_auth_db_resource']['name']
+ : $pageData['setup_ldap_resource']['name'],
+ 'resourceConfig' => $authType === 'db'
+ ? $pageData['setup_auth_db_resource']
+ : null,
+ 'username' => $authType === 'db'
+ ? $pageData['setup_admin_account'][$adminAccountType]
+ : null
+ ))
+ );
+ }
+
+ if (isset($pageData['setup_auth_db_resource'])
+ || isset($pageData['setup_config_db_resource'])
+ || isset($pageData['setup_ldap_resource'])
+ ) {
+ $setup->addStep(
+ new ResourceStep(array(
+ 'dbResourceConfig' => isset($pageData['setup_auth_db_resource'])
+ ? array_diff_key($pageData['setup_auth_db_resource'], array('skip_validation' => null))
+ : (isset($pageData['setup_config_db_resource'])
+ ? array_diff_key($pageData['setup_config_db_resource'], array('skip_validation' => null))
+ : null
+ ),
+ 'ldapResourceConfig' => isset($pageData['setup_ldap_resource'])
+ ? array_diff_key($pageData['setup_ldap_resource'], array('skip_validation' => null))
+ : null
+ ))
+ );
+ }
+
+ foreach ($this->getWizards() as $wizard) {
+ if ($wizard->isComplete()) {
+ $setup->addSteps($wizard->getSetup()->getSteps());
+ }
+ }
+
+ $setup->addStep(new EnableModuleStep(array_keys($this->getPage('setup_modules')->getCheckedModules())));
+
+ return $setup;
+ }
+
+ /**
+ * Return the requirements of this wizard
+ *
+ * @return RequirementSet
+ */
+ public function getRequirements($skipModules = false)
+ {
+ $set = new RequirementSet();
+
+ $set->add(new PhpVersionRequirement(array(
+ 'condition' => array('>=', '7.2'),
+ 'description' => sprintf(mt(
+ 'setup',
+ 'Running Icinga Web 2 requires PHP version %s.'
+ ), '7.2')
+ )));
+
+ $set->add(new OSRequirement(array(
+ 'optional' => true,
+ 'condition' => 'linux',
+ 'description' => mt(
+ 'setup',
+ 'Icinga Web 2 is developed for and tested on Linux. While we cannot'
+ . ' guarantee they will, other platforms may also perform as well.'
+ )
+ )));
+
+ $set->add(new WebLibraryRequirement(array(
+ 'condition' => ['icinga-php-library', '>=', '0.9.0'],
+ 'alias' => 'Icinga PHP library',
+ 'description' => mt(
+ 'setup',
+ 'The Icinga PHP library (IPL) is required for Icinga Web 2 and modules'
+ )
+ )));
+
+ $set->add(new WebLibraryRequirement(array(
+ 'condition' => ['icinga-php-thirdparty', '>=', '0.11.0'],
+ 'alias' => 'Icinga PHP Thirdparty',
+ 'description' => mt(
+ 'setup',
+ 'The Icinga PHP Thirdparty library is required for Icinga Web 2 and modules'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'condition' => 'OpenSSL',
+ 'description' => mt(
+ 'setup',
+ 'The PHP module for OpenSSL is required to generate cryptographically safe password salts.'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'condition' => 'XML',
+ 'description' => mt(
+ 'setup',
+ 'The XML module for PHP is required for Markdown and custom HTML annotations.'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'condition' => 'JSON',
+ 'description' => mt(
+ 'setup',
+ 'The JSON module for PHP is required for various export functionalities as well as APIs.'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'condition' => 'gettext',
+ 'description' => mt(
+ 'setup',
+ 'For message localization, the gettext module for PHP is required.'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'condition' => 'INTL',
+ 'description' => mt(
+ 'setup',
+ 'For language, timezone and date/time format negotiation, the INTL module for PHP is required.'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'condition' => 'DOM',
+ 'description' => mt(
+ 'setup',
+ 'For charts and exports of views and reports to PDF, the DOM module for PHP is required.'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'optional' => true,
+ 'condition' => 'LDAP',
+ 'description' => mt(
+ 'setup',
+ 'If you\'d like to authenticate users using LDAP the corresponding PHP module is required.'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'optional' => true,
+ 'condition' => 'mbstring',
+ 'description' => mt(
+ 'setup',
+ 'In case you want views being exported to PDF, you\'ll need the mbstring extension for PHP.'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'optional' => true,
+ 'condition' => 'GD',
+ 'description' => mt(
+ 'setup',
+ 'In case you want views being exported to PDF, you\'ll need the GD extension for PHP.'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'optional' => true,
+ 'condition' => 'Imagick',
+ 'description' => mt(
+ 'setup',
+ 'In case you want graphs being exported to PDF as well, you\'ll need the ImageMagick extension for PHP.'
+ )
+ )));
+
+ $dbSet = new RequirementSet(false, RequirementSet::MODE_OR);
+ $dbSet->add(new PhpModuleRequirement(array(
+ 'optional' => true,
+ 'condition' => 'pdo_mysql',
+ 'alias' => 'PDO-MySQL',
+ 'description' => mt(
+ 'setup',
+ 'To store users or preferences in a MySQL database the PDO-MySQL module for PHP is required.'
+ )
+ )));
+ $dbSet->add(new PhpModuleRequirement(array(
+ 'optional' => true,
+ 'condition' => 'pdo_pgsql',
+ 'alias' => 'PDO-PostgreSQL',
+ 'description' => mt(
+ 'setup',
+ 'To store users or preferences in a PostgreSQL database the PDO-PostgreSQL module for PHP is required.'
+ )
+ )));
+ $set->merge($dbSet);
+
+ $dbRequire = (new SetRequirement(array(
+ 'optional' => false,
+ 'condition' => $dbSet,
+ 'title' =>'Database',
+ 'alias' => 'PDO-MySQL OR PDO-PostgreSQL',
+ 'description' => mt(
+ 'setup',
+ 'A database is mandatory, therefore at least one module '
+ . 'PDO-MySQL or PDO-PostgreSQL for PHP is required.'
+ )
+ )));
+
+ $set->add($dbRequire);
+
+ $set->add(new ConfigDirectoryRequirement(array(
+ 'condition' => Icinga::app()->getStorageDir(),
+ 'title' => mt('setup', 'Read- and writable storage directory'),
+ 'description' => mt(
+ 'setup',
+ 'The Icinga Web 2 storage directory defaults to "/var/lib/icingaweb2", if' .
+ ' not explicitly set in the environment variable "ICINGAWEB_STORAGEDIR".'
+ )
+ )));
+
+ $set->add(new ConfigDirectoryRequirement(array(
+ 'condition' => Icinga::app()->getConfigDir(),
+ 'description' => mt(
+ 'setup',
+ 'The Icinga Web 2 configuration directory defaults to "/etc/icingaweb2", if' .
+ ' not explicitly set in the environment variable "ICINGAWEB_CONFIGDIR".'
+ )
+ )));
+
+ if (! $skipModules) {
+ foreach ($this->getWizards() as $wizard) {
+ $set->merge($wizard->getRequirements());
+ }
+ }
+
+ return $set;
+ }
+}
diff --git a/modules/setup/library/Setup/Webserver.php b/modules/setup/library/Setup/Webserver.php
new file mode 100644
index 0000000..2251ba3
--- /dev/null
+++ b/modules/setup/library/Setup/Webserver.php
@@ -0,0 +1,233 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup;
+
+use Icinga\Application\Icinga;
+use Icinga\Exception\ProgrammingError;
+
+/**
+ * Base class for generating webserver configuration
+ */
+abstract class Webserver
+{
+ /**
+ * Document root
+ *
+ * @var string
+ */
+ protected $documentRoot;
+
+ /**
+ * URL path of Icinga Web 2
+ *
+ * @var string
+ */
+ protected $urlPath = '/icingaweb2';
+
+ /**
+ * Path to Icinga Web 2's configuration files
+ *
+ * @var string
+ */
+ protected $configDir;
+
+ /**
+ * Address or path where to pass requests to FPM
+ *
+ * @var string
+ */
+ protected $fpmUri;
+
+ /**
+ * Enable to pass requests to FPM
+ *
+ * @var bool
+ */
+ protected $enableFpm = false;
+
+ /**
+ * Create instance by type name
+ *
+ * @param string $type
+ *
+ * @return WebServer
+ *
+ * @throws ProgrammingError
+ */
+ public static function createInstance($type)
+ {
+ $class = __NAMESPACE__ . '\\Webserver\\' . ucfirst($type);
+ if (class_exists($class)) {
+ return new $class();
+ }
+ throw new ProgrammingError('Class "%s" does not exist', $class);
+ }
+
+ /**
+ * Generate configuration
+ *
+ * @return string
+ */
+ public function generate()
+ {
+ $template = $this->getTemplate();
+
+ $searchTokens = array(
+ '{urlPath}',
+ '{documentRoot}',
+ '{aliasDocumentRoot}',
+ '{configDir}',
+ '{fpmUri}'
+ );
+ $replaceTokens = array(
+ $this->getUrlPath(),
+ $this->getDocumentRoot(),
+ preg_match('~/$~', $this->getUrlPath()) ? $this->getDocumentRoot() . '/' : $this->getDocumentRoot(),
+ $this->getConfigDir(),
+ $this->getFpmUri()
+ );
+ $template = str_replace($searchTokens, $replaceTokens, $template);
+ return $template;
+ }
+
+ /**
+ * Specific template
+ *
+ * @return string
+ */
+ abstract protected function getTemplate();
+
+ /**
+ * Set the URL path of Icinga Web 2
+ *
+ * @param string $urlPath
+ *
+ * @return $this
+ */
+ public function setUrlPath($urlPath)
+ {
+ $this->urlPath = '/' . ltrim(trim((string) $urlPath), '/');
+ return $this;
+ }
+
+ /**
+ * Get the URL path of Icinga Web 2
+ *
+ * @return string
+ */
+ public function getUrlPath()
+ {
+ return $this->urlPath;
+ }
+
+ /**
+ * Set the document root
+ *
+ * @param string $documentRoot
+ *
+ * @return $this
+ */
+ public function setDocumentRoot($documentRoot)
+ {
+ $this->documentRoot = trim((string) $documentRoot);
+ return $this;
+ }
+
+ /**
+ * Detect the document root
+ *
+ * @return string
+ */
+ public function detectDocumentRoot()
+ {
+ return Icinga::app()->getBaseDir('public');
+ }
+
+ /**
+ * Get the document root
+ *
+ * @return string
+ */
+ public function getDocumentRoot()
+ {
+ if ($this->documentRoot === null) {
+ $this->documentRoot = $this->detectDocumentRoot();
+ }
+ return $this->documentRoot;
+ }
+
+ /**
+ * Set the configuration directory
+ *
+ * @param string $configDir
+ *
+ * @return $this
+ */
+ public function setConfigDir($configDir)
+ {
+ $this->configDir = (string) $configDir;
+ return $this;
+ }
+
+ /**
+ * Get the configuration directory
+ *
+ * @return string
+ */
+ public function getConfigDir()
+ {
+ if ($this->configDir === null) {
+ return Icinga::app()->getConfigDir();
+ }
+ return $this->configDir;
+ }
+
+ /**
+ * Get whether FPM is enabled
+ *
+ * @return bool
+ */
+ public function getEnableFpm()
+ {
+ return $this->enableFpm;
+ }
+
+ /**
+ * Set FPM enabled
+ *
+ * @param bool $flag
+ *
+ * @return $this
+ */
+ public function setEnableFpm($flag)
+ {
+ $this->enableFpm = (bool) $flag;
+
+ return $this;
+ }
+
+ /**
+ * Get the address or path where to pass requests to FPM
+ *
+ * @return string
+ */
+ public function getFpmUri()
+ {
+ return $this->fpmUri;
+ }
+
+ /**
+ * Set the address or path where to pass requests to FPM
+ *
+ * @param string $uri
+ *
+ * @return $this
+ */
+ public function setFpmUri($uri)
+ {
+ $this->fpmUri = (string) $uri;
+
+ return $this;
+ }
+}
diff --git a/modules/setup/library/Setup/Webserver/Apache.php b/modules/setup/library/Setup/Webserver/Apache.php
new file mode 100644
index 0000000..fdb367f
--- /dev/null
+++ b/modules/setup/library/Setup/Webserver/Apache.php
@@ -0,0 +1,142 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Webserver;
+
+use Icinga\Module\Setup\Webserver;
+
+/**
+ * Generate Apache 2.x configuration
+ */
+class Apache extends Webserver
+{
+ protected $fpmUri = '127.0.0.1:9000';
+
+ protected function getTemplate()
+ {
+ if (! $this->enableFpm) {
+ return <<<'EOD'
+Alias {urlPath} "{aliasDocumentRoot}"
+
+# Remove comments if you want to use PHP FPM and your Apache version is older than 2.4
+#<IfVersion < 2.4>
+# # Forward PHP requests to FPM
+# SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
+# <LocationMatch "^{urlPath}/(.*\.php)$">
+# ProxyPassMatch "fcgi://{fpmUri}/{documentRoot}/$1"
+# </LocationMatch>
+#</IfVersion>
+
+<Directory "{documentRoot}">
+ Options SymLinksIfOwnerMatch
+ AllowOverride None
+
+ DirectoryIndex index.php
+
+ <IfModule mod_authz_core.c>
+ # Apache 2.4
+ <RequireAll>
+ Require all granted
+ </RequireAll>
+ </IfModule>
+
+ <IfModule !mod_authz_core.c>
+ # Apache 2.2
+ Order allow,deny
+ Allow from all
+ </IfModule>
+
+ SetEnv ICINGAWEB_CONFIGDIR "{configDir}"
+
+ EnableSendfile Off
+
+ <IfModule mod_rewrite.c>
+ RewriteEngine on
+ RewriteBase {urlPath}/
+ RewriteCond %{REQUEST_FILENAME} -s [OR]
+ RewriteCond %{REQUEST_FILENAME} -l [OR]
+ RewriteCond %{REQUEST_FILENAME} -d
+ RewriteRule ^.*$ - [NC,L]
+ RewriteRule ^.*$ index.php [NC,L]
+ </IfModule>
+
+ <IfModule !mod_rewrite.c>
+ DirectoryIndex error_norewrite.html
+ ErrorDocument 404 {urlPath}/error_norewrite.html
+ </IfModule>
+
+# Remove comments if you want to use PHP FPM and your Apache version
+# is greater than or equal to 2.4
+# <IfVersion >= 2.4>
+# # Forward PHP requests to FPM
+# SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
+# <FilesMatch "\.php$">
+# SetHandler "proxy:fcgi://{fpmUri}"
+# ErrorDocument 503 {urlPath}/error_unavailable.html
+# </FilesMatch>
+# </IfVersion>
+</Directory>
+EOD;
+ } else {
+ return <<<'EOD'
+Alias {urlPath} "{aliasDocumentRoot}"
+
+<IfVersion < 2.4>
+ # Forward PHP requests to FPM
+ SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
+ <LocationMatch "^{urlPath}/(.*\.php)$">
+ ProxyPassMatch "fcgi://{fpmUri}/{documentRoot}/$1"
+ </LocationMatch>
+</IfVersion>
+
+<Directory "{documentRoot}">
+ Options SymLinksIfOwnerMatch
+ AllowOverride None
+
+ DirectoryIndex index.php
+
+ <IfModule mod_authz_core.c>
+ # Apache 2.4
+ <RequireAll>
+ Require all granted
+ </RequireAll>
+ </IfModule>
+
+ <IfModule !mod_authz_core.c>
+ # Apache 2.2
+ Order allow,deny
+ Allow from all
+ </IfModule>
+
+ SetEnv ICINGAWEB_CONFIGDIR "{configDir}"
+
+ EnableSendfile Off
+
+ <IfModule mod_rewrite.c>
+ RewriteEngine on
+ RewriteBase {urlPath}/
+ RewriteCond %{REQUEST_FILENAME} -s [OR]
+ RewriteCond %{REQUEST_FILENAME} -l [OR]
+ RewriteCond %{REQUEST_FILENAME} -d
+ RewriteRule ^.*$ - [NC,L]
+ RewriteRule ^.*$ index.php [NC,L]
+ </IfModule>
+
+ <IfModule !mod_rewrite.c>
+ DirectoryIndex error_norewrite.html
+ ErrorDocument 404 {urlPath}/error_norewrite.html
+ </IfModule>
+
+ <IfVersion >= 2.4>
+ # Forward PHP requests to FPM
+ SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
+ <FilesMatch "\.php$">
+ SetHandler "proxy:fcgi://{fpmUri}"
+ ErrorDocument 503 {urlPath}/error_unavailable.html
+ </FilesMatch>
+ </IfVersion>
+</Directory>
+EOD;
+ }
+ }
+}
diff --git a/modules/setup/library/Setup/Webserver/Nginx.php b/modules/setup/library/Setup/Webserver/Nginx.php
new file mode 100644
index 0000000..c7ae716
--- /dev/null
+++ b/modules/setup/library/Setup/Webserver/Nginx.php
@@ -0,0 +1,36 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Webserver;
+
+use Icinga\Module\Setup\Webserver;
+
+/**
+ * Generate nginx configuration
+ */
+class Nginx extends Webserver
+{
+ protected $fpmUri = '127.0.0.1:9000';
+
+ protected $enableFpm = true;
+
+ protected function getTemplate()
+ {
+ return <<<'EOD'
+location ~ ^{urlPath}/index\.php(.*)$ {
+ fastcgi_pass {fpmUri};
+ fastcgi_index index.php;
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME {documentRoot}/index.php;
+ fastcgi_param ICINGAWEB_CONFIGDIR {configDir};
+ fastcgi_param REMOTE_USER $remote_user;
+}
+
+location ~ ^{urlPath}(.+)? {
+ alias {documentRoot};
+ index index.php;
+ try_files $1 $uri $uri/ {urlPath}/index.php$is_args$args;
+}
+EOD;
+ }
+}