Merge branch 'master' into feature/monitoring-restrictions-9009

This commit is contained in:
Johannes Meyer 2015-06-09 09:22:50 +02:00
commit d5dffe9a7a
34 changed files with 1501 additions and 214 deletions

3
.gitattributes vendored
View File

@ -6,3 +6,6 @@ Vagrantfile export-ignore
# Normalize puppet manifests' line endings to LF on checkin and prevent conversion to CRLF when the files are checked out
.puppet* eol=lf
# Include version information on `git archive'
/application/VERSION export-subst

1
application/VERSION Normal file
View File

@ -0,0 +1 @@
$Format:%H%d %ci$

View File

@ -0,0 +1,15 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
# namespace Icinga\Application\Controllers;
use Icinga\Web\Controller\ActionController;
use Icinga\Application\Version;
class AboutController extends ActionController
{
public function indexAction()
{
$this->view->version = Version::get();
}
}

View File

@ -28,6 +28,10 @@ class GroupController extends AuthBackendController
function ($b) { return $b->getName(); },
$this->loadUserGroupBackends('Icinga\Data\Selectable')
);
if (empty($backendNames)) {
return;
}
$this->view->backendSelection = new Form();
$this->view->backendSelection->setAttrib('class', 'backend-selection');
$this->view->backendSelection->setUidDisabled();
@ -100,6 +104,7 @@ class GroupController extends AuthBackendController
$filterEditor = Widget::create('filterEditor')
->setQuery($members)
->setSearchColumns(array('user'))
->preserveParams('limit', 'sort', 'dir', 'view', 'backend', 'group')
->ignoreParams('page')
->handleRequest($this->getRequest());

View File

@ -28,6 +28,10 @@ class UserController extends AuthBackendController
function ($b) { return $b->getName(); },
$this->loadUserBackends('Icinga\Data\Selectable')
);
if (empty($backendNames)) {
return;
}
$this->view->backendSelection = new Form();
$this->view->backendSelection->setAttrib('class', 'backend-selection');
$this->view->backendSelection->setUidDisabled();
@ -99,6 +103,7 @@ class UserController extends AuthBackendController
$filterEditor = Widget::create('filterEditor')
->setQuery($memberships)
->setSearchColumns(array('group_name'))
->preserveParams('limit', 'sort', 'dir', 'view', 'backend', 'user')
->ignoreParams('page')
->handleRequest($this->getRequest());

View File

