diff --git a/application/forms/Config/UserGroup/DbUserGroupBackendForm.php b/application/forms/Config/UserGroup/DbUserGroupBackendForm.php index 9f1915968..9d7545fe7 100644 --- a/application/forms/Config/UserGroup/DbUserGroupBackendForm.php +++ b/application/forms/Config/UserGroup/DbUserGroupBackendForm.php @@ -26,6 +26,32 @@ class DbUserGroupBackendForm extends Form */ public function createElements(array $formData) { + $this->addElement( + 'text', + 'name', + array( + 'required' => true, + 'label' => $this->translate('Backend Name'), + 'description' => $this->translate( + 'The name of this user group backend that is used to differentiate it from others' + ), + 'validators' => array( + array( + 'Regex', + false, + array( + 'pattern' => '/^[^\\[\\]:]+$/', + 'messages' => array( + 'regexNotMatch' => $this->translate( + 'The name cannot contain \'[\', \']\' or \':\'.' + ) + ) + ) + ) + ) + ) + ); + $resourceNames = $this->getDatabaseResourceNames(); $this->addElement( 'select', @@ -37,6 +63,15 @@ class DbUserGroupBackendForm extends Form 'multiOptions' => empty($resourceNames) ? array() : array_combine($resourceNames, $resourceNames) ) ); + + $this->addElement( + 'hidden', + 'backend', + array( + 'disabled' => true, // Prevents the element from being submitted, see #7717 + 'value' => 'db' + ) + ); } /** diff --git a/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php b/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php index 6190ab404..a426393c6 100644 --- a/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php +++ b/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php @@ -3,7 +3,6 @@ namespace Icinga\Forms\Config\UserGroup; -use Icinga\Application\Config; use Icinga\Authentication\User\UserBackend; use Icinga\Authentication\UserGroup\LdapUserGroupBackend; use Icinga\Data\ConfigObject; @@ -32,6 +31,32 @@ class LdapUserGroupBackendForm extends Form */ public function createElements(array $formData) { + $this->addElement( + 'text', + 'name', + array( + 'required' => true, + 'label' => $this->translate('Backend Name'), + 'description' => $this->translate( + 'The name of this user group backend that is used to differentiate it from others' + ), + 'validators' => array( + array( + 'Regex', + false, + array( + 'pattern' => '/^[^\\[\\]:]+$/', + 'messages' => array( + 'regexNotMatch' => $this->translate( + 'The name cannot contain \'[\', \']\' or \':\'.' + ) + ) + ) + ) + ) + ) + ); + $resourceNames = $this->getLdapResourceNames(); $this->addElement( 'select', @@ -90,6 +115,15 @@ class LdapUserGroupBackendForm extends Form $this->createGroupConfigElements($defaults, $groupConfigDisabled); $this->createUserConfigElements($defaults, $userConfigDisabled, $dnDisabled); + + $this->addElement( + 'hidden', + 'backend', + array( + 'disabled' => true, // Prevents the element from being submitted, see #7717 + 'value' => $formData['type'] + ) + ); } /** @@ -293,7 +327,7 @@ class LdapUserGroupBackendForm extends Form protected function getLdapUserBackendNames(LdapConnection $resource) { $names = array(); - foreach (Config::app('authentication') as $name => $config) { + foreach (UserBackend::getBackendConfigs() as $name => $config) { if (in_array(strtolower($config->backend), array('ldap', 'msldap'))) { $backendResource = ResourceFactory::create($config->resource); if ( diff --git a/application/forms/Config/UserGroup/UserGroupBackendForm.php b/application/forms/Config/UserGroup/UserGroupBackendForm.php index d47e1f4fa..7f57408c7 100644 --- a/application/forms/Config/UserGroup/UserGroupBackendForm.php +++ b/application/forms/Config/UserGroup/UserGroupBackendForm.php @@ -158,32 +158,6 @@ class UserGroupBackendForm extends ConfigForm */ public function createElements(array $formData) { - $this->addElement( - 'text', - 'name', - array( - 'required' => true, - 'label' => $this->translate('Backend Name'), - 'description' => $this->translate( - 'The name of this user group backend that is used to differentiate it from others' - ), - 'validators' => array( - array( - 'Regex', - false, - array( - 'pattern' => '/^[^\\[\\]:]+$/', - 'messages' => array( - 'regexNotMatch' => $this->translate( - 'The name cannot contain \'[\', \']\' or \':\'.' - ) - ) - ) - ) - ) - ) - ); - // TODO(jom): We did not think about how to configure custom group backends yet! $backendTypes = array( 'db' => $this->translate('Database'), @@ -196,15 +170,6 @@ class UserGroupBackendForm extends ConfigForm $backendType = key($backendTypes); } - $this->addElement( - 'hidden', - 'backend', - array( - 'disabled' => true, // Prevents the element from being submitted, see #7717 - 'value' => $backendType - ) - ); - $this->addElement( 'select', 'type', diff --git a/library/Icinga/Application/ApplicationBootstrap.php b/library/Icinga/Application/ApplicationBootstrap.php index 6f409789a..a8d185023 100644 --- a/library/Icinga/Application/ApplicationBootstrap.php +++ b/library/Icinga/Application/ApplicationBootstrap.php @@ -7,6 +7,7 @@ use ErrorException; use Exception; use LogicException; use Icinga\Application\Modules\Manager as ModuleManager; +use Icinga\Authentication\User\UserBackend; use Icinga\Data\ConfigObject; use Icinga\Data\ResourceFactory; use Icinga\Exception\ConfigurationError; @@ -544,6 +545,24 @@ abstract class ApplicationBootstrap return $this; } + /** + * Set up the user backend factory + * + * @return $this + */ + protected function setupUserBackendFactory() + { + try { + UserBackend::setConfig(Config::app('authentication')); + } catch (NotReadableError $e) { + Logger::error( + new IcingaException('Cannot load user backend configuration. An exception was thrown:', $e) + ); + } + + return $this; + } + /** * Detect the timezone * diff --git a/library/Icinga/Application/Cli.php b/library/Icinga/Application/Cli.php index 4f83c7537..7d2376950 100644 --- a/library/Icinga/Application/Cli.php +++ b/library/Icinga/Application/Cli.php @@ -43,6 +43,7 @@ class Cli extends ApplicationBootstrap ->setupLogger() ->setupResourceFactory() ->setupModuleManager() + ->setupUserBackendFactory() ->loadSetupModuleIfNecessary(); } diff --git a/library/Icinga/Application/Web.php b/library/Icinga/Application/Web.php index 4ab52b4e9..9494ea326 100644 --- a/library/Icinga/Application/Web.php +++ b/library/Icinga/Application/Web.php @@ -107,6 +107,7 @@ class Web extends ApplicationBootstrap ->setupZendMvc() ->setupFormNamespace() ->setupModuleManager() + ->setupUserBackendFactory() ->loadSetupModuleIfNecessary() ->loadEnabledModules() ->setupRoute() diff --git a/library/Icinga/Authentication/User/UserBackend.php b/library/Icinga/Authentication/User/UserBackend.php index 898de5909..3b8e21071 100644 --- a/library/Icinga/Authentication/User/UserBackend.php +++ b/library/Icinga/Authentication/User/UserBackend.php @@ -9,11 +9,12 @@ use Icinga\Application\Icinga; use Icinga\Data\ConfigObject; use Icinga\Data\ResourceFactory; use Icinga\Exception\ConfigurationError; +use Icinga\Util\ConfigAwareFactory; /** * Factory for user backends */ -class UserBackend +class UserBackend implements ConfigAwareFactory { /** * The default user backend types provided by Icinga Web 2 @@ -34,6 +35,48 @@ class UserBackend */ protected static $customBackends; + /** + * User backend configuration + * + * @var Config + */ + private static $backends; + + /** + * Set user backend configuration + * + * @param Config $config + */ + public static function setConfig($config) + { + self::$backends = $config; + } + + /** + * Return the configuration of all existing user backends + * + * @return Config + */ + public static function getBackendConfigs() + { + self::assertBackendsExist(); + return self::$backends; + } + + /** + * Check if any user backends exist. If not, throw an error. + * + * @throws ConfigurationError + */ + private static function assertBackendsExist() + { + if (self::$backends === null) { + throw new ConfigurationError( + 'User backends not set up. Please contact your Icinga Web administrator' + ); + } + } + /** * Register all custom user backends from all loaded modules */ @@ -110,9 +153,9 @@ class UserBackend public static function create($name, ConfigObject $backendConfig = null) { if ($backendConfig === null) { - $authConfig = Config::app('authentication'); - if ($authConfig->hasSection($name)) { - $backendConfig = $authConfig->getSection($name); + self::assertBackendsExist(); + if (self::$backends->hasSection($name)) { + $backendConfig = self::$backends->getSection($name); } else { throw new ConfigurationError('User backend "%s" does not exist', $name); } diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php index 976af266f..8015b04a0 100644 --- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php +++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php @@ -437,7 +437,7 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken throw new ProgrammingError('It is required to set a attribute name where to find a group\'s name first'); } - if ($this->ds->getCapabilities()->hasAdOid()) { + if ($this->ds->getCapabilities()->isActiveDirectory()) { $createdAtAttribute = 'whenCreated'; $lastModifiedAttribute = 'whenChanged'; } else { diff --git a/modules/monitoring/application/forms/Setup/IdoResourcePage.php b/modules/monitoring/application/forms/Setup/IdoResourcePage.php index e0b74c3ac..2bb4de3a9 100644 --- a/modules/monitoring/application/forms/Setup/IdoResourcePage.php +++ b/modules/monitoring/application/forms/Setup/IdoResourcePage.php @@ -138,6 +138,9 @@ class IdoResourcePage extends Form } $this->info($this->translate('The configuration has been successfully validated.')); + } elseif (! isset($formData['backend_validation'])) { + // This is usually done by isValid(Partial), but as we're not calling any of these... + $this->populate($formData); } return true; diff --git a/modules/setup/application/forms/AdminAccountPage.php b/modules/setup/application/forms/AdminAccountPage.php index 191f27b8b..a5dd90866 100644 --- a/modules/setup/application/forms/AdminAccountPage.php +++ b/modules/setup/application/forms/AdminAccountPage.php @@ -4,10 +4,16 @@ namespace Icinga\Module\Setup\Forms; use Exception; +use Icinga\Application\Config; use Icinga\Authentication\User\UserBackend; use Icinga\Authentication\User\DbUserBackend; use Icinga\Authentication\User\LdapUserBackend; +use Icinga\Authentication\UserGroup\UserGroupBackend; +use Icinga\Authentication\UserGroup\LdapUserGroupBackend; use Icinga\Data\ConfigObject; +use Icinga\Data\ResourceFactory; +use Icinga\Data\Selectable; +use Icinga\Exception\NotImplementedError; use Icinga\Web\Form; /** @@ -23,12 +29,19 @@ class AdminAccountPage extends Form protected $resourceConfig; /** - * The backend configuration to use + * The user backend configuration to use * * @var array */ protected $backendConfig; + /** + * The user group backend configuration to use + * + * @var array + */ + protected $groupConfig; + /** * Initialize this page */ @@ -37,7 +50,7 @@ class AdminAccountPage extends Form $this->setName('setup_admin_account'); $this->setTitle($this->translate('Administration', 'setup.page.title')); $this->addDescription($this->translate( - 'Now it\'s time to configure your first administrative account for Icinga Web 2.' + 'Now it\'s time to configure your first administrative account or group for Icinga Web 2.' )); } @@ -55,7 +68,7 @@ class AdminAccountPage extends Form } /** - * Set the backend configuration to use + * Set the user backend configuration to use * * @param array $config * @@ -67,6 +80,19 @@ class AdminAccountPage extends Form return $this; } + /** + * Set the user group backend configuration to use + * + * @param array $config + * + * @return $this + */ + public function setGroupConfig(array $config = null) + { + $this->groupConfig = $config; + return $this; + } + /** * @see Form::createElements() */ @@ -76,6 +102,13 @@ class AdminAccountPage extends Form if ($this->backendConfig['backend'] !== 'db') { $choices['by_name'] = $this->translate('By Name', 'setup.admin'); $choice = isset($formData['user_type']) ? $formData['user_type'] : 'by_name'; + + if (in_array($this->backendConfig['backend'], array('ldap', 'msldap'))) { + $groups = $this->fetchGroups(); + if (! empty($groups)) { + $choices['user_group'] = $this->translate('User Group', 'setup.admin'); + } + } } else { $choices['new_user'] = $this->translate('New User', 'setup.admin'); $choice = isset($formData['user_type']) ? $formData['user_type'] : 'new_user'; @@ -128,6 +161,23 @@ class AdminAccountPage extends Form ); } + if ($choice === 'user_group') { + $this->addElement( + 'select', + 'user_group', + array( + 'required' => true, + 'label' => $this->translate('Group Name'), + 'description' => $this->translate( + 'Choose a user group reported by the LDAP backend' + . ' to permit its members administrative access.', + 'setup.admin' + ), + 'multiOptions' => array_combine($groups, $groups) + ) + ); + } + if ($choice === 'existing_user') { $this->addElement( 'select', @@ -234,14 +284,18 @@ class AdminAccountPage extends Form } /** - * Return the names of all users the backend currently provides + * Return the names of all users the user backend currently provides * * @return array */ protected function fetchUsers() { try { - return $this->createBackend()->select(array('user_name'))->order('user_name', 'asc', true)->fetchColumn(); + return $this + ->createUserBackend() + ->select(array('user_name')) + ->order('user_name', 'asc', true) + ->fetchColumn(); } catch (Exception $_) { // No need to handle anything special here. Error means no users found. return array(); @@ -249,7 +303,7 @@ class AdminAccountPage extends Form } /** - * Return whether the backend provides a user with the given name + * Return whether the user backend provides a user with the given name * * @param string $username * @@ -258,21 +312,103 @@ class AdminAccountPage extends Form protected function hasUser($username) { try { - return $this->createBackend()->select()->where('user_name', $username)->count() > 1; + return $this + ->createUserBackend() + ->select() + ->where('user_name', $username) + ->count() > 1; } catch (Exception $_) { - return null; + return false; } } /** - * Create and return the backend + * Create and return the user backend * * @return DbUserBackend|LdapUserBackend */ - protected function createBackend() + protected function createUserBackend() { + $resourceConfig = new Config(); + $resourceConfig->setSection($this->resourceConfig['name'], $this->resourceConfig); + ResourceFactory::setConfig($resourceConfig); + $config = new ConfigObject($this->backendConfig); - $config->resource = $this->resourceConfig; + $config->resource = $this->resourceConfig['name']; return UserBackend::create(null, $config); } + + /** + * Return the names of all user groups the user group backend currently provides + * + * @return array + */ + protected function fetchGroups() + { + try { + return $this + ->createUserGroupBackend() + ->select(array('group_name')) + ->fetchColumn(); + } catch (Exception $_) { + // No need to handle anything special here. Error means no groups found. + return array(); + } + } + + /** + * Return whether the user group backend provides a user group with the given name + * + * @param string $groupname + * + * @return bool + */ + protected function hasGroup($groupname) + { + try { + return $this + ->createUserGroupBackend() + ->select() + ->where('group_name', $groupname) + ->count() > 1; + } catch (Exception $_) { + return false; + } + } + + /** + * Create and return the user group backend + * + * @return LdapUserGroupBackend + */ + protected function createUserGroupBackend() + { + $resourceConfig = new Config(); + $resourceConfig->setSection($this->resourceConfig['name'], $this->resourceConfig); + ResourceFactory::setConfig($resourceConfig); + + $backendConfig = new Config(); + $backendConfig->setSection($this->backendConfig['name'], array_merge( + $this->backendConfig, + array('resource' => $this->resourceConfig['name']) + )); + UserBackend::setConfig($backendConfig); + + if (empty($this->groupConfig)) { + $groupConfig = new ConfigObject(array( + 'backend' => $this->backendConfig['backend'], // _Should_ be "db" or "msldap" + 'resource' => $this->resourceConfig['name'], + 'user_backend' => $this->backendConfig['name'] // Gets ignored if 'backend' is "db" + )); + } else { + $groupConfig = new ConfigObject($this->groupConfig); + } + + $backend = UserGroupBackend::create(null, $groupConfig); + if (! $backend instanceof Selectable) { + throw new NotImplementedError('Unsupported, until #9772 has been resolved'); + } + + return $backend; + } } diff --git a/modules/setup/application/forms/AuthBackendPage.php b/modules/setup/application/forms/AuthBackendPage.php index 4c37a2967..24ca72b8d 100644 --- a/modules/setup/application/forms/AuthBackendPage.php +++ b/modules/setup/application/forms/AuthBackendPage.php @@ -71,6 +71,12 @@ class AuthBackendPage extends Form . 'to do now is defining a name for your first authentication backend.' )); } elseif ($this->config['type'] === 'ldap') { + $type = null; + if (! isset($formData['type']) && isset($formData['backend'])) { + $type = $formData['backend']; + $formData['type'] = $type; + } + $backendForm = new LdapBackendForm(); $backendForm->setResources(array($this->config['name'])); $backendForm->create($formData); @@ -93,7 +99,8 @@ class AuthBackendPage extends Form 'multiOptions' => array( 'ldap' => 'LDAP', 'msldap' => 'ActiveDirectory' - ) + ), + 'value' => $type ) ); } else { // $this->config['type'] === 'external' @@ -164,7 +171,10 @@ class AuthBackendPage extends Form { if (isset($formData['backend_validation']) && parent::isValid($formData)) { $self = clone $this; - $self->getSubForm('backend_form')->getElement('resource')->setIgnore(false); + if (($resourceElement = $self->getSubForm('backend_form')->getElement('resource')) !== null) { + $resourceElement->setIgnore(false); + } + $inspection = UserBackendConfigForm::inspectUserBackend($self); if ($inspection !== null) { $join = function ($e) use (& $join) { @@ -194,6 +204,9 @@ class AuthBackendPage extends Form } $this->info($this->translate('The configuration has been successfully validated.')); + } elseif (! isset($formData['backend_validation'])) { + // This is usually done by isValid(Partial), but as we're not calling any of these... + $this->populate($formData); } return true; diff --git a/modules/setup/application/forms/AuthenticationPage.php b/modules/setup/application/forms/AuthenticationPage.php index 4f455de1e..370e4bb95 100644 --- a/modules/setup/application/forms/AuthenticationPage.php +++ b/modules/setup/application/forms/AuthenticationPage.php @@ -31,11 +31,14 @@ class AuthenticationPage extends Form public function createElements(array $formData) { if (isset($formData['type']) && $formData['type'] === 'external' && !isset($_SERVER['REMOTE_USER'])) { - $this->addDescription($this->translate( - 'You\'re currently not authenticated using any of the web server\'s authentication ' - . 'mechanisms. Make sure you\'ll configure such, otherwise you\'ll not be able to ' - . 'log into Icinga Web 2.' - )); + $this->info( + $this->translate( + 'You\'re currently not authenticated using any of the web server\'s authentication ' + . 'mechanisms. Make sure you\'ll configure such, otherwise you\'ll not be able to ' + . 'log into Icinga Web 2.' + ), + false + ); } $backendTypes = array(); diff --git a/modules/setup/application/forms/DbResourcePage.php b/modules/setup/application/forms/DbResourcePage.php index a0faf932d..dd52730d3 100644 --- a/modules/setup/application/forms/DbResourcePage.php +++ b/modules/setup/application/forms/DbResourcePage.php @@ -109,6 +109,9 @@ class DbResourcePage extends Form } $this->info($this->translate('The configuration has been successfully validated.')); + } elseif (! isset($formData['backend_validation'])) { + // This is usually done by isValid(Partial), but as we're not calling any of these... + $this->populate($formData); } return true; diff --git a/modules/setup/application/forms/LdapResourcePage.php b/modules/setup/application/forms/LdapResourcePage.php index 2c558dfbb..aacfcb207 100644 --- a/modules/setup/application/forms/LdapResourcePage.php +++ b/modules/setup/application/forms/LdapResourcePage.php @@ -124,6 +124,9 @@ class LdapResourcePage extends Form } $this->info($this->translate('The configuration has been successfully validated.')); + } elseif (! isset($formData['backend_validation'])) { + // This is usually done by isValid(Partial), but as we're not calling any of these... + $this->populate($formData); } return true; diff --git a/modules/setup/application/forms/UserGroupBackendPage.php b/modules/setup/application/forms/UserGroupBackendPage.php new file mode 100644 index 000000000..fbc2d44f0 --- /dev/null +++ b/modules/setup/application/forms/UserGroupBackendPage.php @@ -0,0 +1,132 @@ +setName('setup_usergroup_backend'); + $this->setTitle($this->translate('User Group Backend', 'setup.page.title')); + $this->addDescription($this->translate( + 'To allow Icinga Web 2 to associate users and groups, you\'ll need to provide some further information' + . ' about the LDAP Connection that is already going to be used to locate account details.' + )); + } + + /** + * Set the resource configuration to use + * + * @param array $config + * + * @return $this + */ + public function setResourceConfig(array $config) + { + $this->resourceConfig = $config; + return $this; + } + + /** + * Set the user backend configuration to use + * + * @param array $config + * + * @return $this + */ + public function setBackendConfig(array $config) + { + $this->backendConfig = $config; + return $this; + } + + /** + * Return the resource configuration as Config object + * + * @return Config + */ + protected function createResourceConfiguration() + { + $config = new Config(); + $config->setSection($this->resourceConfig['name'], $this->resourceConfig); + return $config; + } + + /** + * Return the user backend configuration as Config object + * + * @return Config + */ + protected function createBackendConfiguration() + { + $config = new Config(); + $backendConfig = $this->backendConfig; + $backendConfig['resource'] = $this->resourceConfig['name']; + $config->setSection($this->backendConfig['name'], $backendConfig); + return $config; + } + + /** + * Create and add elements to this form + * + * @param array $formData + */ + public function createElements(array $formData) + { + // LdapUserGroupBackendForm requires these factories to provide valid configurations + ResourceFactory::setConfig($this->createResourceConfiguration()); + UserBackend::setConfig($this->createBackendConfiguration()); + + $formData['type'] = 'ldap'; + $formData['user_backend'] = $this->backendConfig['name']; // We're forcing the linkage anyway.. + $backendForm = new LdapUserGroupBackendForm(); + $backendForm->create($formData); + $userBackendOptions = $backendForm->getElement('user_backend')->getMultiOptions(); + unset($userBackendOptions['none']); + $backendForm->getElement('name')->setValue('icingaweb2'); + $backendForm->getElement('user_backend')->setMultiOptions($userBackendOptions); + $this->addSubForm($backendForm, 'backend_form'); + } + + /** + * Retrieve all form element values + * + * @param bool $suppressArrayNotation Ignored + * + * @return array + */ + public function getValues($suppressArrayNotation = false) + { + $values = parent::getValues(); + $values = array_merge($values, $values['backend_form']); + unset($values['backend_form']); + return $values; + } +} diff --git a/modules/setup/application/views/scripts/index/index.phtml b/modules/setup/application/views/scripts/index/index.phtml index d77d95ddc..ea0b61883 100644 --- a/modules/setup/application/views/scripts/index/index.phtml +++ b/modules/setup/application/views/scripts/index/index.phtml @@ -80,7 +80,10 @@ if ($notifications->hasMessages()) { $pos < $maxProgress ? ' visited' : ($currentPos > 2 ? ' active' : '') ); ?> -
+
@@ -109,7 +112,10 @@ if ($notifications->hasMessages()) {
-
+
diff --git a/modules/setup/application/views/scripts/index/parts/finish.phtml b/modules/setup/application/views/scripts/index/parts/finish.phtml index 7066f7f2e..a56c07c3a 100644 --- a/modules/setup/application/views/scripts/index/parts/finish.phtml +++ b/modules/setup/application/views/scripts/index/parts/finish.phtml @@ -27,7 +27,7 @@ ); ?> - + }, $report)); ?> \ No newline at end of file diff --git a/modules/setup/library/Setup/Steps/AuthenticationStep.php b/modules/setup/library/Setup/Steps/AuthenticationStep.php index 0f35e5c51..4510f6a21 100644 --- a/modules/setup/library/Setup/Steps/AuthenticationStep.php +++ b/modules/setup/library/Setup/Steps/AuthenticationStep.php @@ -32,7 +32,7 @@ class AuthenticationStep extends Step $success &= $this->createAccount(); } - $success &= $this->defineInitialAdmin(); + $success &= $this->createRolesIni(); return $success; } @@ -60,16 +60,26 @@ class AuthenticationStep extends Step return true; } - protected function defineInitialAdmin() + protected function createRolesIni() { - $config = array(); - $config['admins'] = array( - 'users' => $this->data['adminAccountData']['username'], - 'permissions' => '*' - ); + 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($config) + Config::fromArray(array(mt('setup', 'Administrators', 'setup.role.name') => $config)) ->setConfigFile(Config::resolvePath('roles.ini')) ->saveIni(); } catch (Exception $e) { @@ -94,13 +104,13 @@ class AuthenticationStep extends Step 'password' => $this->data['adminAccountData']['password'], 'is_active' => true )); + $this->dbError = false; } } catch (Exception $e) { $this->dbError = $e; return false; } - $this->dbError = false; return true; } @@ -114,7 +124,9 @@ class AuthenticationStep extends Step $backendDesc = '

' . sprintf( mt('setup', 'Users will authenticate using %s.', 'setup.summary.auth'), $authType === 'db' ? mt('setup', 'a database', 'setup.summary.auth.type') : ( - $authType === 'ldap' ? 'LDAP' : mt('setup', 'webserver authentication', 'setup.summary.auth.type') + $authType === 'ldap' || $authType === 'msldap' ? 'LDAP' : ( + mt('setup', 'webserver authentication', 'setup.summary.auth.type') + ) ) ) . '

'; @@ -125,18 +137,20 @@ class AuthenticationStep extends Step . '' . t('Backend Name') . '' . '' . $this->data['backendConfig']['name'] . '' . '' - . ($authType === 'ldap' ? ( + . ($authType === 'ldap' || $authType === 'msldap' ? ( '' . '' . mt('setup', 'User Object Class') . '' - . '' . $this->data['backendConfig']['user_class'] . '' + . '' . ($authType === 'msldap' ? 'user' : $this->data['backendConfig']['user_class']) . '' . '' . '' . '' . mt('setup', 'Custom Filter') . '' - . '' . trim($this->data['backendConfig']['filter']) ?: t('None', 'auth.ldap.filter') . '' + . '' . (trim($this->data['backendConfig']['filter']) ?: t('None', 'auth.ldap.filter')) . '' . '' . '' . '' . mt('setup', 'User Name Attribute') . '' - . '' . $this->data['backendConfig']['user_name_attribute'] . '' + . '' . ($authType === 'msldap' + ? 'sAMAccountName' + : $this->data['backendConfig']['user_name_attribute']) . '' . '' ) : ($authType === 'external' ? ( '' @@ -147,13 +161,20 @@ class AuthenticationStep extends Step . '' . ''; - $adminHtml = '