@ -52,7 +52,7 @@ class UsergroupbackendController extends Controller
$form->setIniConfig(Config::app('groups'));
$form->setOnSuccess(function (UserGroupBackendForm $form) {
try {
$form->add($form->getValues());
$form->add(array_filter($form->getValues()));
} catch (Exception $e) {
$form->error($e->getMessage());
return false;
@ -85,7 +85,12 @@ class UsergroupbackendController extends Controller
$form->setIniConfig(Config::app('groups'));
$form->setOnSuccess(function (UserGroupBackendForm $form) use ($backendName) {
try {
$form->edit($backendName, $form->getValues());
$form->edit($backendName, array_map(
function ($v) {
return $v !== '' ? $v : null;
},
$form->getValues()
));
} catch (Exception $e) {
$form->error($e->getMessage());
return false;

View File

@ -8,7 +8,7 @@ use Icinga\Web\Form;
use Icinga\Data\ConfigObject;
use Icinga\Data\ResourceFactory;
use Icinga\Exception\AuthenticationException;
use Icinga\Authentication\User\LdapUserBackend;
use Icinga\Authentication\User\UserBackend;
/**
* Form class for adding/modifying LDAP user backends
@ -48,6 +48,8 @@ class LdapBackendForm extends Form
*/
public function createElements(array $formData)
{
$isAd = isset($formData['type']) ? $formData['type'] === 'msldap' : false;
$this->addElement(
'text',
'name',
@ -77,10 +79,13 @@ class LdapBackendForm extends Form
'text',
'user_class',
array(
'required' => true,
'preserveDefault' => true,
'required' => ! $isAd,
'ignore' => $isAd,
'disabled' => $isAd ?: null,
'label' => $this->translate('LDAP User Object Class'),
'description' => $this->translate('The object class used for storing users on the LDAP server.'),
'value' => 'inetOrgPerson'
'value' => $isAd ? 'user' : 'inetOrgPerson'
)
);
$this->addElement(
@ -117,12 +122,15 @@ class LdapBackendForm extends Form
'text',
'user_name_attribute',
array(
'required' => true,
'preserveDefault' => true,
'required' => ! $isAd,
'ignore' => $isAd,
'disabled' => $isAd ?: null,
'label' => $this->translate('LDAP User Name Attribute'),
'description' => $this->translate(
'The attribute name used for storing the user name on the LDAP server.'
),
'value' => 'uid'
'value' => $isAd ? 'sAMAccountName' : 'uid'
)
);
$this->addElement(
@ -130,7 +138,7 @@ class LdapBackendForm extends Form
'backend',
array(
'disabled' => true,
'value' => 'ldap'
'value' => $isAd ? 'msldap' : 'ldap'
)
);
$this->addElement(
@ -170,8 +178,7 @@ class LdapBackendForm extends Form
public static function isValidUserBackend(Form $form)
{
try {
$ldapUserBackend = new LdapUserBackend(ResourceFactory::createResource($form->getResourceConfig()));
$ldapUserBackend->setConfig(new ConfigObject($form->getValues()));
$ldapUserBackend = UserBackend::create(null, new ConfigObject($form->getValues()));
$ldapUserBackend->assertAuthenticationPossible();
} catch (AuthenticationException $e) {
if (($previous = $e->getPrevious()) !== null) {
@ -193,6 +200,8 @@ class LdapBackendForm extends Form
* Return the configuration for the chosen resource
*
* @return ConfigObject
*
* @todo Check whether it's possible to drop this (Or even all occurences!)
*/
public function getResourceConfig()
{

View File

@ -60,16 +60,24 @@ class UserBackendConfigForm extends ConfigForm
*/
public function getBackendForm($type)
{
if ($type === 'db') {
switch ($type)
{
case 'db':
$form = new DbBackendForm();
$form->setResources(isset($this->resources['db']) ? $this->resources['db'] : array());
} elseif ($type === 'ldap') {
break;
case 'ldap':
case 'msldap':
$form = new LdapBackendForm();
$form->setResources(isset($this->resources['ldap']) ? $this->resources['ldap'] : array());
} elseif ($type === 'external') {
break;
case 'external':
$form = new ExternalBackendForm();
} else {
throw new InvalidArgumentException(sprintf($this->translate('Invalid backend type "%s" provided'), $type));
break;
default:
throw new InvalidArgumentException(
sprintf($this->translate('Invalid backend type "%s" provided'), $type)
);
}
return $form;
@ -296,6 +304,7 @@ class UserBackendConfigForm extends ConfigForm
}
if (isset($this->resources['ldap']) && ($backendType === 'ldap' || Platform::extensionLoaded('ldap'))) {
$backendTypes['ldap'] = 'LDAP';
$backendTypes['msldap'] = 'ActiveDirectory';
}
$externalBackends = array_filter(

View File

@ -0,0 +1,310 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Forms\Config\UserGroup;
use Icinga\Application\Config;
use Icinga\Authentication\User\UserBackend;
use Icinga\Authentication\UserGroup\LdapUserGroupBackend;
use Icinga\Data\ConfigObject;
use Icinga\Data\ResourceFactory;
use Icinga\Protocol\Ldap\Connection;
use Icinga\Web\Form;
use Icinga\Web\Notification;
/**
* Form for managing LDAP user group backends
*/
class LdapUserGroupBackendForm extends Form
{
/**
* Initialize this form
*/
public function init()
{
$this->setName('form_config_ldapusergroupbackend');
}
/**
* Create and add elements to this form
*
* @param array $formData
*/
public function createElements(array $formData)
{
$resourceNames = $this->getLdapResourceNames();
$this->addElement(
'select',
'resource',
array(
'required' => true,
'autosubmit' => true,
'label' => $this->translate('LDAP Connection'),
'description' => $this->translate('The LDAP connection to use for this backend.'),
'multiOptions' => array_combine($resourceNames, $resourceNames)
)
);
$resource = ResourceFactory::create(
isset($formData['resource']) && in_array($formData['resource'], $resourceNames)
? $formData['resource']
: $resourceNames[0]
);
$userBackends = array('none' => $this->translate('None', 'usergroupbackend.ldap.user_backend'));
$userBackendNames = $this->getLdapUserBackendNames($resource);
if (! empty($userBackendNames)) {
$userBackends = array_merge($userBackends, array_combine($userBackendNames, $userBackendNames));
}
$this->addElement(
'select',
'user_backend',
array(
'required' => true,
'autosubmit' => true,
'label' => $this->translate('User Backend'),
'description' => $this->translate('The user backend to link with this user group backend.'),
'multiOptions' => $userBackends
)
);
$groupBackend = new LdapUserGroupBackend($resource);
if ($formData['type'] === 'ldap') {
$defaults = $groupBackend->getOpenLdapDefaults();
$groupConfigDisabled = $userConfigDisabled = null; // MUST BE null, do NOT change this to false!
} else { // $formData['type'] === 'msldap'
$defaults = $groupBackend->getActiveDirectoryDefaults();
$groupConfigDisabled = $userConfigDisabled = true;
}
$dnDisabled = null; // MUST BE null
if (isset($formData['user_backend']) && $formData['user_backend'] !== 'none') {
$userBackend = UserBackend::create($formData['user_backend']);
$defaults->merge(array(
'user_base_dn' => $userBackend->getBaseDn(),
'user_class' => $userBackend->getUserClass(),
'user_name_attribute' => $userBackend->getUserNameAttribute(),
'user_filter' => $userBackend->getFilter()
));
$userConfigDisabled = $dnDisabled = true;
}
$this->createGroupConfigElements($defaults, $groupConfigDisabled);
$this->createUserConfigElements($defaults, $userConfigDisabled, $dnDisabled);
}
/**
* Create and add all elements to this form required for the group configuration
*
* @param ConfigObject $defaults
* @param null|bool $disabled
*/
protected function createGroupConfigElements(ConfigObject $defaults, $disabled)
{
$this->addElement(
'text',
'group_class',
array(
'preserveDefault' => true,
'ignore' => $disabled,
'disabled' => $disabled,
'label' => $this->translate('LDAP Group Object Class'),
'description' => $this->translate('The object class used for storing groups on the LDAP server.'),
'value' => $defaults->group_class
)
);
$this->addElement(
'text',
'group_filter',
array(
'preserveDefault' => true,
'allowEmpty' => true,
'label' => $this->translate('LDAP Group Filter'),
'description' => $this->translate(
'An additional filter to use when looking up groups using the specified connection. '
. 'Leave empty to not to use any additional filter rules.'
),
'requirement' => $this->translate(
'The filter needs to be expressed as standard LDAP expression, without'
. ' outer parentheses. (e.g. &(foo=bar)(bar=foo) or foo=bar)'
),
'validators' => array(
array(
'Callback',
false,
array(
'callback' => function ($v) {
return strpos($v, '(') !== 0;
},
'messages' => array(
'callbackValue' => $this->translate('The filter must not be wrapped in parantheses.')
)
)
)
),
'value' => $defaults->group_filter
)
);
$this->addElement(
'text',
'group_name_attribute',
array(
'preserveDefault' => true,
'ignore' => $disabled,
'disabled' => $disabled,
'label' => $this->translate('LDAP Group Name Attribute'),
'description' => $this->translate(
'The attribute name used for storing a group\'s name on the LDAP server.'
),
'value' => $defaults->group_name_attribute
)
);
$this->addElement(
'text',
'base_dn',
array(
'preserveDefault' => true,
'label' => $this->translate('LDAP Group Base DN'),
'description' => $this->translate(
'The path where groups can be found on the LDAP server. Leave ' .
'empty to select all users available using the specified connection.'
),
'value' => $defaults->base_dn
)
);
}
/**
* Create and add all elements to this form required for the user configuration
*
* @param ConfigObject $defaults
* @param null|bool $disabled
* @param null|bool $dnDisabled
*/
protected function createUserConfigElements(ConfigObject $defaults, $disabled, $dnDisabled)
{
$this->addElement(
'text',
'user_class',
array(
'preserveDefault' => true,
'ignore' => $disabled,
'disabled' => $disabled,
'label' => $this->translate('LDAP User Object Class'),
'description' => $this->translate('The object class used for storing users on the LDAP server.'),
'value' => $defaults->user_class
)
);
$this->addElement(
'text',
'user_filter',
array(
'preserveDefault' => true,
'allowEmpty' => true,
'ignore' => $dnDisabled,
'disabled' => $dnDisabled,
'label' => $this->translate('LDAP User Filter'),
'description' => $this->translate(
'An additional filter to use when looking up users using the specified connection. '
. 'Leave empty to not to use any additional filter rules.'
),
'requirement' => $this->translate(
'The filter needs to be expressed as standard LDAP expression, without'
. ' outer parentheses. (e.g. &(foo=bar)(bar=foo) or foo=bar)'
),
'validators' => array(
array(
'Callback',
false,
array(
'callback' => function ($v) {
return strpos($v, '(') !== 0;
},
'messages' => array(
'callbackValue' => $this->translate('The filter must not be wrapped in parantheses.')
)
)
)
),
'value' => $defaults->user_filter
)
);
$this->addElement(
'text',
'user_name_attribute',
array(
'preserveDefault' => true,
'ignore' => $disabled,
'disabled' => $disabled,
'label' => $this->translate('LDAP User Name Attribute'),
'description' => $this->translate(
'The attribute name used for storing a user\'s name on the LDAP server.'
),
'value' => $defaults->user_name_attribute
)
);
$this->addElement(
'text',
'user_base_dn',
array(
'preserveDefault' => true,
'ignore' => $dnDisabled,
'disabled' => $dnDisabled,
'label' => $this->translate('LDAP User Base DN'),
'description' => $this->translate(
'The path where users can be found on the LDAP server. Leave ' .
'empty to select all users available using the specified connection.'
),
'value' => $defaults->user_base_dn
)
);
}
/**
* Return the names of all configured LDAP resources
*
* @return array
*/
protected function getLdapResourceNames()
{
$names = array();
foreach (ResourceFactory::getResourceConfigs() as $name => $config) {
if (in_array(strtolower($config->type), array('ldap', 'msldap'))) {
$names[] = $name;
}
}
if (empty($names)) {
Notification::error(
$this->translate('No LDAP resources available. Please configure an LDAP resource first.')
);
$this->getResponse()->redirectAndExit('config/createresource');
}
return $names;
}
/**
* Return the names of all configured LDAP user backends
*
* @param Connection $resource
*
* @return array
*/
protected function getLdapUserBackendNames(Connection $resource)
{
$names = array();
foreach (Config::app('authentication') as $name => $config) {
if (in_array(strtolower($config->backend), array('ldap', 'msldap'))) {
$backendResource = ResourceFactory::create($config->resource);
if (
$backendResource->getHostname() === $resource->getHostname()
&& $backendResource->getPort() === $resource->getPort()
) {
$names[] = $name;
}
}
}
return $names;
}
}

View File

@ -13,6 +13,13 @@ use Icinga\Forms\ConfigForm;
*/
class UserGroupBackendForm extends ConfigForm
{
/**
* The backend to load when displaying the form for the first time
*
* @var string
*/
protected $backendToLoad;
/**
* Initialize this form
*/
@ -31,10 +38,17 @@ class UserGroupBackendForm extends ConfigForm
*/
public function getBackendForm($type)
{
if ($type === 'db') {
switch ($type)
{
case 'db':
return new DbUserGroupBackendForm();
} else {
throw new InvalidArgumentException(sprintf($this->translate('Invalid backend type "%s" provided'), $type));
case 'ldap':
case 'msldap':
return new LdapUserGroupBackendForm();
default:
throw new InvalidArgumentException(
sprintf($this->translate('Invalid backend type "%s" provided'), $type)
);
}
}
@ -53,10 +67,7 @@ class UserGroupBackendForm extends ConfigForm
throw new NotFoundError('No user group backend called "%s" found', $name);
}
$data = $this->config->getSection($name)->toArray();
$data['type'] = $data['backend'];
$data['name'] = $name;
$this->populate($data);
$this->backendToLoad = $name;
return $this;
}
@ -103,13 +114,23 @@ class UserGroupBackendForm extends ConfigForm
}
$backendConfig = $this->config->getSection($name);
if (isset($data['name']) && $data['name'] !== $name) {
if (isset($data['name'])) {
if ($data['name'] !== $name) {
$this->config->removeSection($name);
$name = $data['name'];
}
unset($data['name']);
}
$this->config->setSection($name, $backendConfig->merge($data));
$backendConfig->merge($data);
foreach ($backendConfig->toArray() as $k => $v) {
if ($v === null) {
unset($backendConfig->$k);
}
}
$this->config->setSection($name, $backendConfig);
return $this;
}
@ -161,7 +182,9 @@ class UserGroupBackendForm extends ConfigForm
// TODO(jom): We did not think about how to configure custom group backends yet!
$backendTypes = array(
'db' => $this->translate('Database')
'db' => $this->translate('Database'),
'ldap' => 'LDAP',
'msldap' => 'ActiveDirectory'
);
$backendType = isset($formData['type']) ? $formData['type'] : null;
@ -191,8 +214,34 @@ class UserGroupBackendForm extends ConfigForm
)
);
$backendForm = $this->getBackendForm($backendType);
$backendForm->createElements($formData);
$this->addElements($backendForm->getElements());
$this->addSubForm($this->getBackendForm($backendType)->create($formData), 'backend_form');
}
/**
* Populate the configuration of the backend to load
*/
public function onRequest()
{
if ($this->backendToLoad) {
$data = $this->config->getSection($this->backendToLoad)->toArray();
$data['type'] = $data['backend'];
$data['name'] = $this->backendToLoad;
$this->populate($data);
}
}
/**
* 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;
}
}

View File

@ -0,0 +1,25 @@
<div class="content">
<h1>Icinga Web 2</h1>
<?php
$versionInfo = array();
if ($version !== false) {
foreach (array(
'appVersion' => $this->translate('Version: %s'),
'gitCommitID' => $this->translate('Git commit ID: %s'),
'gitCommitDate' => $this->translate('Git commit date: %s')
) as $key => $label) {
if (array_key_exists($key, $version) && null !== ($value = $version[$key])) {
$versionInfo[] = sprintf($label, htmlspecialchars($value));
}
}
}
echo (
0 === count($versionInfo)
? '<p class="message-error">' . $this->translate(
'Can\'t determine Icinga Web 2\'s version'
)
: '<p>' . nl2br(implode("\n", $versionInfo), false)
) . '</p>';
?>
</div>

View File

@ -18,7 +18,7 @@ if (! $this->compact): ?>
<div class="content groups">
<?php
if ($backend === null) {
if (! isset($backend)) {
echo $this->translate('No backend found which is able to list groups') . '</div>';
return;
} else {

View File

@ -67,7 +67,7 @@
</tbody>
</table>
<?php endif ?>
<a data-base-target="_next" href="<?= $this->href('role/new') ?>">
<a data-base-target="_next" href="<?= $this->href('role/add') ?>">
<?= $this->translate('Create a New Role') ?>
</a>
</div>

View File

@ -18,7 +18,7 @@ if (! $this->compact): ?>
<div class="content users">
<?php
if ($backend === null) {
if (! isset($backend)) {
echo $this->translate('No backend found which is able to list users') . '</div>';
return;
} else {

View File

@ -218,6 +218,7 @@ rm -rf %{buildroot}
%{basedir}/application/forms
%{basedir}/application/layouts
%{basedir}/application/views
%{basedir}/application/VERSION
%{basedir}/doc
%{basedir}/modules
%{basedir}/public

View File

@ -59,6 +59,122 @@ class Platform
return strtoupper(substr(self::getOperatingSystemName(), 0, 5)) === 'LINUX';
}
/**
* Return the Linux distribution's name
* or 'linux' if the name could not be found out
* or false if the OS isn't Linux or an error occurred
*
* @param int $reliable
* 3: Only parse /etc/os-release (or /usr/lib/os-release).
* For the paranoid ones.
* 2: If that (3) doesn't help, check /etc/*-release, too.
* If something is unclear, return 'linux'.
* 1: Almost equal to mode 2. The possible return values also include:
* 'redhat' -- unclear whether RHEL/Fedora/...
* 'suse' -- unclear whether SLES/openSUSE/...
* 0: If even that (1) doesn't help, check /proc/version, too.
* This may not work (as expected) on LXC containers!
* (No reliability at all!)
*
* @return string|bool
*/
public static function getLinuxDistro($reliable = 2)
{
if (! self::isLinux()) {
return false;
}
foreach (array('/etc/os-release', '/usr/lib/os-release') as $osReleaseFile) {
if (false === ($osRelease = @file(
$osReleaseFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES
))) {
continue;
}
foreach ($osRelease as $osInfo) {
if (false === ($res = @preg_match('/(?<!.)[ \t]*#/ms', $osInfo))) {
return false;
}
if ($res === 1) {
continue;
}
$matches = array();
if (false === ($res = @preg_match(
'/(?<!.)[ \t]*ID[ \t]*=[ \t]*(\'|"|)(.*?)(?:\1)[ \t]*(?!.)/msi',
$osInfo,
$matches
))) {
return false;
}
if (! ($res === 0 || $matches[2] === '' || $matches[2] === 'linux')) {
return $matches[2];
}
}
}
if ($reliable > 2) {
return 'linux';
}
foreach (array(
'fedora' => '/etc/fedora-release',
'centos' => '/etc/centos-release'
) as $distro => $releaseFile) {
if (! (false === (
$release = @file_get_contents($releaseFile)
) || false === strpos(strtolower($release), $distro))) {
return $distro;
}
}
if (false !== ($release = @file_get_contents('/etc/redhat-release'))) {
$release = strtolower($release);
if (false !== strpos($release, 'red hat enterprise linux')) {
return 'rhel';
}
foreach (array('fedora', 'centos') as $distro) {
if (false !== strpos($release, $distro)) {
return $distro;
}
}
return $reliable < 2 ? 'redhat' : 'linux';
}
if (false !== ($release = @file_get_contents('/etc/SuSE-release'))) {
$release = strtolower($release);
foreach (array(
'opensuse' => 'opensuse',
'sles' => 'suse linux enterprise server',
'sled' => 'suse linux enterprise desktop'
) as $distro => $name) {
if (false !== strpos($release, $name)) {
return $distro;
}
}
return $reliable < 2 ? 'suse' : 'linux';
}
if ($reliable < 1) {
if (false === ($procVersion = @file_get_contents('/proc/version'))) {
return false;
}
$procVersion = strtolower($procVersion);
foreach (array(
'redhat' => 'red hat',
'suse' => 'suse linux',
'ubuntu' => 'ubuntu',
'debian' => 'debian'
) as $distro => $name) {
if (false !== strpos($procVersion, $name)) {
return $distro;
}
}
}
return 'linux';
}
/**
* Test of CLI environment
*

View File

@ -0,0 +1,37 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Application;
class Version
{
/**
* Get the version of this instance of Icinga Web 2
*
* @return array|bool array on success, false otherwise
*/
public static function get()
{
if (false === ($appVersion = @file_get_contents(
Icinga::app()->getApplicationDir() . DIRECTORY_SEPARATOR . 'VERSION'
))) {
return false;
}
$matches = array();
if (false === ($res = preg_match(
'/(?<!.)\s*(?P<gitCommitID>\w+)(?:\s*\(.*?(?:(?<=[\(,])\s*tag\s*:\s*v(?P<appVersion>.+?)\s*(?=[\),]).*?)?\))?\s*(?P<gitCommitDate>\S+)/ms',
$appVersion,
$matches
)) || $res === 0) {
return false;
}
foreach ($matches as $key => $value) {
if (is_int($key) || $value === '') {
unset($matches[$key]);
}
}
return $matches;
}
}

View File

@ -4,17 +4,16 @@
namespace Icinga\Authentication\User;
use DateTime;
use Icinga\Application\Logger;
use Icinga\Data\ConfigObject;
use Icinga\Exception\AuthenticationException;
use Icinga\Exception\ProgrammingError;
use Icinga\Repository\Repository;
use Icinga\Repository\LdapRepository;
use Icinga\Repository\RepositoryQuery;
use Icinga\Protocol\Ldap\Exception as LdapException;
use Icinga\Protocol\Ldap\Expression;
use Icinga\User;
class LdapUserBackend extends Repository implements UserBackendInterface
class LdapUserBackend extends LdapRepository implements UserBackendInterface
{
/**
* The base DN to use for a query
@ -65,20 +64,6 @@ class LdapUserBackend extends Repository implements UserBackendInterface
)
);
protected $groupOptions;
/**
* Normed attribute names based on known LDAP environments
*
* @var array
*/
protected $normedAttributes = array(
'uid' => 'uid',
'user' => 'user',
'inetorgperson' => 'inetOrgPerson',
'samaccountname' => 'sAMAccountName'
);
/**
* Set the base DN to use for a query
*
@ -179,34 +164,6 @@ class LdapUserBackend extends Repository implements UserBackendInterface
return $this->filter;
}
public function setGroupOptions(array $options)
{
$this->groupOptions = $options;
return $this;
}
public function getGroupOptions()
{
return $this->groupOptions;
}
/**
* Return the given attribute name normed to known LDAP enviroments, if possible
*
* @param string $name
*
* @return string
*/
protected function getNormedAttribute($name)
{
$loweredName = strtolower($name);
if (array_key_exists($loweredName, $this->normedAttributes)) {
return $this->normedAttributes[$loweredName];
}
return $name;
}
/**
* Apply the given configuration to this backend
*
@ -325,37 +282,6 @@ class LdapUserBackend extends Repository implements UserBackendInterface
return ((int) $value & $ADS_UF_ACCOUNTDISABLE) === 0;
}
/**
* Parse the given value based on the ASN.1 standard (GeneralizedTime) and return its timestamp representation
*
* @param string|null $value
*
* @return int
*/
protected function retrieveGeneralizedTime($value)
{
if ($value === null) {
return $value;
}
if (
($dateTime = DateTime::createFromFormat('YmdHis.uO', $value)) !== false
|| ($dateTime = DateTime::createFromFormat('YmdHis.uZ', $value)) !== false
|| ($dateTime = DateTime::createFromFormat('YmdHis.u', $value)) !== false
|| ($dateTime = DateTime::createFromFormat('YmdHis', $value)) !== false
|| ($dateTime = DateTime::createFromFormat('YmdHi', $value)) !== false
|| ($dateTime = DateTime::createFromFormat('YmdH', $value)) !== false
) {
return $dateTime->getTimeStamp();
} else {
Logger::debug(sprintf(
'Failed to parse "%s" based on the ASN.1 standard (GeneralizedTime) for user backend "%s".',
$value,
$this->getName()
));
}
}
/**
* Return whether the given shadowExpire value defines that a user is permitted to login
*
@ -413,41 +339,6 @@ class LdapUserBackend extends Repository implements UserBackendInterface
}
}
/**
* Retrieve the user groups
*
* @TODO: Subject to change, see #7343
*
* @param string $dn
*
* @return array
*/
public function getGroups($dn)
{
if (empty($this->groupOptions) || ! isset($this->groupOptions['group_base_dn'])) {
return array();
}
$result = $this->ds->select()
->setBase($this->groupOptions['group_base_dn'])
->from(
$this->groupOptions['group_class'],
array($this->groupOptions['group_attribute'])
)
->where(
$this->groupOptions['group_member_attribute'],
$dn
)
->fetchAll();
$groups = array();
foreach ($result as $group) {
$groups[] = $group->{$this->groupOptions['group_attribute']};
}
return $groups;
}
/**
* Authenticate the given user
*
@ -472,15 +363,7 @@ class LdapUserBackend extends Repository implements UserBackendInterface
return false;
}
$authenticated = $this->ds->testCredentials($userDn, $password);
if ($authenticated) {
$groups = $this->getGroups($userDn);
if ($groups !== null) {
$user->setGroups($groups);
}
}
return $authenticated;
return $this->ds->testCredentials($userDn, $password);
} catch (LdapException $e) {
throw new AuthenticationException(
'Failed to authenticate user "%s" against backend "%s". An exception was thrown:',

View File

@ -3,6 +3,7 @@
namespace Icinga\Authentication\User;
use Icinga\Application\Config;
use Icinga\Application\Logger;
use Icinga\Application\Icinga;
use Icinga\Data\ConfigObject;
@ -106,8 +107,17 @@ class UserBackend
*
* @throws ConfigurationError
*/
public static function create($name, ConfigObject $backendConfig)
public static function create($name, ConfigObject $backendConfig = null)
{
if ($backendConfig === null) {
$authConfig = Config::app('authentication');
if ($authConfig->hasSection($name)) {
$backendConfig = $authConfig->getSection($name);
} else {
throw new ConfigurationError('User backend "%s" does not exist', $name);
}
}
if ($backendConfig->name !== null) {
$name = $backendConfig->name;
}
@ -165,12 +175,6 @@ class UserBackend
$backend->setUserClass($backendConfig->get('user_class', 'user'));
$backend->setUserNameAttribute($backendConfig->get('user_name_attribute', 'sAMAccountName'));
$backend->setFilter($backendConfig->filter);
$backend->setGroupOptions(array(
'group_base_dn' => $backendConfig->get('group_base_dn', $resource->getDN()),
'group_attribute' => $backendConfig->get('group_attribute', 'sAMAccountName'),
'group_member_attribute' => $backendConfig->get('group_member_attribute', 'member'),
'group_class' => $backendConfig->get('group_class', 'group')
));
break;
case 'ldap':
$backend = new LdapUserBackend($resource);
@ -178,12 +182,6 @@ class UserBackend
$backend->setUserClass($backendConfig->get('user_class', 'inetOrgPerson'));
$backend->setUserNameAttribute($backendConfig->get('user_name_attribute', 'uid'));
$backend->setFilter($backendConfig->filter);
$backend->setGroupOptions(array(
'group_base_dn' => $backendConfig->group_base_dn,
'group_attribute' => $backendConfig->group_attribute,
'group_member_attribute' => $backendConfig->group_member_attribute,
'group_class' => $backendConfig->group_class
));
break;
}

View File

@ -0,0 +1,627 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Authentication\UserGroup;
use Icinga\Authentication\User\UserBackend;
use Icinga\Authentication\User\LdapUserBackend;
use Icinga\Data\ConfigObject;
use Icinga\Exception\ConfigurationError;
use Icinga\Exception\ProgrammingError;
use Icinga\Protocol\Ldap\Expression;
use Icinga\Repository\LdapRepository;
use Icinga\Repository\RepositoryQuery;
use Icinga\User;
class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBackendInterface
{
/**
* The base DN to use for a user query
*
* @var string
*/
protected $userBaseDn;
/**
* The base DN to use for a group query
*
* @var string
*/
protected $groupBaseDn;
/**
* The objectClass where look for users
*
* @var string
*/
protected $userClass;
/**
* The objectClass where look for groups
*
* @var string
*/
protected $groupClass;
/**
* The attribute name where to find a user's name
*
* @var string
*/
protected $userNameAttribute;
/**
* The attribute name where to find a group's name
*
* @var string
*/
protected $groupNameAttribute;
/**
* The attribute name where to find a group's member
*
* @var string
*/
protected $groupMemberAttribute;
/**
* The custom LDAP filter to apply on a user query
*
* @var string
*/
protected $userFilter;
/**
* The custom LDAP filter to apply on a group query
*
* @var string
*/
protected $groupFilter;
/**
* The columns which are not permitted to be queried
*
* @var array
*/
protected $filterColumns = array('group', 'user');
/**
* The default sort rules to be applied on a query
*
* @var array
*/
protected $sortRules = array(
'group_name' => array(
'order' => 'asc'
)
);
/**
* Normed attribute names based on known LDAP environments
*
* @var array
*/
protected $normedAttributes = array(
'uid' => 'uid',
'gid' => 'gid',
'user' => 'user',
'group' => 'group',
'member' => 'member',
'inetorgperson' => 'inetOrgPerson',
'samaccountname' => 'sAMAccountName'
);
/**
* The name of this repository
*
* @var string
*/
protected $name;
/**
* The datasource being used
*
* @var Connection
*/
protected $ds;
/**
* Create a new LDAP repository object
*
* @param Connection $ds The data source to use
*/
public function __construct($ds)
{
$this->ds = $ds;
}
/**
* Return the given attribute name normed to known LDAP enviroments, if possible
*
* @param string $name
*
* @return string
*/
protected function getNormedAttribute($name)
{
$loweredName = strtolower($name);
if (array_key_exists($loweredName, $this->normedAttributes)) {
return $this->normedAttributes[$loweredName];
}
return $name;
}
/**
* Set this repository's name
*
* @param string $name
*
* @return $this
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Return this repository's name
*
* In case no name has been explicitly set yet, the class name is returned.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set the base DN to use for a user query
*
* @param string $baseDn
*
* @return $this
*/
public function setUserBaseDn($baseDn)
{
if (($baseDn = trim($baseDn))) {
$this->userBaseDn = $baseDn;
}
return $this;
}
/**
* Return the base DN to use for a user query
*
* @return string
*/
public function getUserBaseDn()
{
return $this->userBaseDn;
}
/**
* Set the base DN to use for a group query
*
* @param string $baseDn
*
* @return $this
*/
public function setGroupBaseDn($baseDn)
{
if (($baseDn = trim($baseDn))) {
$this->groupBaseDn = $baseDn;
}
return $this;
}
/**
* Return the base DN to use for a group query
*
* @return string
*/
public function getGroupBaseDn()
{
return $this->groupBaseDn;
}
/**
* Set the objectClass where to look for users
*
* @param string $userClass
*
* @return $this
*/
public function setUserClass($userClass)
{
$this->userClass = $this->getNormedAttribute($userClass);
return $this;
}
/**
* Return the objectClass where to look for users
*
* @return string
*/
public function getUserClass()
{
return $this->userClass;
}
/**
* Set the objectClass where to look for groups
*
* Sets also the base table name for the underlying repository.
*
* @param string $groupClass
*
* @return $this
*/
public function setGroupClass($groupClass)
{
$this->baseTable = $this->groupClass = $this->getNormedAttribute($groupClass);
return $this;
}
/**
* Return the objectClass where to look for groups
*
* @return string
*/
public function getGroupClass()
{
return $this->groupClass;
}
/**
* Set the attribute name where to find a user's name
*
* @param string $userNameAttribute
*
* @return $this
*/
public function setUserNameAttribute($userNameAttribute)
{
$this->userNameAttribute = $this->getNormedAttribute($userNameAttribute);
return $this;
}
/**
* Return the attribute name where to find a user's name
*
* @return string
*/
public function getUserNameAttribute()
{
return $this->userNameAttribute;
}
/**
* Set the attribute name where to find a group's name
*
* @param string $groupNameAttribute
*
* @return $this
*/
public function setGroupNameAttribute($groupNameAttribute)
{
$this->groupNameAttribute = $this->getNormedAttribute($groupNameAttribute);
return $this;
}
/**
* Return the attribute name where to find a group's name
*
* @return string
*/
public function getGroupNameAttribute()
{
return $this->groupNameAttribute;
}
/**
* Set the attribute name where to find a group's member
*
* @param string $groupMemberAttribute
*
* @return $this
*/
public function setGroupMemberAttribute($groupMemberAttribute)
{
$this->groupMemberAttribute = $this->getNormedAttribute($groupMemberAttribute);
return $this;
}
/**
* Return the attribute name where to find a group's member
*
* @return string
*/
public function getGroupMemberAttribute()
{
return $this->groupMemberAttribute;
}
/**
* Set the custom LDAP filter to apply on a user query
*
* @param string $filter
*
* @return $this
*/
public function setUserFilter($filter)
{
if (($filter = trim($filter))) {
$this->userFilter = $filter;
}
return $this;
}
/**
* Return the custom LDAP filter to apply on a user query
*
* @return string
*/
public function getUserFilter()
{
return $this->userFilter;
}
/**
* Set the custom LDAP filter to apply on a group query
*
* @param string $filter
*
* @return $this
*/
public function setGroupFilter($filter)
{
if (($filter = trim($filter))) {
$this->groupFilter = $filter;
}
return $this;
}
/**
* Return the custom LDAP filter to apply on a group query
*
* @return string
*/
public function getGroupFilter()
{
return $this->groupFilter;
}
/**
* Return a new query for the given columns
*
* @param array $columns The desired columns, if null all columns will be queried
*
* @return RepositoryQuery
*/
public function select(array $columns = null)
{
$query = parent::select($columns);
$query->getQuery()->setBase($this->groupBaseDn);
if ($this->groupFilter) {
// TODO(jom): This should differentiate between groups and their memberships
$query->getQuery()->where(new Expression($this->groupFilter));
}
return $query;
}
/**
* Initialize this repository's query columns
*
* @return array
*
* @throws ProgrammingError In case either $this->groupNameAttribute or $this->groupClass has not been set yet
*/
protected function initializeQueryColumns()
{
if ($this->groupClass === null) {
throw new ProgrammingError('It is required to set the objectClass where to look for groups first');
}
if ($this->groupNameAttribute === null) {
throw new ProgrammingError('It is required to set a attribute name where to find a group\'s name first');
}
if ($this->ds->getCapabilities()->hasAdOid()) {
$createdAtAttribute = 'whenCreated';
$lastModifiedAttribute = 'whenChanged';
} else {
$createdAtAttribute = 'createTimestamp';
$lastModifiedAttribute = 'modifyTimestamp';
}
// TODO(jom): Fetching memberships does not work currently, we'll need some aggregate functionality!
$columns = array(
'group' => $this->groupNameAttribute,
'group_name' => $this->groupNameAttribute,
'user' => $this->groupMemberAttribute,
'user_name' => $this->groupMemberAttribute,
'created_at' => $createdAtAttribute,
'last_modified' => $lastModifiedAttribute
);
return array('group' => $columns, 'group_membership' => $columns);
}
/**
* Initialize this repository's conversion rules
*
* @return array
*
* @throws ProgrammingError In case $this->groupClass has not been set yet
*/
protected function initializeConversionRules()
{
if ($this->groupClass === null) {
throw new ProgrammingError('It is required to set the objectClass where to look for groups first');
}
return array(
$this->groupClass => array(
'created_at' => 'generalized_time',
'last_modified' => 'generalized_time'
)
);
}
/**
* Validate that the requested table exists
*
* This will return $this->groupClass in case $table equals "group" or "group_membership".
*
* @param string $table The table to validate
* @param RepositoryQuery $query An optional query to pass as context
* (unused by the base implementation)
*
* @return string
*
* @throws ProgrammingError In case the given table does not exist
*/
public function requireTable($table, RepositoryQuery $query = null)
{
$table = parent::requireTable($table, $query);
if ($table === 'group' || $table === 'group_membership') {
$table = $this->groupClass;
}
return $table;
}
/**
* Return the groups the given user is a member of
*
* @param User $user
*
* @return array
*/
public function getMemberships(User $user)
{
$userQuery = $this->ds
->select()
->from($this->userClass)
->where($this->userNameAttribute, $user->getUsername())
->setBase($this->userBaseDn)
->setUsePagedResults(false);
if ($this->userFilter) {
$userQuery->where(new Expression($this->userFilter));
}
if (($userDn = $userQuery->fetchDn()) === null) {
return array();
}
$groupQuery = $this->ds
->select()
->from($this->groupClass, array($this->groupNameAttribute))
->where($this->groupMemberAttribute, $userDn)
->setBase($this->groupBaseDn);
if ($this->groupFilter) {
$groupQuery->where(new Expression($this->groupFilter));
}
$groups = array();
foreach ($groupQuery as $row) {
$groups[] = $row->{$this->groupNameAttribute};
}
return $groups;
}
/**
* Apply the given configuration on this backend
*
* @param ConfigObject $config
*
* @return $this
*
* @throws ConfigurationError In case a linked user backend does not exist or is invalid
*/
public function setConfig(ConfigObject $config)
{
if ($config->backend === 'ldap') {
$defaults = $this->getOpenLdapDefaults();
} elseif ($config->backend === 'msldap') {
$defaults = $this->getActiveDirectoryDefaults();
} else {
$defaults = new ConfigObject();
}
if ($config->user_backend && $config->user_backend !== 'none') {
$userBackend = UserBackend::create($config->user_backend);
if (! $userBackend instanceof LdapUserBackend) {
throw new ConfigurationError('User backend "%s" is not of type LDAP', $config->user_backend);
}
if (
$this->ds->getHostname() !== $userBackend->getDataSource()->getHostname()
|| $this->ds->getPort() !== $userBackend->getDataSource()->getPort()
) {
// TODO(jom): Elaborate whether it makes sense to link directories on different hosts
throw new ConfigurationError(
'It is required that a linked user backend refers to the '
. 'same directory as it\'s user group backend counterpart'
);
}
$defaults->merge(array(
'user_base_dn' => $userBackend->getBaseDn(),
'user_class' => $userBackend->getUserClass(),
'user_name_attribute' => $userBackend->getUserNameAttribute(),
'user_filter' => $userBackend->getFilter()
));
}
return $this
->setGroupBaseDn($config->base_dn)
->setUserBaseDn($config->get('user_base_dn', $this->getGroupBaseDn()))
->setGroupClass($config->get('group_class', $defaults->group_class))
->setUserClass($config->get('user_class', $defaults->user_class))
->setGroupNameAttribute($config->get('group_name_attribute', $defaults->group_name_attribute))
->setUserNameAttribute($config->get('user_name_attribute', $defaults->user_name_attribute))
->setGroupMemberAttribute($config->get('group_member_attribute', $defaults->group_member_attribute))
->setGroupFilter($config->filter)
->setUserFilter($config->user_filter);
}
/**
* Return the configuration defaults for an OpenLDAP environment
*
* @return ConfigObject
*/
public function getOpenLdapDefaults()
{
return new ConfigObject(array(
'group_class' => 'group',
'user_class' => 'inetOrgPerson',
'group_name_attribute' => 'gid',
'user_name_attribute' => 'uid',
'group_member_attribute' => 'member'
));
}
/**
* Return the configuration defaults for an ActiveDirectory environment
*
* @return ConfigObject
*/
public function getActiveDirectoryDefaults()
{
return new ConfigObject(array(
'group_class' => 'group',
'user_class' => 'user',
'group_name_attribute' => 'sAMAccountName',
'user_name_attribute' => 'sAMAccountName',
'group_member_attribute' => 'member'
));
}
}

View File

@ -21,6 +21,8 @@ class UserGroupBackend
*/
protected static $defaultBackends = array(
'db',
'ldap',
'msldap',
//'ini'
);
@ -156,6 +158,11 @@ class UserGroupBackend
case 'ini':
$backend = new IniUserGroupBackend($resource);
break;
case 'ldap':
case 'msldap':
$backend = new LdapUserGroupBackend($resource);
$backend->setConfig($backendConfig);
break;
}
$backend->setName($name);

View File

@ -313,14 +313,19 @@ class IniEditor
*/
public function getText()
{
$this->cleanUpWhitespaces();
return rtrim(implode(PHP_EOL, $this->text)) . PHP_EOL;
$this->normalizeSectionSpacing();
// trim leading and trailing whitespaces from generated file
$txt = trim(implode(PHP_EOL, $this->text)) . PHP_EOL;
// replace linebreaks, unless they preceed a comment or a section
return preg_replace("/\n[\n]*([^;\[])/", "\n$1", $txt);
}
/**
* Remove all unneeded line breaks between sections
* normalize section spacing according to the current settings
*/
private function cleanUpWhitespaces()
private function normalizeSectionSpacing()
{
$i = count($this->text) - 1;
for (; $i > 0; $i--) {
@ -328,24 +333,18 @@ class IniEditor
if ($this->isSectionDeclaration($line) && $i > 0) {
$i--;
$line = $this->text[$i];
/*
* Ignore comments that are glued to the section declaration
*/
// ignore comments that are glued to the section declaration
while ($i > 0 && $this->isComment($line)) {
$i--;
$line = $this->text[$i];
}
/*
* Remove whitespaces between the sections
*/
// remove whitespaces between the sections
while ($i > 0 && preg_match('/^\s*$/', $line) === 1) {
$this->deleteLine($i);
$i--;
$line = $this->text[$i];
}
/*
* Refresh section separators
*/
// refresh section separators
if ($i !== 0 && $this->sectionSeparators > 0) {
$this->insertAtLine($i + 1, str_repeat(PHP_EOL, $this->sectionSeparators - 1));
}
@ -621,6 +620,6 @@ class IniEditor
private function sanitize($value)
{
return str_replace('\n', '', $value);
return str_replace("\n", '', $value);
}
}

View File

@ -60,7 +60,7 @@ class IniWriter extends Zend_Config_Writer_FileAbstract
{
if (file_exists($this->_filename)) {
$oldconfig = new Zend_Config_Ini($this->_filename);
$content = file_get_contents($this->_filename);
$content = trim(file_get_contents($this->_filename));
} else {
$oldconfig = new Zend_Config(array());
$content = '';

View File

@ -0,0 +1,66 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Repository;
use Icinga\Protocol\Ldap\Connection;
/**
* Abstract base class for concrete LDAP repository implementations
*
* Additionally provided features:
* <ul>
* <li>Attribute name normalization</li>
* </ul>
*/
abstract class LdapRepository extends Repository
{
/**
* The datasource being used
*
* @var Connection
*/
protected $ds;
/**
* Normed attribute names based on known LDAP environments
*
* @var array
*/
protected $normedAttributes = array(
'uid' => 'uid',
'gid' => 'gid',
'user' => 'user',
'group' => 'group',
'member' => 'member',
'inetorgperson' => 'inetOrgPerson',
'samaccountname' => 'sAMAccountName'
);
/**
* Create a new LDAP repository object
*
* @param Connection $ds The data source to use
*/
public function __construct(Connection $ds)
{
parent::__construct($ds);
}
/**
* Return the given attribute name normed to known LDAP enviroments, if possible
*
* @param string $name
*
* @return string
*/
protected function getNormedAttribute($name)
{
$loweredName = strtolower($name);
if (array_key_exists($loweredName, $this->normedAttributes)) {
return $this->normedAttributes[$loweredName];
}
return $name;
}
}

View File

@ -599,6 +599,37 @@ abstract class Repository implements Selectable
return $value;
}
/**
* Parse the given value based on the ASN.1 standard (GeneralizedTime) and return its timestamp representation
*
* @param string|null $value
*
* @return int
*/
protected function retrieveGeneralizedTime($value)
{
if ($value === null) {
return $value;
}
if (
($dateTime = DateTime::createFromFormat('YmdHis.uO', $value)) !== false
|| ($dateTime = DateTime::createFromFormat('YmdHis.uZ', $value)) !== false
|| ($dateTime = DateTime::createFromFormat('YmdHis.u', $value)) !== false
|| ($dateTime = DateTime::createFromFormat('YmdHis', $value)) !== false
|| ($dateTime = DateTime::createFromFormat('YmdHi', $value)) !== false
|| ($dateTime = DateTime::createFromFormat('YmdH', $value)) !== false
) {
return $dateTime->getTimeStamp();
} else {
Logger::debug(sprintf(
'Failed to parse "%s" based on the ASN.1 standard (GeneralizedTime) in repository "%s".',
$value,
$this->getName()
));
}
}
/**
* Validate that the requested table exists
*

View File

@ -855,8 +855,19 @@ class Form extends Zend_Form
public function populate(array $defaults)
{
$this->create($defaults);
$this->preserveDefaults($this, $defaults);
return parent::populate($defaults);
}
foreach ($this->getElements() as $name => $_) {
/**
* Recurse the given form and unset all unchanged default values
*
* @param Zend_Form $form
* @param array $defaults
*/
protected function preserveDefaults(Zend_Form $form, array & $defaults)
{
foreach ($form->getElements() as $name => $_) {
if (
array_key_exists($name, $defaults)
&& array_key_exists($name . static::DEFAULT_SUFFIX, $defaults)
@ -866,7 +877,9 @@ class Form extends Zend_Form
}
}
return parent::populate($defaults);
foreach ($form->getSubForms() as $_ => $subForm) {
$this->preserveDefaults($subForm, $defaults);
}
}
/**

View File

@ -279,6 +279,11 @@ class Menu implements RecursiveIterator
'priority' => 990,
'renderer' => 'ForeignMenuItemRenderer'
));
$this->add(t('About'), array(
'url' => 'about',
'priority' => 1000
));
}
}

View File

@ -20,7 +20,12 @@ $this->addHelperFunction('url', function ($path = null, $params = null) {
} else {
$url = Url::fromPath($path);
}
if ($params !== null) {
if ($url === $path) {
$url = clone $url;
}
$url->overwriteParams($params);
}

View File

@ -1,8 +1,8 @@
<div class="content">
<?php foreach (/** @var \Icinga\Module\Doc\Renderer\DocSearchRenderer[] $searches */ $searches as $title => $search): ?>
<?php if (! $search->isEmpty()): ?>
<h1><?= $this->escape($title) ?></h1>
<?= $search ?>
<?php endif ?>
<h2><?= $this->escape($title) ?></h2>
<?= $search->isEmpty()
? $this->translate('No documentation found matching the filter')
: $search ?>
<?php endforeach ?>
</div>

View File

@ -132,8 +132,8 @@ $extrapolatedCircleWidth = $timeline->getExtrapolatedCircleWidth($timeInfo[1][$g
<?php endforeach ?>
<a aria-hidden="true" id="end" href="<?= Url::fromRequest()->remove(
array(
'raw_timestamp<',
'raw_timestamp>'
'timestamp<',
'timestamp>'
)
)->overwriteParams(
array(

View File

@ -10,6 +10,39 @@ $configDir = Icinga::app()->getConfigDir();
$setupTokenPath = rtrim($configDir, '/') . '/setup.token';
$cliPath = realpath(Icinga::app()->getApplicationDir() . '/../bin/icingacli');
$groupadd = null;
$usermod = null;
if (! (false === ($distro = Platform::getLinuxDistro(1)) || $distro === 'linux')) {
foreach (array(
'groupadd -r icingaweb2' => array(
'redhat', 'rhel', 'centos', 'fedora',
'suse', 'sles', 'sled', 'opensuse'
),
'addgroup --system icingaweb2' => array('debian', 'ubuntu')
) as $groupadd_ => $distros) {
if (in_array($distro, $distros)) {
$groupadd = $groupadd_;
break;
}
}
foreach (array(
'usermod -a -G icingaweb2 apache' => array(
'redhat', 'rhel', 'centos', 'fedora'
),
'usermod -A icingaweb2 wwwrun' => array(
'suse', 'sles', 'sled', 'opensuse'
),
'usermod -a -G icingaweb2 www-data' => array(
'debian', 'ubuntu'
)
) as $usermod_ => $distros) {
if (in_array($distro, $distros)) {
$usermod = $usermod_;
break;
}
}
}
?>
<div class="welcome-page">
<h2><?= $this->translate('Welcome to the configuration of Icinga Web 2!') ?></h2>
@ -51,6 +84,12 @@ $cliPath = realpath(Icinga::app()->getApplicationDir() . '/../bin/icingacli');
<li><?= $this->translate('Your webserver\'s user is a member of the system group "icingaweb2"'); ?></li>
<?php endif ?>
</ul>
<?php if (! ($groupadd === null || $usermod === null)) { ?>
<div class="code">
<span><?= $this->escape($groupadd . ';') ?></span>
<span><?= $this->escape($usermod . ';') ?></span>
</div>
<?php } ?>
<p><?= $this->translate('If you\'ve got the IcingaCLI installed you can do the following:'); ?></p>
<div class="code">
<span><?= $cliPath ? $cliPath : 'icingacli'; ?> setup config directory --group icingaweb2<?= $configDir !== '/etc/icingaweb2' ? ' --config ' . $configDir : ''; ?>;</span>

View File

@ -331,8 +331,7 @@ html {
position: absolute;
}
/* TODO: replace this with .error */
.fileNotReadable {
.message-error {
padding: 0.5em;
background-color: @colorCritical;
font-weight: bold;

View File

@ -27,10 +27,9 @@ class LdapBackendFormTest extends BaseTestCase
*/
public function testValidBackendIsValid()
{
$this->setUpResourceFactoryMock();
Mockery::mock('overload:Icinga\Authentication\User\LdapUserBackend')
->shouldReceive('assertAuthenticationPossible')->andReturnNull()
->shouldReceive('setConfig')->andReturnNull();
$ldapUserBackendMock = Mockery::mock('overload:Icinga\Authentication\User\LdapUserBackend');
$ldapUserBackendMock->shouldReceive('assertAuthenticationPossible')->andReturnNull();
$this->setUpUserBackendMock($ldapUserBackendMock);
// Passing array(null) is required to make Mockery call the constructor...
$form = Mockery::mock('Icinga\Forms\Config\UserBackend\LdapBackendForm[getView]', array(null));
@ -53,9 +52,9 @@ class LdapBackendFormTest extends BaseTestCase
*/
public function testInvalidBackendIsNotValid()
{
$this->setUpResourceFactoryMock();
Mockery::mock('overload:Icinga\Authentication\User\LdapUserBackend')
->shouldReceive('assertAuthenticationPossible')->andThrow(new AuthenticationException);
$ldapUserBackendMock = Mockery::mock('overload:Icinga\Authentication\User\LdapUserBackend');
$ldapUserBackendMock->shouldReceive('assertAuthenticationPossible')->andThrow(new AuthenticationException);
$this->setUpUserBackendMock($ldapUserBackendMock);
// Passing array(null) is required to make Mockery call the constructor...
$form = Mockery::mock('Icinga\Forms\Config\UserBackend\LdapBackendForm[getView]', array(null));
@ -72,12 +71,10 @@ class LdapBackendFormTest extends BaseTestCase
);
}
protected function setUpResourceFactoryMock()
protected function setUpUserBackendMock($ldapUserBackendMock)
{
Mockery::mock('alias:Icinga\Data\ResourceFactory')
->shouldReceive('createResource')
->andReturn(Mockery::mock('Icinga\Protocol\Ldap\Connection'))
->shouldReceive('getResourceConfig')
->andReturn(new ConfigObject());
Mockery::mock('alias:Icinga\Authentication\User\UserBackend')
->shouldReceive('create')
->andReturn($ldapUserBackendMock);
}
}

View File

@ -719,6 +719,34 @@ EOD;
);
}
public function testWhetherLinebreaksAreRemoved()
{
$target = $this->writeConfigToTemporaryFile('');
$writer = new IniWriter(
array(
'config' => Config::fromArray(
array(
'section' => array(
'foo' => 'linebreak
in line',
'linebreak
inkey' => 'blarg'
)
)
),
'filename' => $target
)
);
$rendered = $writer->render();
var_dump($rendered);
$this->assertEquals(
count(explode("\n", $rendered)),
4,
'generated config should not contain more than three line breaks'
);
}
/**
* Write a INI-configuration string to a temporary file and return its path
*