' . (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'] - )) . '

'; + if (isset($this->data['adminAccountData']['username'])) { + $adminHtml = '

' . (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'] + )) . '

'; + } else { // isset($this->data['adminAccountData']['groupname']) + $adminHtml = '

' . sprintf( + mt('setup', 'Administrative rights will initially be granted to members of the user group "%s".'), + $this->data['adminAccountData']['groupname'] + ) . '

'; + } return $pageTitle . '
' . $backendDesc . $backendTitle . $backendHtml . '
' . '
' . $adminTitle . $adminHtml . '
'; @@ -190,14 +211,23 @@ class AuthenticationStep extends Step } if ($this->permIniError === false) { - $report[] = sprintf( + $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[] = sprintf( + $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'), $this->permIniError->getMessage()); } diff --git a/modules/setup/library/Setup/Steps/UserGroupStep.php b/modules/setup/library/Setup/Steps/UserGroupStep.php new file mode 100644 index 000000000..ab58c1880 --- /dev/null +++ b/modules/setup/library/Setup/Steps/UserGroupStep.php @@ -0,0 +1,209 @@ +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 = '

' . mt('setup', 'User Groups', 'setup.page.title') . '

'; + $backendTitle = '

' . mt('setup', 'User Group Backend', 'setup.page.title') . '

'; + + $backendHtml = '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '
' . t('Backend Name') . '' . $this->data['groupConfig']['name'] . '
' . mt('setup', 'Group Object Class') . '' . $this->data['groupConfig']['group_class'] . '
' . mt('setup', 'Custom Filter') . '' . (trim($this->data['groupConfig']['group_filter']) ?: t('None', 'auth.ldap.filter')) . '
' . mt('setup', 'Group Name Attribute') . '' . $this->data['groupConfig']['group_name_attribute'] . '
'; + + return $pageTitle . '
' . $backendTitle . $backendHtml . '
'; + } + + 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/WebWizard.php b/modules/setup/library/Setup/WebWizard.php index cd1df67f4..00956d108 100644 --- a/modules/setup/library/Setup/WebWizard.php +++ b/modules/setup/library/Setup/WebWizard.php @@ -22,10 +22,12 @@ 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; @@ -104,6 +106,7 @@ class WebWizard extends Wizard implements SetupWizard //$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'))); @@ -135,15 +138,37 @@ class WebWizard extends Wizard implements SetupWizard } elseif ($authData['type'] === 'ldap') { $page->setResourceConfig($this->getPageData('setup_ldap_resource')); - $suggestions = $this->getPageData('setup_ldap_discovery'); - if (isset($suggestions['backend'])) { - $page->populate($suggestions['backend']); + if (! $this->hasPageData('setup_authentication_backend')) { + $suggestions = $this->getPageData('setup_ldap_discovery'); + if (isset($suggestions['backend'])) { + $page->populate($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_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')); @@ -173,6 +198,14 @@ class WebWizard extends Wizard implements SetupWizard 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_general_config') { $authData = $this->getPageData('setup_authentication_type'); if ($authData['type'] === 'db') { @@ -233,6 +266,9 @@ class WebWizard extends Wizard implements SetupWizard } 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'); $configData = $this->getPageData('setup_general_config'); @@ -401,14 +437,18 @@ class WebWizard extends Wizard implements SetupWizard ); $adminAccountType = $pageData['setup_admin_account']['user_type']; - $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']; + 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( @@ -421,6 +461,26 @@ class WebWizard extends Wizard implements SetupWizard )) ); + 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']) diff --git a/public/css/icinga/forms.less b/public/css/icinga/forms.less index e31081f53..467fea827 100644 --- a/public/css/icinga/forms.less +++ b/public/css/icinga/forms.less @@ -107,6 +107,53 @@ form.inline { display: inline; } +button, .button-like { + font-size: 0.9em; + font-weight: bold; + outline: 0; + color: #fff; + padding: 0.2em; + border: 1px solid; + border-color: @colorPetrol; + background: @colorPetrol; + + &[disabled] { + background-color: #666; + border-color: black; + } + + &:hover, &:focus, &:active { + background-color: #333; + border-color: #333; + cursor: pointer; + + &[disabled] { + background-color: #666; + } + } + + &.icon-only { + font-size: 1.5em; + padding: 0; + border: none; + color: inherit; + background-color: transparent; + + &:hover, &:focus, &:active { + color: #666; + } + } +} + +.button-like { + display: inline-block; +} + +a.button-like { + cursor: default; + text-decoration: none; +} + form.link-like input[type="submit"], form.link-like button[type="submit"], input.link-like, button.link-like { color: @colorLinkDefault; font-weight: normal; @@ -119,9 +166,16 @@ form.link-like input[type="submit"], form.link-like button[type="submit"], input form.link-like input[type="submit"]:hover, form.link-like input[type="submit"]:focus, +form.link-like input[type="submit"]:active, form.link-like button[type="submit"]:hover, form.link-like button[type="submit"]:focus, -input.link-like:hover, button.link-like:focus { +form.link-like button[type="submit"]:active, +input.link-like:hover, +input.link-like:focus, +input.link-like:active, +button.link-like:hover, +button.link-like:focus, +button.link-like:active { text-decoration: underline; background: none; color: @colorLinkDefault; @@ -302,47 +356,4 @@ form ul.hints { height: 0; clear: both; } -} - -button, .button-like { - font-size: 0.9em; - font-weight: bold; - outline: 0; - color: #fff; - padding: 0.2em; - border: 1px solid; - border-color: @colorPetrol; - background: @colorPetrol; - - &[disabled] { - background-color: #666; - border-color: black; - } - - &:hover, &:focus, &:active { - background-color: #333; - border-color: #333; - cursor: pointer; - - &[disabled] { - background-color: #666; - } - } - - &.icon-only { - font-size: 1.5em; - padding: 0; - border: none; - color: inherit; - background-color: transparent; - - &:hover, &:focus, &:active { - color: #666; - } - } -} - -a.button-like { - cursor: default; - text-decoration: none; } \ No newline at end of file diff --git a/public/css/icinga/setup.less b/public/css/icinga/setup.less index cae83a430..1f71be9e0 100644 --- a/public/css/icinga/setup.less +++ b/public/css/icinga/setup.less @@ -117,16 +117,8 @@ left: -1337px; } - button, .button-like { - &.finish, &.login { - min-width: 25em; - color: #fffafa; - background: @colorPetrol; - - &:hover, &:focus, &:active { - background: #666; - } - } + button.finish, a.button-like.login { + min-width: 25em; } } @@ -294,13 +286,14 @@ form#setup_requirements { } } - textarea.report { + pre.log-output { width: 66%; height: 25em; + max-height: none; } div.buttons { - margin-top: 0.5em; + margin-top: 0; text-align: center; a {