Merge branch 'master' into bugfix/hot-all-hostgroups-are-shown-10316
This commit is contained in:
commit
689cae8701
|
@ -37,7 +37,16 @@ userpassword: password
|
|||
dn: cn=Users,ou=groups,dc=icinga,dc=org
|
||||
objectClass: groupOfUniqueNames
|
||||
cn: Users
|
||||
uniqueMember: cn=Jon Doe,ou=people,dc=icinga,dc=org
|
||||
uniqueMember: cn=John Doe,ou=people,dc=icinga,dc=org
|
||||
uniqueMember: cn=Jane Smith,ou=people,dc=icinga,dc=org
|
||||
uniqueMember: cn=John Q. Public,ou=people,dc=icinga,dc=org
|
||||
uniqueMember: cn=Richard Roe,ou=people,dc=icinga,dc=org
|
||||
|
||||
dn: cn=PosixUsers,ou=groups,dc=icinga,dc=org
|
||||
objectClass: posixGroup
|
||||
cn: PosixUsers
|
||||
gidNumber: 2001
|
||||
memberUid: jdoe
|
||||
memberUid: jsmith
|
||||
memberUid: jqpublic
|
||||
memberUid: rroe
|
||||
|
|
|
@ -88,6 +88,7 @@ class icingaweb2_dev (
|
|||
|
||||
icingaweb2::config::general { 'authentication':
|
||||
source => $name,
|
||||
replace => false,
|
||||
}
|
||||
|
||||
icingaweb2::config::general { [ 'config', 'resources', 'roles' ]:
|
||||
|
|
|
@ -44,7 +44,7 @@ class GroupController extends AuthBackendController
|
|||
'backend',
|
||||
array(
|
||||
'autosubmit' => true,
|
||||
'label' => $this->translate('Usergroup Backend'),
|
||||
'label' => $this->translate('User Group Backend'),
|
||||
'multiOptions' => array_combine($backendNames, $backendNames),
|
||||
'value' => $this->params->get('backend')
|
||||
)
|
||||
|
@ -66,7 +66,7 @@ class GroupController extends AuthBackendController
|
|||
$this->setupLimitControl();
|
||||
$this->setupSortControl(
|
||||
array(
|
||||
'group_name' => $this->translate('Group'),
|
||||
'group_name' => $this->translate('User Group'),
|
||||
'created_at' => $this->translate('Created at'),
|
||||
'last_modified' => $this->translate('Last modified')
|
||||
),
|
||||
|
@ -97,7 +97,7 @@ class GroupController extends AuthBackendController
|
|||
->from('group_membership', array('user_name'))
|
||||
->where('group_name', $groupName);
|
||||
|
||||
$this->setupFilterControl($members, null, array('user'));
|
||||
$this->setupFilterControl($members, null, array('user'), array('group'));
|
||||
$this->setupPaginationControl($members);
|
||||
$this->setupLimitControl();
|
||||
$this->setupSortControl(
|
||||
|
@ -134,7 +134,7 @@ class GroupController extends AuthBackendController
|
|||
$removeForm->addElement('button', 'btn_submit', array(
|
||||
'escape' => false,
|
||||
'type' => 'submit',
|
||||
'class' => 'link-like spinner',
|
||||
'class' => 'link-button spinner',
|
||||
'value' => 'btn_submit',
|
||||
'decorators' => array('ViewHelper'),
|
||||
'label' => $this->view->icon('trash'),
|
||||
|
|
|
@ -44,7 +44,7 @@ class UserController extends AuthBackendController
|
|||
'backend',
|
||||
array(
|
||||
'autosubmit' => true,
|
||||
'label' => $this->translate('Authentication Backend'),
|
||||
'label' => $this->translate('User Backend'),
|
||||
'multiOptions' => array_combine($backendNames, $backendNames),
|
||||
'value' => $this->params->get('backend')
|
||||
)
|
||||
|
@ -99,7 +99,8 @@ class UserController extends AuthBackendController
|
|||
$this->setupFilterControl(
|
||||
$memberships,
|
||||
array('group_name' => t('User Group')),
|
||||
array('group_name')
|
||||
array('group'),
|
||||
array('user')
|
||||
);
|
||||
$this->setupPaginationControl($memberships);
|
||||
$this->setupLimitControl();
|
||||
|
@ -140,7 +141,7 @@ class UserController extends AuthBackendController
|
|||
$removeForm->addElement('button', 'btn_submit', array(
|
||||
'escape' => false,
|
||||
'type' => 'submit',
|
||||
'class' => 'link-like spinner',
|
||||
'class' => 'link-button spinner',
|
||||
'value' => 'btn_submit',
|
||||
'decorators' => array('ViewHelper'),
|
||||
'label' => $this->view->icon('trash'),
|
||||
|
@ -259,6 +260,7 @@ class UserController extends AuthBackendController
|
|||
$alreadySeen[$groupName] = null;
|
||||
$groups[] = (object) array(
|
||||
'group_name' => $groupName,
|
||||
'group' => $groupName,
|
||||
'backend' => $backend
|
||||
);
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ class LoginForm extends Form
|
|||
if ($this->created) {
|
||||
$redirect = $this->getElement('redirect')->getValue();
|
||||
}
|
||||
if (empty($redirect) || strpos($redirect, 'authentication/logout') !== 0) {
|
||||
if (empty($redirect) || strpos($redirect, 'authentication/logout') !== false) {
|
||||
$redirect = static::REDIRECT_URL;
|
||||
}
|
||||
return Url::fromPath($redirect);
|
||||
|
|
|
@ -4,30 +4,24 @@ use Icinga\Data\Extensible;
|
|||
use Icinga\Data\Reducible;
|
||||
|
||||
if (! $this->compact): ?>
|
||||
<div class="controls">
|
||||
<?= $this->tabs; ?>
|
||||
<div class="grid dont-print">
|
||||
<div class="col-1-3 text-left">
|
||||
<?= $this->limiter ?>
|
||||
<div class="controls separated dont-print">
|
||||
<?= $tabs; ?>
|
||||
<div class="grid">
|
||||
<?= $this->limiter ?>
|
||||
<?= $this->paginator ?>
|
||||
<?= $this->sortBox ?>
|
||||
</div>
|
||||
<div class="col-1-3">
|
||||
<?= $this->paginator ?>
|
||||
<div>
|
||||
<?= $this->backendSelection; ?>
|
||||
<?= $this->filterEditor; ?>
|
||||
</div>
|
||||
<div class="col-1-3 text-right">
|
||||
<?= $this->sortBox ?>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<?= $this->backendSelection; ?>
|
||||
<?= $this->filterEditor; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<div class="content groups">
|
||||
<div class="content">
|
||||
<?php
|
||||
|
||||
if (! isset($backend)) {
|
||||
echo $this->translate('No backend found which is able to list groups') . '</div>';
|
||||
echo $this->translate('No backend found which is able to list user groups') . '</div>';
|
||||
return;
|
||||
} else {
|
||||
$extensible = $this->hasPermission('config/authentication/groups/add') && $backend instanceof Extensible;
|
||||
|
@ -37,64 +31,68 @@ if (! isset($backend)) {
|
|||
|
||||
<?php if ($extensible): ?>
|
||||
<?= $this->qlink(
|
||||
$this->translate('Add a New User Group') ,
|
||||
$this->translate('Add a New User Group'),
|
||||
'group/add',
|
||||
array('backend' => $backend->getName()),
|
||||
array(
|
||||
'class' => 'button-link',
|
||||
'data-base-target' => '_next',
|
||||
'icon' => 'plus',
|
||||
'title' => $this->translate('Create a new user group')
|
||||
'icon' => 'plus'
|
||||
)
|
||||
) ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php
|
||||
// @TODO(el): Remove $firstRow stuff
|
||||
$firstRow = true;
|
||||
foreach ($groups as $group): ?>
|
||||
<?php if ($firstRow): ?>
|
||||
<?php $firstRow = false; ?>
|
||||
<table data-base-target="_next" class="action-table listing-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="group-name"><?= $this->translate('Group'); ?></th>
|
||||
<?php if ($reducible): ?>
|
||||
<th class="group-remove"><?= $this->translate('Remove'); ?></th>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php endif ?>
|
||||
<tr>
|
||||
<td class="group-name"><?= $this->qlink($group->group_name, 'group/show', array(
|
||||
'backend' => $backend->getName(),
|
||||
'group' => $group->group_name
|
||||
), array(
|
||||
'title' => sprintf($this->translate('Show detailed information for group %s'), $group->group_name)
|
||||
)); ?></td>
|
||||
<?php if ($reducible): ?>
|
||||
<td class="group-remove">
|
||||
<?= $this->qlink(
|
||||
null,
|
||||
'group/remove',
|
||||
array(
|
||||
'backend' => $backend->getName(),
|
||||
'group' => $group->group_name
|
||||
),
|
||||
array(
|
||||
'title' => sprintf($this->translate('Remove group %s'), $group->group_name),
|
||||
'icon' => 'trash'
|
||||
)
|
||||
); ?>
|
||||
</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
<?php if ($groups->hasResult()): ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<p><?= $this->translate('No groups found matching the filter'); ?></p>
|
||||
<?php endif ?>
|
||||
<?php if (! $groups->hasResult()): ?>
|
||||
<p><?= $this->translate('No user groups found matching the filter'); ?></p>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<table data-base-target="_next" class="action-table listing-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $this->translate('User Group'); ?></th>
|
||||
<?php if ($reducible): ?>
|
||||
<th><?= $this->translate('Remove'); ?></th>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($groups as $group): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<?= $this->qlink(
|
||||
$group->group_name,
|
||||
'group/show',
|
||||
array(
|
||||
'backend' => $backend->getName(),
|
||||
'group' => $group->group_name
|
||||
),
|
||||
array(
|
||||
'title' => sprintf(
|
||||
$this->translate('Show detailed information for user group %s'),
|
||||
$group->group_name
|
||||
)
|
||||
)
|
||||
); ?>
|
||||
</td>
|
||||
<?php if ($reducible): ?>
|
||||
<td class="icon-col">
|
||||
<?= $this->qlink(
|
||||
null,
|
||||
'group/remove',
|
||||
array(
|
||||
'backend' => $backend->getName(),
|
||||
'group' => $group->group_name
|
||||
),
|
||||
array(
|
||||
'title' => sprintf($this->translate('Remove user group %s'), $group->group_name),
|
||||
'icon' => 'trash'
|
||||
)
|
||||
); ?>
|
||||
</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
use Icinga\Data\Extensible;
|
||||
use Icinga\Data\Updatable;
|
||||
use Icinga\Data\Selectable;
|
||||
|
||||
$extensible = $this->hasPermission('config/authentication/groups/add') && $backend instanceof Extensible;
|
||||
|
||||
|
@ -24,92 +23,86 @@ if ($this->hasPermission('config/authentication/groups/edit') && $backend instan
|
|||
}
|
||||
|
||||
?>
|
||||
<div class="controls">
|
||||
<?php if (! $this->compact): ?>
|
||||
<?= $tabs; ?>
|
||||
<?php endif ?>
|
||||
<h2 class="clearfix"><?= $this->escape($group->group_name) ?><span class="pull-right"><?= $editLink ?></span></h2>
|
||||
<table class="name-value-table">
|
||||
<tr>
|
||||
<th><?= $this->translate('Created at'); ?></th>
|
||||
<td><?= $group->created_at === null ? '-' : $this->formatDateTime($group->created_at); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?= $this->translate('Last modified'); ?></th>
|
||||
<td><?= $group->last_modified === null ? '-' : $this->formatDateTime($group->last_modified); ?></td>
|
||||
</tr>
|
||||
</table>
|
||||
<h2><?= $this->translate('Members'); ?></h2>
|
||||
<div class="controls separated dont-print">
|
||||
<?php if (! $this->compact): ?>
|
||||
<?= $this->sortBox; ?>
|
||||
<?= $tabs; ?>
|
||||
<?php endif ?>
|
||||
<?= $this->limiter; ?>
|
||||
<?= $this->paginator; ?>
|
||||
<h2 class="clearfix"><?= $this->escape($group->group_name) ?><span class="pull-right"><?= $editLink ?></span></h2>
|
||||
<table class="name-value-table">
|
||||
<tr>
|
||||
<th><?= $this->translate('Created at'); ?></th>
|
||||
<td><?= $group->created_at === null ? '-' : $this->formatDateTime($group->created_at); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?= $this->translate('Last modified'); ?></th>
|
||||
<td><?= $group->last_modified === null ? '-' : $this->formatDateTime($group->last_modified); ?></td>
|
||||
</tr>
|
||||
</table>
|
||||
<?php if (! $this->compact): ?>
|
||||
<?= $this->filterEditor; ?>
|
||||
<h2><?= $this->translate('Members'); ?></h2>
|
||||
<div class="grid">
|
||||
<?= $this->limiter; ?>
|
||||
<?= $this->paginator; ?>
|
||||
<?= $this->sortBox; ?>
|
||||
</div>
|
||||
<?= $this->filterEditor; ?>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<div class="content members" data-base-target="_next">
|
||||
<div class="content">
|
||||
<?php if ($extensible): ?>
|
||||
<?= $this->qlink(
|
||||
$this->translate('Add User to Group') ,
|
||||
$this->translate('Add New Member'),
|
||||
'group/addmember',
|
||||
null,
|
||||
array(
|
||||
'class' => 'button-link',
|
||||
'data-base-target' => '_next',
|
||||
'icon' => 'plus',
|
||||
'title' => $this->translate('Add user to group')
|
||||
'backend' => $backend->getName(),
|
||||
'group' => $group->group_name
|
||||
),
|
||||
array(
|
||||
'icon' => 'plus',
|
||||
'class' => 'button-link'
|
||||
)
|
||||
) ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php
|
||||
|
||||
$firstRow = true;
|
||||
foreach ($members as $member): ?>
|
||||
<?php if ($firstRow): ?>
|
||||
<?php $firstRow = false; ?>
|
||||
<table data-base-target="_next" class="action-table listing-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="member-name"><?= $this->translate('Username'); ?></th>
|
||||
<?php if (isset($removeForm)): ?>
|
||||
<th class="member-remove"><?= $this->translate('Remove'); ?></th>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php endif ?>
|
||||
<tr>
|
||||
<td class="member-name">
|
||||
<?php if (
|
||||
$this->hasPermission('config/authentication/users/show')
|
||||
&& method_exists($backend, 'getUserBackend')
|
||||
&& ($userBackend = $backend->getUserBackend()) !== null
|
||||
&& $userBackend instanceof Selectable
|
||||
): ?>
|
||||
<?= $this->qlink($member->user_name, 'user/show', array(
|
||||
'backend' => $userBackend->getName(),
|
||||
'user' => $member->user_name
|
||||
), array(
|
||||
'title' => sprintf($this->translate('Show detailed information about %s'), $member->user_name)
|
||||
)); ?>
|
||||
<?php else: ?>
|
||||
<?= $this->escape($member->user_name); ?>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
<?php if (isset($removeForm)): ?>
|
||||
<td class="icon-col" data-base-target="_self">
|
||||
<?php $removeForm->getElement('user_name')->setValue($member->user_name); echo $removeForm; ?>
|
||||
</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
<?php if ($members->hasResult()): ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<?php if (! $members->hasResult()): ?>
|
||||
<p><?= $this->translate('No group member found matching the filter'); ?></p>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?php return; endif ?>
|
||||
|
||||
<table data-base-target="_next" class="action-table listing-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $this->translate('Username'); ?></th>
|
||||
<?php if (isset($removeForm)): ?>
|
||||
<th><?= $this->translate('Remove'); ?></th>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($members as $member): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<?php if (
|
||||
$this->hasPermission('config/authentication/users/show')
|
||||
&& ($userBackend = $backend->getUserBackendName($member->user_name)) !== null
|
||||
): ?>
|
||||
<?= $this->qlink($member->user_name, 'user/show', array(
|
||||
'backend' => $userBackend,
|
||||
'user' => $member->user_name
|
||||
), array(
|
||||
'title' => sprintf($this->translate('Show detailed information about %s'), $member->user_name)
|
||||
)); ?>
|
||||
<?php else: ?>
|
||||
<?= $this->escape($member->user_name); ?>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
<?php if (isset($removeForm)): ?>
|
||||
<td class="icon-col" data-base-target="_self">
|
||||
<?php $removeForm->getElement('user_name')->setValue($member->user_name); echo $removeForm; ?>
|
||||
</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
|
@ -1,20 +1,15 @@
|
|||
<?php
|
||||
|
||||
use Icinga\Data\Extensible;
|
||||
use Icinga\Data\Reducible;
|
||||
|
||||
if (! $this->compact): ?>
|
||||
<div class="controls">
|
||||
<?= $this->tabs ?>
|
||||
<div class="grid dont-print">
|
||||
<div class="col-1-3 text-left">
|
||||
<?= $this->limiter ?>
|
||||
</div>
|
||||
<div class="col-1-3">
|
||||
<?= $this->paginator ?>
|
||||
</div>
|
||||
<div class="col-1-3 text-right">
|
||||
<?= $this->sortBox ?>
|
||||
</div>
|
||||
<div class="controls separated dont-print">
|
||||
<?= $tabs ?>
|
||||
<div class="grid">
|
||||
<?= $this->limiter ?>
|
||||
<?= $this->paginator ?>
|
||||
<?= $this->sortBox ?>
|
||||
</div>
|
||||
<div>
|
||||
<?= $this->backendSelection ?>
|
||||
|
@ -22,20 +17,18 @@ if (! $this->compact): ?>
|
|||
</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="content">
|
||||
<?php if (! isset($backend)): ?>
|
||||
<p><?= $this->translate('No backend found which is able to list users.') ?></p>
|
||||
</div>
|
||||
<?php return; endif ?>
|
||||
<?php if (! $users->hasResult()): ?>
|
||||
<p><?= $this->translate('No users found matching the filter.') ?></p>
|
||||
</div>
|
||||
<?php return; endif ?>
|
||||
<?php
|
||||
$extensible = $this->hasPermission('config/authentication/users/add') && $backend instanceof Extensible;
|
||||
$reducible = $this->hasPermission('config/authentication/users/remove') && $backend instanceof Reducible;
|
||||
|
||||
if (! isset($backend)) {
|
||||
echo $this->translate('No backend found which is able to list users') . '</div>';
|
||||
return;
|
||||
} else {
|
||||
$extensible = $this->hasPermission('config/authentication/users/add') && $backend instanceof Extensible;
|
||||
$reducible = $this->hasPermission('config/authentication/users/remove') && $backend instanceof Reducible;
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if ($extensible): ?>
|
||||
<?= $this->qlink(
|
||||
$this->translate('Add a New User') ,
|
||||
|
@ -44,12 +37,17 @@ $reducible = $this->hasPermission('config/authentication/users/remove') && $back
|
|||
array(
|
||||
'class' => 'button-link',
|
||||
'data-base-target' => '_next',
|
||||
'icon' => 'plus',
|
||||
'title' => $this->translate('Create a new user')
|
||||
'icon' => 'plus'
|
||||
)
|
||||
) ?>
|
||||
<?php endif ?>
|
||||
<table class="action-table listing-table" data-base-target="_next">
|
||||
|
||||
<?php if (! $users->hasResult()): ?>
|
||||
<p><?= $this->translate('No users found matching the filter') ?></p>
|
||||
</div>
|
||||
<?php return; endif ?>
|
||||
|
||||
<table data-base-target="_next" class="action-table listing-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $this->translate('Username') ?></th>
|
||||
|
|
|
@ -22,93 +22,90 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc
|
|||
}
|
||||
|
||||
?>
|
||||
<div class="controls">
|
||||
<div class="controls separated dont-print">
|
||||
<?php if (! $this->compact): ?>
|
||||
<?= $tabs; ?>
|
||||
<?= $tabs; ?>
|
||||
<?php endif ?>
|
||||
<h2 class="clearfix"><?= $this->escape($user->user_name) ?><span class="pull-right"><?= $editLink ?></span></h2>
|
||||
<table class="name-value-table">
|
||||
<tr>
|
||||
<th><?= $this->translate('State'); ?></th>
|
||||
<td><?= $user->is_active === null ? '-' : ($user->is_active ? $this->translate('Active') : $this->translate('Inactive')); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?= $this->translate('Created at'); ?></th>
|
||||
<td><?= $user->created_at === null ? '-' : $this->formatDateTime($user->created_at); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?= $this->translate('Last modified'); ?></th>
|
||||
<td><?= $user->last_modified === null ? '-' : $this->formatDateTime($user->last_modified); ?></td>
|
||||
</tr>
|
||||
</table>
|
||||
<h2><?= $this->translate('Group Memberships'); ?></h2>
|
||||
<h2 class="clearfix"><?= $this->escape($user->user_name) ?><span class="pull-right"><?= $editLink ?></span></h2>
|
||||
<table class="name-value-table">
|
||||
<tr>
|
||||
<th><?= $this->translate('State'); ?></th>
|
||||
<td><?= $user->is_active === null ? '-' : ($user->is_active ? $this->translate('Active') : $this->translate('Inactive')); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?= $this->translate('Created at'); ?></th>
|
||||
<td><?= $user->created_at === null ? '-' : $this->formatDateTime($user->created_at); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?= $this->translate('Last modified'); ?></th>
|
||||
<td><?= $user->last_modified === null ? '-' : $this->formatDateTime($user->last_modified); ?></td>
|
||||
</tr>
|
||||
</table>
|
||||
<?php if (! $this->compact): ?>
|
||||
<?= $this->sortBox; ?>
|
||||
<?php endif ?>
|
||||
<?= $this->limiter; ?>
|
||||
<?= $this->paginator; ?>
|
||||
<?php if (! $this->compact): ?>
|
||||
<?= $this->filterEditor; ?>
|
||||
<h2><?= $this->translate('Group Memberships'); ?></h2>
|
||||
<div class="grid">
|
||||
<?= $this->limiter; ?>
|
||||
<?= $this->paginator; ?>
|
||||
<?= $this->sortBox; ?>
|
||||
</div>
|
||||
<?= $this->filterEditor; ?>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<div class="content memberships">
|
||||
<?php if ($showCreateMembershipLink): ?>
|
||||
<div class="content">
|
||||
<?php if ($showCreateMembershipLink): ?>
|
||||
<?= $this->qlink(
|
||||
$this->translate('Add User to Group') ,
|
||||
$this->translate('Create New Membership'),
|
||||
'user/createmembership',
|
||||
array('backend' => $backend->getName()),
|
||||
array(
|
||||
'class' => 'button-link',
|
||||
'data-base-target' => '_next',
|
||||
'icon' => 'plus',
|
||||
'title' => $this->translate('Add user to user group')
|
||||
'backend' => $backend->getName(),
|
||||
'user' => $user->user_name
|
||||
),
|
||||
array(
|
||||
'icon' => 'plus',
|
||||
'class' => 'button-link'
|
||||
)
|
||||
) ?>
|
||||
<?php endif ?>
|
||||
<?php
|
||||
// @TODO(el): Remove that $firstRow thingy
|
||||
$firstRow = true;
|
||||
foreach ($memberships as $membership): ?>
|
||||
<?php if ($firstRow): ?>
|
||||
<?php $firstRow = false; ?>
|
||||
<table data-base-target="_next" class="action-table listing-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="membership-group"><?= $this->translate('Group'); ?></th>
|
||||
<th class="membership-cancel"><?= $this->translate('Cancel', 'group.membership'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php endif ?>
|
||||
<tr>
|
||||
<td class="membership-group">
|
||||
<?php if ($this->hasPermission('config/authentication/groups/show') && $membership->backend instanceof Selectable): ?>
|
||||
<?= $this->qlink($membership->group_name, 'group/show', array(
|
||||
'backend' => $membership->backend->getName(),
|
||||
'group' => $membership->group_name
|
||||
), array(
|
||||
'title' => sprintf($this->translate('Show detailed information for group %s'), $membership->group_name)
|
||||
)); ?>
|
||||
<?php else: ?>
|
||||
<?= $this->escape($membership->group_name); ?>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
<td class="membership-cancel" data-base-target="_self">
|
||||
<?php if (isset($removeForm) && $membership->backend instanceof Reducible): ?>
|
||||
<?= $removeForm->setAction($this->url('group/removemember', array(
|
||||
'backend' => $membership->backend->getName(),
|
||||
'group' => $membership->group_name
|
||||
))); ?>
|
||||
<?php else: ?>
|
||||
-
|
||||
<?php endif ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
<?php if ($memberships->hasResult()): ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php else: ?>
|
||||
<p><?= $this->translate('No memberships found matching the filter'); ?></p>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (! $memberships->hasResult()): ?>
|
||||
<p><?= $this->translate('No memberships found matching the filter'); ?></p>
|
||||
</div>
|
||||
<?php return; endif ?>
|
||||
|
||||
<table data-base-target="_next" class="action-table listing-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= $this->translate('Group'); ?></th>
|
||||
<th><?= $this->translate('Cancel', 'group.membership'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($memberships as $membership): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<?php if ($this->hasPermission('config/authentication/groups/show') && $membership->backend instanceof Selectable): ?>
|
||||
<?= $this->qlink($membership->group_name, 'group/show', array(
|
||||
'backend' => $membership->backend->getName(),
|
||||
'group' => $membership->group_name
|
||||
), array(
|
||||
'title' => sprintf($this->translate('Show detailed information for group %s'), $membership->group_name)
|
||||
)); ?>
|
||||
<?php else: ?>
|
||||
<?= $this->escape($membership->group_name); ?>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
<td class="icon-col" data-base-target="_self">
|
||||
<?php if (isset($removeForm) && $membership->backend instanceof Reducible): ?>
|
||||
<?= $removeForm->setAction($this->url('group/removemember', array(
|
||||
'backend' => $membership->backend->getName(),
|
||||
'group' => $membership->group_name
|
||||
))); ?>
|
||||
<?php else: ?>
|
||||
-
|
||||
<?php endif ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
|
@ -495,6 +495,6 @@ The first release candidate of Icinga Web 2 introduces the following non-backwar
|
|||
the file remains unchanged.
|
||||
|
||||
* The location of a user's preferences has been changed from
|
||||
**<config-dir>/preferences/<username>.ini** to
|
||||
**<config-dir>/preferences/<username>/config.ini**.
|
||||
**<config-dir>/preferences/<username>.ini** to
|
||||
**<config-dir>/preferences/<username>/config.ini**.
|
||||
The content of the file remains unchanged.
|
||||
|
|
|
@ -25,7 +25,7 @@ Packager: Icinga Team <info@icinga.org>
|
|||
%if 0%{?suse_version}
|
||||
%define wwwconfigdir %{_sysconfdir}/apache2/conf.d
|
||||
%define wwwuser wwwrun
|
||||
%define zend php5-ZendFramework
|
||||
%define zend %{name}-vendor-Zend
|
||||
%if 0%{?suse_version} == 1110
|
||||
%define php php53
|
||||
Requires: apache2-mod_php53
|
||||
|
@ -83,7 +83,6 @@ Requires: %{php}-gd %{php}-intl
|
|||
%{?rhel:Requires: php-pecl-imagick}
|
||||
%{?suse_version:Requires: %{php}-gettext %{php}-json %{php}-openssl %{php}-posix}
|
||||
Requires: %{zend}
|
||||
Obsoletes: %{name}-vendor-zend
|
||||
Requires: %{zend}-Db-Adapter-Pdo-Mysql
|
||||
Requires: %{zend}-Db-Adapter-Pdo-Pgsql
|
||||
|
||||
|
|
|
@ -136,6 +136,16 @@ class Logger
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the logging level being used
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLevel()
|
||||
{
|
||||
return $this->level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given message as config error
|
||||
*
|
||||
|
|
|
@ -130,6 +130,7 @@ class DbUserBackend extends DbRepository implements UserBackendInterface, Inspec
|
|||
*/
|
||||
public function insert($table, array $bind)
|
||||
{
|
||||
$this->requireTable($table);
|
||||
$bind['created_at'] = date('Y-m-d H:i:s');
|
||||
$this->ds->insert(
|
||||
$this->prependTablePrefix($table),
|
||||
|
@ -150,6 +151,7 @@ class DbUserBackend extends DbRepository implements UserBackendInterface, Inspec
|
|||
*/
|
||||
public function update($table, array $bind, Filter $filter = null)
|
||||
{
|
||||
$this->requireTable($table);
|
||||
$bind['last_modified'] = date('Y-m-d H:i:s');
|
||||
if ($filter) {
|
||||
$filter = $this->requireFilter($table, $filter);
|
||||
|
|
|
@ -12,7 +12,6 @@ use Icinga\Exception\ProgrammingError;
|
|||
use Icinga\Repository\LdapRepository;
|
||||
use Icinga\Repository\RepositoryQuery;
|
||||
use Icinga\Protocol\Ldap\LdapException;
|
||||
use Icinga\Protocol\Ldap\Expression;
|
||||
use Icinga\User;
|
||||
|
||||
class LdapUserBackend extends LdapRepository implements UserBackendInterface, Inspectable
|
||||
|
@ -102,15 +101,13 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In
|
|||
/**
|
||||
* Set the objectClass where to look for users
|
||||
*
|
||||
* Sets also the base table name for the underlying repository.
|
||||
*
|
||||
* @param string $userClass
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setUserClass($userClass)
|
||||
{
|
||||
$this->baseTable = $this->userClass = $this->getNormedAttribute($userClass);
|
||||
$this->userClass = $this->getNormedAttribute($userClass);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -194,21 +191,21 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In
|
|||
}
|
||||
|
||||
/**
|
||||
* Return a new query for the given columns
|
||||
* Initialize this repository's virtual tables
|
||||
*
|
||||
* @param array $columns The desired columns, if null all columns will be queried
|
||||
* @return array
|
||||
*
|
||||
* @return RepositoryQuery
|
||||
* @throws ProgrammingError In case $this->userClass has not been set yet
|
||||
*/
|
||||
public function select(array $columns = null)
|
||||
protected function initializeVirtualTables()
|
||||
{
|
||||
$query = parent::select($columns);
|
||||
$query->getQuery()->setBase($this->baseDn);
|
||||
if ($this->filter) {
|
||||
$query->getQuery()->where(new Expression($this->filter));
|
||||
if ($this->userClass === null) {
|
||||
throw new ProgrammingError('It is required to set the object class where to find users first');
|
||||
}
|
||||
|
||||
return $query;
|
||||
return array(
|
||||
'user' => $this->userClass
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -216,13 +213,10 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In
|
|||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws ProgrammingError In case either $this->userNameAttribute or $this->userClass has not been set yet
|
||||
* @throws ProgrammingError In case $this->userNameAttribute has not been set yet
|
||||
*/
|
||||
protected function initializeQueryColumns()
|
||||
{
|
||||
if ($this->userClass === null) {
|
||||
throw new ProgrammingError('It is required to set the objectClass where to look for users first');
|
||||
}
|
||||
if ($this->userNameAttribute === null) {
|
||||
throw new ProgrammingError('It is required to set a attribute name where to find a user\'s name first');
|
||||
}
|
||||
|
@ -240,7 +234,7 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In
|
|||
}
|
||||
|
||||
return array(
|
||||
$this->userClass => array(
|
||||
'user' => array(
|
||||
'user' => $this->userNameAttribute,
|
||||
'user_name' => $this->userNameAttribute,
|
||||
'is_active' => $isActiveAttribute,
|
||||
|
@ -269,15 +263,9 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In
|
|||
* Initialize this repository's conversion rules
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws ProgrammingError In case $this->userClass has not been set yet
|
||||
*/
|
||||
protected function initializeConversionRules()
|
||||
{
|
||||
if ($this->userClass === null) {
|
||||
throw new ProgrammingError('It is required to set the objectClass where to look for users first');
|
||||
}
|
||||
|
||||
if ($this->ds->getCapabilities()->isActiveDirectory()) {
|
||||
$stateConverter = 'user_account_control';
|
||||
} else {
|
||||
|
@ -285,7 +273,7 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In
|
|||
}
|
||||
|
||||
return array(
|
||||
$this->userClass => array(
|
||||
'user' => array(
|
||||
'is_active' => $stateConverter,
|
||||
'created_at' => 'generalized_time',
|
||||
'last_modified' => 'generalized_time'
|
||||
|
@ -330,14 +318,46 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In
|
|||
}
|
||||
|
||||
/**
|
||||
|
||||
* @param Inspection $info Optional inspection to fill with diagnostic info
|
||||
* Validate that the requested table exists
|
||||
*
|
||||
* @throws AuthenticationException When authentication is not possible
|
||||
* @param string $table The table to validate
|
||||
* @param RepositoryQuery $query An optional query to pass as context
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws ProgrammingError In case the given table does not exist
|
||||
*/
|
||||
public function assertAuthenticationPossible(Inspection $insp = null)
|
||||
public function requireTable($table, RepositoryQuery $query = null)
|
||||
{
|
||||
if ($query !== null) {
|
||||
$query->getQuery()->setBase($this->baseDn);
|
||||
if ($this->filter) {
|
||||
$query->getQuery()->setNativeFilter($this->filter);
|
||||
}
|
||||
}
|
||||
|
||||
return parent::requireTable($table, $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the given column is a valid query target and return it or the actual name if it's an alias
|
||||
*
|
||||
* @param string $table The table where to look for the column or alias
|
||||
* @param string $name The name or alias of the column to validate
|
||||
* @param RepositoryQuery $query An optional query to pass as context
|
||||
*
|
||||
* @return string The given column's name
|
||||
*
|
||||
* @throws QueryException In case the given column is not a valid query column
|
||||
*/
|
||||
public function requireQueryColumn($table, $name, RepositoryQuery $query = null)
|
||||
{
|
||||
$column = parent::requireQueryColumn($table, $name, $query);
|
||||
if ($name === 'user_name' && $query !== null) {
|
||||
$query->getQuery()->setUnfoldAttribute('user_name');
|
||||
}
|
||||
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -359,17 +379,16 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In
|
|||
->getQuery()
|
||||
->setUsePagedResults(false)
|
||||
->fetchDn();
|
||||
|
||||
if ($userDn === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$testCredentialsResult = $this->ds->testCredentials($userDn, $password);
|
||||
if ($testCredentialsResult) {
|
||||
$validCredentials = $this->ds->testCredentials($userDn, $password);
|
||||
if ($validCredentials) {
|
||||
$user->setAdditional('ldap_dn', $userDn);
|
||||
}
|
||||
|
||||
return $testCredentialsResult;
|
||||
return $validCredentials;
|
||||
} catch (LdapException $e) {
|
||||
throw new AuthenticationException(
|
||||
'Failed to authenticate user "%s" against backend "%s". An exception was thrown:',
|
||||
|
|
|
@ -217,6 +217,18 @@ class DbUserGroupBackend extends DbRepository implements UserGroupBackendInterfa
|
|||
return $memberships;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the backend that is providing the given user
|
||||
*
|
||||
* @param string $username Currently unused
|
||||
*
|
||||
* @return null|string The name of the backend or null in case this information is not available
|
||||
*/
|
||||
public function getUserBackendName($username)
|
||||
{
|
||||
return null; // TODO(10373): Store this to the database when inserting and fetch it here
|
||||
}
|
||||
|
||||
/**
|
||||
* Join group into group_membership
|
||||
*
|
||||
|
@ -238,7 +250,7 @@ class DbUserGroupBackend extends DbRepository implements UserGroupBackendInterfa
|
|||
*/
|
||||
protected function joinGroupMembership(RepositoryQuery $query)
|
||||
{
|
||||
$query->getQuery()->join(
|
||||
$query->getQuery()->joinLeft(
|
||||
$this->requireTable('group_membership'),
|
||||
'g.id = gm.group_id',
|
||||
array()
|
||||
|
|
|
@ -5,10 +5,11 @@ namespace Icinga\Authentication\UserGroup;
|
|||
|
||||
use Icinga\Authentication\User\UserBackend;
|
||||
use Icinga\Authentication\User\LdapUserBackend;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Data\ConfigObject;
|
||||
use Icinga\Exception\ConfigurationError;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\Protocol\Ldap\Expression;
|
||||
use Icinga\Protocol\Ldap\LdapException;
|
||||
use Icinga\Repository\LdapRepository;
|
||||
use Icinga\Repository\RepositoryQuery;
|
||||
use Icinga\User;
|
||||
|
@ -71,6 +72,13 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
*/
|
||||
protected $groupMemberAttribute;
|
||||
|
||||
/**
|
||||
* Whether the attribute name where to find a group's member holds ambiguous values
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $ambiguousMemberAttribute;
|
||||
|
||||
/**
|
||||
* The custom LDAP filter to apply on a user query
|
||||
*
|
||||
|
@ -211,15 +219,13 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
/**
|
||||
* 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);
|
||||
$this->groupClass = $this->getNormedAttribute($groupClass);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -359,22 +365,55 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
}
|
||||
|
||||
/**
|
||||
* Return a new query for the given columns
|
||||
* Return whether the attribute name where to find a group's member holds ambiguous values
|
||||
*
|
||||
* @param array $columns The desired columns, if null all columns will be queried
|
||||
* @return bool
|
||||
*
|
||||
* @return RepositoryQuery
|
||||
* @throws ProgrammingError In case either $this->groupClass or $this->groupMemberAttribute
|
||||
* has not been set yet
|
||||
*/
|
||||
public function select(array $columns = null)
|
||||
protected function isMemberAttributeAmbiguous()
|
||||
{
|
||||
$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));
|
||||
if ($this->ambiguousMemberAttribute === null) {
|
||||
if ($this->groupClass === null) {
|
||||
throw new ProgrammingError(
|
||||
'It is required to set the objectClass where to look for groups first'
|
||||
);
|
||||
} elseif ($this->groupMemberAttribute === null) {
|
||||
throw new ProgrammingError(
|
||||
'It is required to set a attribute name where to find a group\'s members first'
|
||||
);
|
||||
}
|
||||
|
||||
$sampleValue = $this->ds
|
||||
->select()
|
||||
->from($this->groupClass, array($this->groupMemberAttribute))
|
||||
->setUnfoldAttribute($this->groupMemberAttribute)
|
||||
->setBase($this->groupBaseDn)
|
||||
->fetchOne();
|
||||
$this->ambiguousMemberAttribute = !$this->isRelatedDn($sampleValue);
|
||||
}
|
||||
|
||||
return $query;
|
||||
return $this->ambiguousMemberAttribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this repository's virtual tables
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws ProgrammingError In case $this->groupClass has not been set yet
|
||||
*/
|
||||
protected function initializeVirtualTables()
|
||||
{
|
||||
if ($this->groupClass === null) {
|
||||
throw new ProgrammingError('It is required to set the object class where to find groups first');
|
||||
}
|
||||
|
||||
return array(
|
||||
'group' => $this->groupClass,
|
||||
'group_membership' => $this->groupClass
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -382,16 +421,17 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws ProgrammingError In case either $this->groupNameAttribute or $this->groupClass has not been set yet
|
||||
* @throws ProgrammingError In case either $this->groupNameAttribute or
|
||||
* $this->groupMemberAttribute 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->groupMemberAttribute === null) {
|
||||
throw new ProgrammingError('It is required to set a attribute name where to find a group\'s members first');
|
||||
}
|
||||
|
||||
if ($this->ds->getCapabilities()->isActiveDirectory()) {
|
||||
$createdAtAttribute = 'whenCreated';
|
||||
|
@ -409,7 +449,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
'created_at' => $createdAtAttribute,
|
||||
'last_modified' => $lastModifiedAttribute
|
||||
);
|
||||
return array($this->groupClass => $columns, $this->groupClass => $columns);
|
||||
return array('group' => $columns, 'group_membership' => $columns);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -420,7 +460,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
protected function initializeFilterColumns()
|
||||
{
|
||||
return array(
|
||||
t('Username') => 'user',
|
||||
t('Username') => 'user_name',
|
||||
t('User Group') => 'group_name',
|
||||
t('Created At') => 'created_at',
|
||||
t('Last Modified') => 'last_modified'
|
||||
|
@ -431,31 +471,68 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
* 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');
|
||||
}
|
||||
if ($this->groupMemberAttribute === null) {
|
||||
throw new ProgrammingError('It is required to set a attribute name where to find a group\'s members first');
|
||||
}
|
||||
|
||||
$rules = array(
|
||||
$this->groupClass => array(
|
||||
'group' => array(
|
||||
'created_at' => 'generalized_time',
|
||||
'last_modified' => 'generalized_time'
|
||||
),
|
||||
'group_membership' => array(
|
||||
'created_at' => 'generalized_time',
|
||||
'last_modified' => 'generalized_time'
|
||||
)
|
||||
);
|
||||
if (! $this->isAmbiguous($this->groupClass, $this->groupMemberAttribute)) {
|
||||
$rules[$this->groupClass][] = 'user_name';
|
||||
if (! $this->isMemberAttributeAmbiguous()) {
|
||||
$rules['group_membership']['user_name'] = 'user_name';
|
||||
$rules['group_membership']['user'] = 'user_name';
|
||||
$rules['group']['user_name'] = 'user_name';
|
||||
$rules['group']['user'] = 'user_name';
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the distinguished name for the given uid or gid
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function persistUserName($name)
|
||||
{
|
||||
try {
|
||||
$userDn = $this->ds
|
||||
->select()
|
||||
->from($this->userClass, array())
|
||||
->where($this->userNameAttribute, $name)
|
||||
->setBase($this->userBaseDn)
|
||||
->setUsePagedResults(false)
|
||||
->fetchDn();
|
||||
if ($userDn) {
|
||||
return $userDn;
|
||||
}
|
||||
|
||||
$groupDn = $this->ds
|
||||
->select()
|
||||
->from($this->groupClass, array())
|
||||
->where($this->groupNameAttribute, $name)
|
||||
->setBase($this->groupBaseDn)
|
||||
->setUsePagedResults(false)
|
||||
->fetchDn();
|
||||
if ($groupDn) {
|
||||
return $groupDn;
|
||||
}
|
||||
} catch (LdapException $_) {
|
||||
// pass
|
||||
}
|
||||
|
||||
Logger::debug('Unable to persist uid or gid "%s" in repository "%s". No DN found.', $name, $this->getName());
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the uid for the given distinguished name
|
||||
*
|
||||
|
@ -475,11 +552,8 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
/**
|
||||
* 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
|
||||
*
|
||||
|
@ -487,12 +561,14 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
*/
|
||||
public function requireTable($table, RepositoryQuery $query = null)
|
||||
{
|
||||
$table = parent::requireTable($table, $query);
|
||||
if ($table === 'group' || $table === 'group_membership') {
|
||||
$table = $this->groupClass;
|
||||
if ($query !== null) {
|
||||
$query->getQuery()->setBase($this->groupBaseDn);
|
||||
if ($table === 'group' && $this->groupFilter) {
|
||||
$query->getQuery()->setNativeFilter($this->groupFilter);
|
||||
}
|
||||
}
|
||||
|
||||
return $table;
|
||||
return parent::requireTable($table, $query);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -525,7 +601,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
*/
|
||||
public function getMemberships(User $user)
|
||||
{
|
||||
if ($this->isAmbiguous($this->groupClass, $this->groupMemberAttribute)) {
|
||||
if ($this->isMemberAttributeAmbiguous()) {
|
||||
$queryValue = $user->getUsername();
|
||||
} elseif (($queryValue = $user->getAdditional('ldap_dn')) === null) {
|
||||
$userQuery = $this->ds
|
||||
|
@ -535,7 +611,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
->setBase($this->userBaseDn)
|
||||
->setUsePagedResults(false);
|
||||
if ($this->userFilter) {
|
||||
$userQuery->where(new Expression($this->userFilter));
|
||||
$userQuery->setNativeFilter($this->userFilter);
|
||||
}
|
||||
|
||||
if (($queryValue = $userQuery->fetchDn()) === null) {
|
||||
|
@ -549,7 +625,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
->where($this->groupMemberAttribute, $queryValue)
|
||||
->setBase($this->groupBaseDn);
|
||||
if ($this->groupFilter) {
|
||||
$groupQuery->where(new Expression($this->groupFilter));
|
||||
$groupQuery->setNativeFilter($this->groupFilter);
|
||||
}
|
||||
|
||||
$groups = array();
|
||||
|
@ -560,6 +636,21 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the backend that is providing the given user
|
||||
*
|
||||
* @param string $username Unused
|
||||
*
|
||||
* @return null|string The name of the backend or null in case this information is not available
|
||||
*/
|
||||
public function getUserBackendName($username)
|
||||
{
|
||||
$userBackend = $this->getUserBackend();
|
||||
if ($userBackend !== null) {
|
||||
return $userBackend->getName();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the given configuration on this backend
|
||||
*
|
||||
|
@ -607,7 +698,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
|||
|
||||
return $this
|
||||
->setGroupBaseDn($config->base_dn)
|
||||
->setUserBaseDn($config->get('user_base_dn', $this->getGroupBaseDn()))
|
||||
->setUserBaseDn($config->get('user_base_dn', $defaults->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))
|
||||
|
|
|
@ -34,4 +34,13 @@ interface UserGroupBackendInterface
|
|||
* @return array
|
||||
*/
|
||||
public function getMemberships(User $user);
|
||||
|
||||
/**
|
||||
* Return the name of the backend that is providing the given user
|
||||
*
|
||||
* @param string $username
|
||||
*
|
||||
* @return null|string The name of the backend or null in case this information is not available
|
||||
*/
|
||||
public function getUserBackendName($username);
|
||||
}
|
||||
|
|
|
@ -28,9 +28,9 @@ abstract class Command
|
|||
protected $commandName;
|
||||
protected $actionName;
|
||||
|
||||
private $config;
|
||||
protected $config;
|
||||
|
||||
private $configs;
|
||||
protected $configs;
|
||||
|
||||
protected $defaultActionName = 'default';
|
||||
|
||||
|
@ -127,7 +127,7 @@ abstract class Command
|
|||
|
||||
public function fail($msg)
|
||||
{
|
||||
throw new IcingaException($msg);
|
||||
throw new IcingaException('%s', $msg);
|
||||
}
|
||||
|
||||
public function getDefaultActionName()
|
||||
|
|
|
@ -130,6 +130,7 @@ class DbConnection implements Selectable, Extensible, Updatable, Reducible, Insp
|
|||
'username' => $this->config->username,
|
||||
'password' => $this->config->password,
|
||||
'dbname' => $this->config->dbname,
|
||||
'charset' => $this->config->charset,
|
||||
'persistent' => (bool) $this->config->get('persistent', false),
|
||||
'options' => & $genericAdapterOptions,
|
||||
'driver_options' => & $driverOptions
|
||||
|
|
|
@ -288,9 +288,14 @@ class DbQuery extends SimpleQuery
|
|||
$expression = $this->valueToTimestamp($expression);
|
||||
}
|
||||
|
||||
if (is_array($expression) && $sign === '=') {
|
||||
// TODO: Should we support this? Doesn't work for blub*
|
||||
return $col . ' IN (' . $this->escapeForSql($expression) . ')';
|
||||
if (is_array($expression)) {
|
||||
if ($sign === '=') {
|
||||
return $col . ' IN (' . $this->escapeForSql($expression) . ')';
|
||||
} elseif ($sign === '!=') {
|
||||
return $col . ' NOT IN (' . $this->escapeForSql($expression) . ')';
|
||||
}
|
||||
|
||||
throw new QueryException('Unable to render array expressions with operators other than equal or not equal');
|
||||
} elseif ($sign === '=' && strpos($expression, '*') !== false) {
|
||||
if ($expression === '*') {
|
||||
// We'll ignore such filters as it prevents index usage and because "*" means anything, anything means
|
||||
|
|
|
@ -125,17 +125,26 @@ abstract class FilterChain extends Filter
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function listFilteredColumns()
|
||||
/**
|
||||
* List and return all column names referenced in this filter
|
||||
*
|
||||
* @param array $columns The columns listed so far
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function listFilteredColumns(array $columns = array())
|
||||
{
|
||||
$columns = array();
|
||||
foreach ($this->filters as $filter) {
|
||||
if ($filter instanceof FilterExpression) {
|
||||
$columns[] = $filter->getColumn();
|
||||
$column= $filter->getColumn();
|
||||
if (! in_array($column, $columns, true)) {
|
||||
$columns[] = $column;
|
||||
}
|
||||
} else {
|
||||
$columns += $filter->listFilteredColumns();
|
||||
$columns = $filter->listFilteredColumns($columns);
|
||||
}
|
||||
}
|
||||
// array_unique?
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Protocol\Ldap;
|
||||
|
||||
class Expression
|
||||
{
|
||||
protected $value;
|
||||
|
||||
public function __construct($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function setValue($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return (string) $this->getValue();
|
||||
}
|
||||
}
|
|
@ -308,12 +308,8 @@ class LdapCapabilities
|
|||
ldap_error($ds)
|
||||
);
|
||||
}
|
||||
$cap = new LdapCapabilities(
|
||||
$connection->cleanupAttributes(
|
||||
ldap_get_attributes($ds, $entry),
|
||||
array_flip($fields)
|
||||
)
|
||||
);
|
||||
|
||||
$cap = new LdapCapabilities($connection->cleanupAttributes(ldap_get_attributes($ds, $entry), $fields));
|
||||
return $cap;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,13 +7,14 @@ use Exception;
|
|||
use ArrayIterator;
|
||||
use Icinga\Application\Config;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Application\Platform;
|
||||
use Icinga\Data\ConfigObject;
|
||||
use Icinga\Data\Inspectable;
|
||||
use Icinga\Data\Inspection;
|
||||
use Icinga\Data\Selectable;
|
||||
use Icinga\Data\Sortable;
|
||||
use Icinga\Exception\InspectionException;
|
||||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Data\Filter\FilterChain;
|
||||
use Icinga\Data\Filter\FilterExpression;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\Protocol\Ldap\LdapException;
|
||||
|
||||
|
@ -377,14 +378,7 @@ class LdapConnection implements Selectable, Inspectable
|
|||
}
|
||||
|
||||
$ds = $this->getConnection();
|
||||
$results = @ldap_search(
|
||||
$ds,
|
||||
$query->getBase() ?: $this->getDn(),
|
||||
(string) $query,
|
||||
array('dn'),
|
||||
0,
|
||||
0
|
||||
);
|
||||
$results = $this->ldapSearch($query, array('dn'));
|
||||
|
||||
if ($results === false) {
|
||||
if (ldap_errno($ds) !== self::LDAP_NO_SUCH_OBJECT) {
|
||||
|
@ -482,8 +476,28 @@ class LdapConnection implements Selectable, Inspectable
|
|||
*/
|
||||
public function fetchOne(LdapQuery $query, array $fields = null)
|
||||
{
|
||||
$row = (array) $this->fetchRow($query, $fields);
|
||||
return array_shift($row) ?: false;
|
||||
$row = $this->fetchRow($query, $fields);
|
||||
if ($row === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$values = get_object_vars($row);
|
||||
if (empty($values)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($fields === null) {
|
||||
// Fetch the desired columns from the query if not explicitly overriden in the method's parameter
|
||||
$fields = $query->getColumns();
|
||||
}
|
||||
|
||||
if (empty($fields)) {
|
||||
// The desired columns may be empty independently whether provided by the query or the method's parameter
|
||||
return array_shift($values);
|
||||
}
|
||||
|
||||
$alias = key($fields);
|
||||
return $values[is_string($alias) ? $alias : $fields[$alias]];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -694,19 +708,27 @@ class LdapConnection implements Selectable, Inspectable
|
|||
));
|
||||
} else {
|
||||
foreach ($query->getOrder() as $rule) {
|
||||
if (! in_array($rule[0], $fields)) {
|
||||
if (! in_array($rule[0], $fields, true)) {
|
||||
$fields[] = $rule[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$results = @ldap_search(
|
||||
$ds,
|
||||
$query->getBase() ?: $this->rootDn,
|
||||
(string) $query,
|
||||
$unfoldAttribute = $query->getUnfoldAttribute();
|
||||
if ($unfoldAttribute) {
|
||||
foreach ($query->getFilter()->listFilteredColumns() as $filterColumn) {
|
||||
$fieldKey = array_search($filterColumn, $fields, true);
|
||||
if ($fieldKey === false || is_string($fieldKey)) {
|
||||
$fields[] = $filterColumn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$results = $this->ldapSearch(
|
||||
$query,
|
||||
array_values($fields),
|
||||
0, // Attributes and values
|
||||
0,
|
||||
$serverSorting && $limit ? $offset + $limit : 0
|
||||
);
|
||||
if ($results === false) {
|
||||
|
@ -727,25 +749,21 @@ class LdapConnection implements Selectable, Inspectable
|
|||
$count = 0;
|
||||
$entries = array();
|
||||
$entry = ldap_first_entry($ds, $results);
|
||||
$unfoldAttribute = $query->getUnfoldAttribute();
|
||||
do {
|
||||
if ($unfoldAttribute) {
|
||||
$rows = $this->cleanupAttributes(
|
||||
ldap_get_attributes($ds, $entry),
|
||||
array_flip($fields),
|
||||
$unfoldAttribute
|
||||
);
|
||||
|
||||
$rows = $this->cleanupAttributes(ldap_get_attributes($ds, $entry), $fields, $unfoldAttribute);
|
||||
if (is_array($rows)) {
|
||||
// TODO: Register the DN the same way as a section name in the ArrayDatasource!
|
||||
foreach ($rows as $row) {
|
||||
$count += 1;
|
||||
if (! $serverSorting || $offset === 0 || $offset < $count) {
|
||||
$entries[] = $row;
|
||||
}
|
||||
if ($query->getFilter()->matches($row)) {
|
||||
$count += 1;
|
||||
if (! $serverSorting || $offset === 0 || $offset < $count) {
|
||||
$entries[] = $row;
|
||||
}
|
||||
|
||||
if ($serverSorting && $limit > 0 && $limit === count($entries)) {
|
||||
break;
|
||||
if ($serverSorting && $limit > 0 && $limit === count($entries)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -759,7 +777,7 @@ class LdapConnection implements Selectable, Inspectable
|
|||
if (! $serverSorting || $offset === 0 || $offset < $count) {
|
||||
$entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
|
||||
ldap_get_attributes($ds, $entry),
|
||||
array_flip($fields)
|
||||
$fields
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -799,8 +817,6 @@ class LdapConnection implements Selectable, Inspectable
|
|||
|
||||
$limit = $query->getLimit();
|
||||
$offset = $query->hasOffset() ? $query->getOffset() : 0;
|
||||
$queryString = (string) $query;
|
||||
$base = $query->getBase() ?: $this->rootDn;
|
||||
|
||||
if ($fields === null) {
|
||||
$fields = $query->getColumns();
|
||||
|
@ -811,16 +827,25 @@ class LdapConnection implements Selectable, Inspectable
|
|||
$serverSorting = false;//$this->getCapabilities()->hasOid(LdapCapabilities::LDAP_SERVER_SORT_OID);
|
||||
if (! $serverSorting && $query->hasOrder()) {
|
||||
foreach ($query->getOrder() as $rule) {
|
||||
if (! in_array($rule[0], $fields)) {
|
||||
if (! in_array($rule[0], $fields, true)) {
|
||||
$fields[] = $rule[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$unfoldAttribute = $query->getUnfoldAttribute();
|
||||
if ($unfoldAttribute) {
|
||||
foreach ($query->getFilter()->listFilteredColumns() as $filterColumn) {
|
||||
$fieldKey = array_search($filterColumn, $fields, true);
|
||||
if ($fieldKey === false || is_string($fieldKey)) {
|
||||
$fields[] = $filterColumn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
$cookie = '';
|
||||
$entries = array();
|
||||
$unfoldAttribute = $query->getUnfoldAttribute();
|
||||
do {
|
||||
// Do not request the pagination control as a critical extension, as we want the
|
||||
// server to return results even if the paged search request cannot be satisfied
|
||||
|
@ -835,12 +860,10 @@ class LdapConnection implements Selectable, Inspectable
|
|||
));
|
||||
}
|
||||
|
||||
$results = @ldap_search(
|
||||
$ds,
|
||||
$base,
|
||||
$queryString,
|
||||
$results = $this->ldapSearch(
|
||||
$query,
|
||||
array_values($fields),
|
||||
0, // Attributes and values
|
||||
0,
|
||||
$serverSorting && $limit ? $offset + $limit : 0
|
||||
);
|
||||
if ($results === false) {
|
||||
|
@ -850,14 +873,15 @@ class LdapConnection implements Selectable, Inspectable
|
|||
|
||||
throw new LdapException(
|
||||
'LDAP query "%s" (base %s) failed. Error: %s',
|
||||
$queryString,
|
||||
$base,
|
||||
(string) $query,
|
||||
$query->getBase() ?: $this->getDn(),
|
||||
ldap_error($ds)
|
||||
);
|
||||
} elseif (ldap_count_entries($ds, $results) === 0) {
|
||||
if (in_array(
|
||||
ldap_errno($ds),
|
||||
array(static::LDAP_SIZELIMIT_EXCEEDED, static::LDAP_ADMINLIMIT_EXCEEDED)
|
||||
array(static::LDAP_SIZELIMIT_EXCEEDED, static::LDAP_ADMINLIMIT_EXCEEDED),
|
||||
true
|
||||
)) {
|
||||
Logger::warning(
|
||||
'Unable to request more than %u results. Does the server allow paged search requests? (%s)',
|
||||
|
@ -872,22 +896,19 @@ class LdapConnection implements Selectable, Inspectable
|
|||
$entry = ldap_first_entry($ds, $results);
|
||||
do {
|
||||
if ($unfoldAttribute) {
|
||||
$rows = $this->cleanupAttributes(
|
||||
ldap_get_attributes($ds, $entry),
|
||||
array_flip($fields),
|
||||
$unfoldAttribute
|
||||
);
|
||||
|
||||
$rows = $this->cleanupAttributes(ldap_get_attributes($ds, $entry), $fields, $unfoldAttribute);
|
||||
if (is_array($rows)) {
|
||||
// TODO: Register the DN the same way as a section name in the ArrayDatasource!
|
||||
foreach ($rows as $row) {
|
||||
$count += 1;
|
||||
if (! $serverSorting || $offset === 0 || $offset < $count) {
|
||||
$entries[] = $row;
|
||||
}
|
||||
if ($query->getFilter()->matches($row)) {
|
||||
$count += 1;
|
||||
if (! $serverSorting || $offset === 0 || $offset < $count) {
|
||||
$entries[] = $row;
|
||||
}
|
||||
|
||||
if ($serverSorting && $limit > 0 && $limit === count($entries)) {
|
||||
break;
|
||||
if ($serverSorting && $limit > 0 && $limit === count($entries)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -901,7 +922,7 @@ class LdapConnection implements Selectable, Inspectable
|
|||
if (! $serverSorting || $offset === 0 || $offset < $count) {
|
||||
$entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
|
||||
ldap_get_attributes($ds, $entry),
|
||||
array_flip($fields)
|
||||
$fields
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -932,7 +953,8 @@ class LdapConnection implements Selectable, Inspectable
|
|||
// pagedResultsControl with the size set to zero (0) and the cookie set to the last cookie returned by
|
||||
// the server: https://www.ietf.org/rfc/rfc2696.txt
|
||||
ldap_control_paged_result($ds, 0, false, $cookie);
|
||||
ldap_search($ds, $base, $queryString); // Returns no entries, due to the page size
|
||||
// Returns no entries, due to the page size
|
||||
ldap_search($ds, $query->getBase() ?: $this->getDn(), (string) $query);
|
||||
}
|
||||
|
||||
if (! $serverSorting && $query->hasOrder()) {
|
||||
|
@ -963,8 +985,17 @@ class LdapConnection implements Selectable, Inspectable
|
|||
// necessary to create another array to map attributes case insensitively to their requested counterparts.
|
||||
// This does also apply the virtual alias handling. (Since an LDAP server does not handle such)
|
||||
$loweredFieldMap = array();
|
||||
foreach ($requestedFields as $name => $alias) {
|
||||
$loweredFieldMap[strtolower($name)] = is_string($alias) ? $alias : $name;
|
||||
foreach ($requestedFields as $alias => $name) {
|
||||
$loweredName = strtolower($name);
|
||||
if (isset($loweredFieldMap[$loweredName])) {
|
||||
if (! is_array($loweredFieldMap[$loweredName])) {
|
||||
$loweredFieldMap[$loweredName] = array($loweredFieldMap[$loweredName]);
|
||||
}
|
||||
|
||||
$loweredFieldMap[$loweredName][] = is_string($alias) ? $alias : $name;
|
||||
} else {
|
||||
$loweredFieldMap[$loweredName] = is_string($alias) ? $alias : $name;
|
||||
}
|
||||
}
|
||||
|
||||
$cleanedAttributes = array();
|
||||
|
@ -982,12 +1013,18 @@ class LdapConnection implements Selectable, Inspectable
|
|||
$requestedAttributeName = isset($loweredFieldMap[strtolower($attribute_name)])
|
||||
? $loweredFieldMap[strtolower($attribute_name)]
|
||||
: $attribute_name;
|
||||
$cleanedAttributes[$requestedAttributeName] = $attribute_value;
|
||||
if (is_array($requestedAttributeName)) {
|
||||
foreach ($requestedAttributeName as $requestedName) {
|
||||
$cleanedAttributes[$requestedName] = $attribute_value;
|
||||
}
|
||||
} else {
|
||||
$cleanedAttributes[$requestedAttributeName] = $attribute_value;
|
||||
}
|
||||
}
|
||||
|
||||
// The result may not contain all requested fields, so populate the cleaned
|
||||
// result with the missing fields and their value being set to null
|
||||
foreach ($requestedFields as $name => $alias) {
|
||||
foreach ($requestedFields as $alias => $name) {
|
||||
if (! is_string($alias)) {
|
||||
$alias = $name;
|
||||
}
|
||||
|
@ -1003,6 +1040,14 @@ class LdapConnection implements Selectable, Inspectable
|
|||
&& isset($cleanedAttributes[$unfoldAttribute])
|
||||
&& is_array($cleanedAttributes[$unfoldAttribute])
|
||||
) {
|
||||
$siblings = array();
|
||||
foreach ($loweredFieldMap as $loweredName => $requestedNames) {
|
||||
if (is_array($requestedNames) && in_array($unfoldAttribute, $requestedNames, true)) {
|
||||
$siblings = array_diff($requestedNames, array($unfoldAttribute));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$values = $cleanedAttributes[$unfoldAttribute];
|
||||
unset($cleanedAttributes[$unfoldAttribute]);
|
||||
$baseRow = (object) $cleanedAttributes;
|
||||
|
@ -1010,6 +1055,10 @@ class LdapConnection implements Selectable, Inspectable
|
|||
foreach ($values as $value) {
|
||||
$row = clone $baseRow;
|
||||
$row->{$unfoldAttribute} = $value;
|
||||
foreach ($siblings as $sibling) {
|
||||
$row->{$sibling} = $value;
|
||||
}
|
||||
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
|
@ -1119,6 +1168,77 @@ class LdapConnection implements Selectable, Inspectable
|
|||
return $ds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a LDAP search and return the result
|
||||
*
|
||||
* @param LdapQuery $query
|
||||
* @param array $attributes An array of the required attributes
|
||||
* @param int $attrsonly Should be set to 1 if only attribute types are wanted
|
||||
* @param int $sizelimit Enables you to limit the count of entries fetched
|
||||
* @param int $timelimit Sets the number of seconds how long is spend on the search
|
||||
* @param int $deref
|
||||
*
|
||||
* @return resource|bool A search result identifier or false on error
|
||||
*/
|
||||
public function ldapSearch(
|
||||
LdapQuery $query,
|
||||
array $attributes = null,
|
||||
$attrsonly = 0,
|
||||
$sizelimit = 0,
|
||||
$timelimit = 0,
|
||||
$deref = LDAP_DEREF_NEVER
|
||||
) {
|
||||
$queryString = (string) $query;
|
||||
$baseDn = $query->getBase() ?: $this->getDn();
|
||||
|
||||
if (Logger::getInstance()->getLevel() === Logger::DEBUG) {
|
||||
// We're checking the level by ourself to avoid rendering the ldapsearch commandline for nothing
|
||||
$starttlsParam = $this->encryption === static::STARTTLS ? ' -ZZ' : '';
|
||||
$ldapUrl = ($this->encryption === static::LDAPS ? 'ldaps://' : 'ldap://')
|
||||
. $this->hostname
|
||||
. ($this->port ? ':' . $this->port : '');
|
||||
|
||||
if ($this->bound) {
|
||||
$bindParams = ' -D "' . $this->bindDn . '"' . ($this->bindPw ? ' -w "' . $this->bindPw . '"' : '');
|
||||
}
|
||||
|
||||
if ($deref === LDAP_DEREF_NEVER) {
|
||||
$derefName = 'never';
|
||||
} elseif ($deref === LDAP_DEREF_ALWAYS) {
|
||||
$derefName = 'always';
|
||||
} elseif ($deref === LDAP_DEREF_SEARCHING) {
|
||||
$derefName = 'search';
|
||||
} else { // $deref === LDAP_DEREF_FINDING
|
||||
$derefName = 'find';
|
||||
}
|
||||
|
||||
Logger::debug("Issueing LDAP search. Use '%s' to reproduce.", sprintf(
|
||||
'ldapsearch -P 3%s -H "%s"%s -b "%s" -s "sub" -z %u -l %u -a "%s"%s%s%s',
|
||||
$starttlsParam,
|
||||
$ldapUrl,
|
||||
$bindParams,
|
||||
$baseDn,
|
||||
$sizelimit,
|
||||
$timelimit,
|
||||
$derefName,
|
||||
$attrsonly ? ' -A' : '',
|
||||
$queryString ? ' "' . $queryString . '"' : '',
|
||||
$attributes ? ' "' . join('" "', $attributes) . '"' : ''
|
||||
));
|
||||
}
|
||||
|
||||
return @ldap_search(
|
||||
$this->getConnection(),
|
||||
$baseDn,
|
||||
$queryString,
|
||||
$attributes,
|
||||
$attrsonly,
|
||||
$sizelimit,
|
||||
$timelimit,
|
||||
$deref
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an LDAP entry
|
||||
*
|
||||
|
@ -1184,6 +1304,93 @@ class LdapConnection implements Selectable, Inspectable
|
|||
return $dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render and return a valid LDAP filter representation of the given filter
|
||||
*
|
||||
* @param Filter $filter
|
||||
* @param int $level
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function renderFilter(Filter $filter, $level = 0)
|
||||
{
|
||||
if ($filter->isExpression()) {
|
||||
/** @var $filter FilterExpression */
|
||||
return $this->renderFilterExpression($filter);
|
||||
}
|
||||
|
||||
/** @var $filter FilterChain */
|
||||
$parts = array();
|
||||
foreach ($filter->filters() as $filterPart) {
|
||||
$part = $this->renderFilter($filterPart, $level + 1);
|
||||
if ($part) {
|
||||
$parts[] = $part;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($parts)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$format = '%1$s(%2$s)';
|
||||
if (count($parts) === 1) {
|
||||
$format = '%2$s';
|
||||
}
|
||||
if ($level === 0) {
|
||||
$format = '(' . $format . ')';
|
||||
}
|
||||
|
||||
return sprintf($format, $filter->getOperatorSymbol(), implode(')(', $parts));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render and return a valid LDAP filter expression of the given filter
|
||||
*
|
||||
* @param FilterExpression $filter
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function renderFilterExpression(FilterExpression $filter)
|
||||
{
|
||||
$column = $filter->getColumn();
|
||||
$sign = $filter->getSign();
|
||||
$expression = $filter->getExpression();
|
||||
$format = '%1$s%2$s%3$s';
|
||||
|
||||
if ($expression === null || $expression === true) {
|
||||
$expression = '*';
|
||||
} elseif (is_array($expression)) {
|
||||
$seqFormat = '|(%s)';
|
||||
if ($sign === '!=') {
|
||||
$seqFormat = '!(' . $seqFormat . ')';
|
||||
$sign = '=';
|
||||
}
|
||||
|
||||
$seqParts = array();
|
||||
foreach ($expression as $expressionValue) {
|
||||
$seqParts[] = sprintf(
|
||||
$format,
|
||||
LdapUtils::quoteForSearch($column),
|
||||
$sign,
|
||||
LdapUtils::quoteForSearch($expressionValue, true)
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf($seqFormat, implode(')(', $seqParts));
|
||||
}
|
||||
|
||||
if ($sign === '!=') {
|
||||
$format = '!(%1$s=%3$s)';
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
$format,
|
||||
LdapUtils::quoteForSearch($column),
|
||||
$sign,
|
||||
LdapUtils::quoteForSearch($expression, true)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspect if this LDAP Connection is working as expected
|
||||
*
|
||||
|
|
|
@ -4,23 +4,12 @@
|
|||
namespace Icinga\Protocol\Ldap;
|
||||
|
||||
use Icinga\Data\SimpleQuery;
|
||||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Exception\NotImplementedError;
|
||||
|
||||
/**
|
||||
* LDAP query class
|
||||
*/
|
||||
class LdapQuery extends SimpleQuery
|
||||
{
|
||||
/**
|
||||
* This query's filters
|
||||
*
|
||||
* Currently just a basic key/value pair based array. Can be removed once Icinga\Data\Filter is supported.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $filters;
|
||||
|
||||
/**
|
||||
* The base dn being used for this query
|
||||
*
|
||||
|
@ -42,12 +31,18 @@ class LdapQuery extends SimpleQuery
|
|||
*/
|
||||
protected $unfoldAttribute;
|
||||
|
||||
/**
|
||||
* This query's native LDAP filter
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $nativeFilter;
|
||||
|
||||
/**
|
||||
* Initialize this query
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
$this->filters = array();
|
||||
$this->usePagedResults = false;
|
||||
}
|
||||
|
||||
|
@ -120,6 +115,29 @@ class LdapQuery extends SimpleQuery
|
|||
return $this->unfoldAttribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this query's native LDAP filter
|
||||
*
|
||||
* @param string $filter
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setNativeFilter($filter)
|
||||
{
|
||||
$this->nativeFilter = $filter;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this query's native LDAP filter
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNativeFilter()
|
||||
{
|
||||
return $this->nativeFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose an objectClass and the columns you are interested in
|
||||
*
|
||||
|
@ -127,53 +145,10 @@ class LdapQuery extends SimpleQuery
|
|||
*/
|
||||
public function from($target, array $fields = null)
|
||||
{
|
||||
$this->filters['objectClass'] = $target;
|
||||
$this->where('objectClass', $target);
|
||||
return parent::from($target, $fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new filter to the query
|
||||
*
|
||||
* @param string $condition Column to search in
|
||||
* @param mixed $value Value to look for (asterisk wildcards are allowed)
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function where($condition, $value = null)
|
||||
{
|
||||
// TODO: Adjust this once support for Icinga\Data\Filter is available
|
||||
if ($condition instanceof Expression) {
|
||||
$this->filters[] = $condition;
|
||||
} else {
|
||||
$this->filters[$condition] = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFilter()
|
||||
{
|
||||
throw new NotImplementedError('Support for Icinga\Data\Filter is still missing. Use $this->where() instead');
|
||||
}
|
||||
|
||||
public function addFilter(Filter $filter)
|
||||
{
|
||||
// TODO: This should be considered a quick fix only.
|
||||
// Drop this entirely once support for Icinga\Data\Filter is available
|
||||
if ($filter->isExpression()) {
|
||||
$this->where($filter->getColumn(), $filter->getExpression());
|
||||
} elseif ($filter->isChain()) {
|
||||
foreach ($filter->filters() as $chainOrExpression) {
|
||||
$this->addFilter($chainOrExpression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function setFilter(Filter $filter)
|
||||
{
|
||||
throw new NotImplementedError('Support for Icinga\Data\Filter is still missing. Use $this->where() instead');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch result as tree
|
||||
*
|
||||
|
@ -225,36 +200,18 @@ class LdapQuery extends SimpleQuery
|
|||
}
|
||||
|
||||
/**
|
||||
* Return the LDAP filter to be applied on this query
|
||||
* Render and return this query's filter
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws LdapException In case the objectClass filter does not exist
|
||||
*/
|
||||
protected function renderFilter()
|
||||
public function renderFilter()
|
||||
{
|
||||
if (! isset($this->filters['objectClass'])) {
|
||||
throw new LdapException('Object class is mandatory');
|
||||
$filter = $this->ds->renderFilter($this->filter);
|
||||
if ($this->nativeFilter) {
|
||||
$filter = '(&(' . $this->nativeFilter . ')' . $filter . ')';
|
||||
}
|
||||
|
||||
$parts = array();
|
||||
foreach ($this->filters as $key => $value) {
|
||||
if ($value instanceof Expression) {
|
||||
$parts[] = (string) $value;
|
||||
} else {
|
||||
$parts[] = sprintf(
|
||||
'%s=%s',
|
||||
LdapUtils::quoteForSearch($key),
|
||||
LdapUtils::quoteForSearch($value, true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($parts) > 1) {
|
||||
return '(&(' . implode(')(', $parts) . '))';
|
||||
} else {
|
||||
return '(' . $parts[0] . ')';
|
||||
}
|
||||
return $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace Icinga\Repository;
|
|||
use Icinga\Data\Db\DbConnection;
|
||||
use Icinga\Data\Extensible;
|
||||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Data\Filter\FilterExpression;
|
||||
use Icinga\Data\Reducible;
|
||||
use Icinga\Data\Updatable;
|
||||
use Icinga\Exception\IcingaException;
|
||||
|
@ -93,20 +94,20 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
protected $statementColumnAliasMap;
|
||||
|
||||
/**
|
||||
* List of columns where the COLLATE SQL-instruction has been removed
|
||||
* List of column names or aliases mapped to their table where the COLLATE SQL-instruction has been removed
|
||||
*
|
||||
* This list is being populated in case of a PostgreSQL backend only,
|
||||
* to ensure case-insensitive string comparison in WHERE clauses.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $columnsWithoutCollation;
|
||||
protected $caseInsensitiveColumns;
|
||||
|
||||
/**
|
||||
* Create a new DB repository object
|
||||
*
|
||||
* In case $this->queryColumns has already been initialized, this initializes
|
||||
* $this->columnsWithoutCollation in case of a PostgreSQL connection.
|
||||
* $this->caseInsensitiveColumns in case of a PostgreSQL connection.
|
||||
*
|
||||
* @param DbConnection $ds The datasource to use
|
||||
*/
|
||||
|
@ -114,7 +115,6 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
{
|
||||
parent::__construct($ds);
|
||||
|
||||
$this->columnsWithoutCollation = array();
|
||||
if ($ds->getDbType() === 'pgsql' && $this->queryColumns !== null) {
|
||||
$this->queryColumns = $this->removeCollateInstruction($this->queryColumns);
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
/**
|
||||
* Return the query columns being provided
|
||||
*
|
||||
* Initializes $this->columnsWithoutCollation in case of a PostgreSQL connection.
|
||||
* Initializes $this->caseInsensitiveColumns in case of a PostgreSQL connection.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
@ -174,11 +174,12 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
*/
|
||||
protected function removeCollateInstruction($queryColumns)
|
||||
{
|
||||
foreach ($queryColumns as & $columns) {
|
||||
foreach ($columns as & $column) {
|
||||
foreach ($queryColumns as $table => & $columns) {
|
||||
foreach ($columns as $alias => & $column) {
|
||||
// Using a regex here because COLLATE may occur anywhere in the string
|
||||
$column = preg_replace('/ COLLATE .+$/', '', $column, -1, $count);
|
||||
if ($count > 0) {
|
||||
$this->columnsWithoutCollation[] = $column;
|
||||
$this->caseInsensitiveColumns[$table][is_string($alias) ? $alias : $column] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -254,17 +255,24 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
* Return the given table with its alias being applied
|
||||
*
|
||||
* @param array|string $table
|
||||
* @param string $virtualTable
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
protected function applyTableAlias($table)
|
||||
protected function applyTableAlias($table, $virtualTable = null)
|
||||
{
|
||||
$tableAliases = $this->getTableAliases();
|
||||
if (is_array($table) || !isset($tableAliases[($nonPrefixedTable = $this->removeTablePrefix($table))])) {
|
||||
return $table;
|
||||
if (! is_array($table)) {
|
||||
if ($virtualTable !== null && isset($tableAliases[$virtualTable])) {
|
||||
return array($tableAliases[$virtualTable] => $table);
|
||||
}
|
||||
|
||||
if (isset($tableAliases[($nonPrefixedTable = $this->removeTablePrefix($table))])) {
|
||||
return array($tableAliases[$nonPrefixedTable] => $table);
|
||||
}
|
||||
}
|
||||
|
||||
return array($tableAliases[$nonPrefixedTable] => $table);
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -294,10 +302,16 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
*
|
||||
* @param string $table
|
||||
* @param array $bind
|
||||
*
|
||||
* @return int The number of affected rows
|
||||
*/
|
||||
public function insert($table, array $bind)
|
||||
{
|
||||
$this->ds->insert($this->prependTablePrefix($table), $this->requireStatementColumns($table, $bind));
|
||||
$this->requireTable($table);
|
||||
return $this->ds->insert(
|
||||
$this->prependTablePrefix($table),
|
||||
$this->requireStatementColumns($table, $bind)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -306,14 +320,22 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
* @param string $table
|
||||
* @param array $bind
|
||||
* @param Filter $filter
|
||||
*
|
||||
* @return int The number of affected rows
|
||||
*/
|
||||
public function update($table, array $bind, Filter $filter = null)
|
||||
{
|
||||
$this->requireTable($table);
|
||||
|
||||
if ($filter) {
|
||||
$filter = $this->requireFilter($table, $filter);
|
||||
}
|
||||
|
||||
$this->ds->update($this->prependTablePrefix($table), $this->requireStatementColumns($table, $bind), $filter);
|
||||
return $this->ds->update(
|
||||
$this->prependTablePrefix($table),
|
||||
$this->requireStatementColumns($table, $bind),
|
||||
$filter
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -321,14 +343,18 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
*
|
||||
* @param string $table
|
||||
* @param Filter $filter
|
||||
*
|
||||
* @return int The number of affected rows
|
||||
*/
|
||||
public function delete($table, Filter $filter = null)
|
||||
{
|
||||
$this->requireTable($table);
|
||||
|
||||
if ($filter) {
|
||||
$filter = $this->requireFilter($table, $filter);
|
||||
}
|
||||
|
||||
$this->ds->delete($this->prependTablePrefix($table), $filter);
|
||||
return $this->ds->delete($this->prependTablePrefix($table), $filter);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -468,8 +494,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
* This does not check whether any conversion for the given table is available if $column is not given, as it
|
||||
* may be possible that columns from another table where joined in which would otherwise not being converted.
|
||||
*
|
||||
* @param array|string $table
|
||||
* @param string $column
|
||||
* @param string $table
|
||||
* @param string $column
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
|
@ -477,10 +503,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
{
|
||||
if ($column !== null) {
|
||||
if ($this->validateQueryColumnAssociation($table, $column)) {
|
||||
return parent::providesValueConversion(
|
||||
$this->removeTablePrefix($this->clearTableAlias($table)),
|
||||
$column
|
||||
);
|
||||
return parent::providesValueConversion($table, $column);
|
||||
}
|
||||
|
||||
if (($tableName = $this->findTableName($column))) {
|
||||
|
@ -513,11 +536,9 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
protected function getConverter($table, $name, $context, RepositoryQuery $query = null)
|
||||
{
|
||||
if (
|
||||
($query !== null && $this->validateQueryColumnAssociation($table, $name))
|
||||
|| ($query === null && $this->validateStatementColumnAssociation($table, $name))
|
||||
! ($query !== null && $this->validateQueryColumnAssociation($table, $name))
|
||||
&& !($query === null && $this->validateStatementColumnAssociation($table, $name))
|
||||
) {
|
||||
$table = $this->removeTablePrefix($this->clearTableAlias($table));
|
||||
} else {
|
||||
$table = $this->findTableName($name);
|
||||
if (! $table) {
|
||||
throw new ProgrammingError('Column name validation seems to have failed. Did you require the column?');
|
||||
|
@ -542,86 +563,46 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
*/
|
||||
public function requireTable($table, RepositoryQuery $query = null)
|
||||
{
|
||||
$virtualTable = null;
|
||||
$statementColumns = $this->getStatementColumns();
|
||||
if (! isset($statementColumns[$table])) {
|
||||
$table = parent::requireTable($table);
|
||||
}
|
||||
|
||||
return $this->prependTablePrefix($this->applyTableAlias($table));
|
||||
}
|
||||
|
||||
/**
|
||||
* Recurse the given filter, require each column for the given table and convert all values
|
||||
*
|
||||
* In case of a PostgreSQL connection, this applies LOWER() on the column and strtolower()
|
||||
* on the value if a COLLATE SQL-instruction is part of the resolved column.
|
||||
*
|
||||
* @param string $table The table being filtered
|
||||
* @param Filter $filter The filter to recurse
|
||||
* @param RepositoryQuery $query An optional query to pass as context
|
||||
* (Directly passed through to $this->requireFilterColumn)
|
||||
* @param bool $clone Whether to clone $filter first
|
||||
*
|
||||
* @return Filter The udpated filter
|
||||
*/
|
||||
public function requireFilter($table, Filter $filter, RepositoryQuery $query = null, $clone = true)
|
||||
{
|
||||
$filter = parent::requireFilter($table, $filter, $query, $clone);
|
||||
|
||||
if ($filter->isExpression()) {
|
||||
$column = $filter->getColumn();
|
||||
if (in_array($column, $this->columnsWithoutCollation) && strpos($column, 'LOWER') !== 0) {
|
||||
$filter->setColumn('LOWER(' . $column . ')');
|
||||
$expression = $filter->getExpression();
|
||||
if (is_array($expression)) {
|
||||
$filter->setExpression(array_map('strtolower', $expression));
|
||||
} else {
|
||||
$filter->setExpression(strtolower($expression));
|
||||
}
|
||||
$newTable = parent::requireTable($table);
|
||||
if ($newTable !== $table) {
|
||||
$virtualTable = $table;
|
||||
}
|
||||
|
||||
$table = $newTable;
|
||||
}
|
||||
|
||||
return $filter;
|
||||
return $this->prependTablePrefix($this->applyTableAlias($table, $virtualTable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this repository's query columns of the given table mapped to their respective aliases
|
||||
* Return the alias for the given table or null if none has been defined
|
||||
*
|
||||
* @param array|string $table
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws ProgrammingError In case $table does not exist
|
||||
*/
|
||||
public function requireAllQueryColumns($table)
|
||||
{
|
||||
return parent::requireAllQueryColumns($this->removeTablePrefix($this->clearTableAlias($table)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query column name for the given alias or null in case the alias does not exist
|
||||
*
|
||||
* @param array|string $table
|
||||
* @param string $alias
|
||||
* @param string $table
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function resolveQueryColumnAlias($table, $alias)
|
||||
public function resolveTableAlias($table)
|
||||
{
|
||||
return parent::resolveQueryColumnAlias($this->removeTablePrefix($this->clearTableAlias($table)), $alias);
|
||||
$tableAliases = $this->getTableAliases();
|
||||
if (isset($tableAliases[$table])) {
|
||||
return $tableAliases[$table];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the alias for the given query column name or null in case the query column name does not exist
|
||||
*
|
||||
* @param array|string $table
|
||||
* @param string $column
|
||||
* @param string $table
|
||||
* @param string $column
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function reassembleQueryColumnAlias($table, $column)
|
||||
{
|
||||
$alias = parent::reassembleQueryColumnAlias($this->removeTablePrefix($this->clearTableAlias($table)), $column);
|
||||
$alias = parent::reassembleQueryColumnAlias($table, $column);
|
||||
if (
|
||||
$alias === null
|
||||
&& !$this->validateQueryColumnAssociation($table, $column)
|
||||
|
@ -633,29 +614,13 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
return $alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given query column name or alias is available in the given table
|
||||
*
|
||||
* @param array|string $table
|
||||
* @param string $column
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validateQueryColumnAssociation($table, $column)
|
||||
{
|
||||
return parent::validateQueryColumnAssociation(
|
||||
$this->removeTablePrefix($this->clearTableAlias($table)),
|
||||
$column
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the given column is a valid query target and return it or the actual name if it's an alias
|
||||
*
|
||||
* Attempts to join the given column from a different table if its association to the given table cannot be
|
||||
* verified.
|
||||
*
|
||||
* @param array|string $table The table where to look for the column or alias
|
||||
* @param string $table The table where to look for the column or alias
|
||||
* @param string $name The name or alias of the column to validate
|
||||
* @param RepositoryQuery $query An optional query to pass as context,
|
||||
* if not given no join will be attempted
|
||||
|
@ -667,7 +632,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
public function requireQueryColumn($table, $name, RepositoryQuery $query = null)
|
||||
{
|
||||
if ($query === null || $this->validateQueryColumnAssociation($table, $name)) {
|
||||
return parent::requireQueryColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name, $query);
|
||||
return parent::requireQueryColumn($table, $name, $query);
|
||||
}
|
||||
|
||||
return $this->joinColumn($name, $table, $query);
|
||||
|
@ -677,35 +642,65 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
* Validate that the given column is a valid filter target and return it or the actual name if it's an alias
|
||||
*
|
||||
* Attempts to join the given column from a different table if its association to the given table cannot be
|
||||
* verified.
|
||||
* verified. In case of a PostgreSQL connection and if a COLLATE SQL-instruction is part of the resolved column,
|
||||
* this applies LOWER() on the column and, if given, strtolower() on the filter's expression.
|
||||
*
|
||||
* @param array|string $table The table where to look for the column or alias
|
||||
* @param string $table The table where to look for the column or alias
|
||||
* @param string $name The name or alias of the column to validate
|
||||
* @param RepositoryQuery $query An optional query to pass as context,
|
||||
* if not given the column is considered being used for a statement filter
|
||||
* @param FilterExpression $filter An optional filter to pass as context
|
||||
*
|
||||
* @return string The given column's name
|
||||
*
|
||||
* @throws QueryException In case the given column is not a valid filter column
|
||||
*/
|
||||
public function requireFilterColumn($table, $name, RepositoryQuery $query = null)
|
||||
public function requireFilterColumn($table, $name, RepositoryQuery $query = null, FilterExpression $filter = null)
|
||||
{
|
||||
$joined = false;
|
||||
if ($query === null) {
|
||||
return $this->requireStatementColumn($table, $name);
|
||||
$column = $this->requireStatementColumn($table, $name);
|
||||
} elseif ($this->validateQueryColumnAssociation($table, $name)) {
|
||||
$column = parent::requireFilterColumn($table, $name, $query, $filter);
|
||||
} else {
|
||||
$column = $this->joinColumn($name, $table, $query);
|
||||
$joined = true;
|
||||
}
|
||||
|
||||
if ($this->validateQueryColumnAssociation($table, $name)) {
|
||||
return parent::requireFilterColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name, $query);
|
||||
if (! empty($this->caseInsensitiveColumns)) {
|
||||
if ($joined) {
|
||||
$table = $this->findTableName($name);
|
||||
}
|
||||
|
||||
if ($column === $name) {
|
||||
if ($query === null) {
|
||||
$name = $this->reassembleStatementColumnAlias($table, $name);
|
||||
} else {
|
||||
$name = $this->reassembleQueryColumnAlias($table, $name);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->caseInsensitiveColumns[$table][$name])) {
|
||||
$column = 'LOWER(' . $column . ')';
|
||||
if ($filter !== null) {
|
||||
$expression = $filter->getExpression();
|
||||
if (is_array($expression)) {
|
||||
$filter->setExpression(array_map('strtolower', $expression));
|
||||
} else {
|
||||
$filter->setExpression(strtolower($expression));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->joinColumn($name, $table, $query);
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the statement column name for the given alias or null in case the alias does not exist
|
||||
*
|
||||
* @param array|string $table
|
||||
* @param string $alias
|
||||
* @param string $table
|
||||
* @param string $alias
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
|
@ -716,7 +711,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
return $statementAliasColumnMap[$alias];
|
||||
}
|
||||
|
||||
$prefixedAlias = $this->removeTablePrefix($this->clearTableAlias($table)) . '.' . $alias;
|
||||
$prefixedAlias = $table . '.' . $alias;
|
||||
if (isset($statementAliasColumnMap[$prefixedAlias])) {
|
||||
return $statementAliasColumnMap[$prefixedAlias];
|
||||
}
|
||||
|
@ -725,8 +720,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
/**
|
||||
* Return the alias for the given statement column name or null in case the statement column does not exist
|
||||
*
|
||||
* @param array|string $table
|
||||
* @param string $column
|
||||
* @param string $table
|
||||
* @param string $column
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
|
@ -737,7 +732,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
return $statementColumnAliasMap[$column];
|
||||
}
|
||||
|
||||
$prefixedColumn = $this->removeTablePrefix($this->clearTableAlias($table)) . '.' . $column;
|
||||
$prefixedColumn = $table . '.' . $column;
|
||||
if (isset($statementColumnAliasMap[$prefixedColumn])) {
|
||||
return $statementColumnAliasMap[$prefixedColumn];
|
||||
}
|
||||
|
@ -746,34 +741,32 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
/**
|
||||
* Return whether the given alias or statement column name is available in the given table
|
||||
*
|
||||
* @param array|string $table
|
||||
* @param string $alias
|
||||
* @param string $table
|
||||
* @param string $alias
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validateStatementColumnAssociation($table, $alias)
|
||||
{
|
||||
$tableName = $this->removeTablePrefix($this->clearTableAlias($table));
|
||||
|
||||
$statementAliasTableMap = $this->getStatementAliasTableMap();
|
||||
if (isset($statementAliasTableMap[$alias])) {
|
||||
return $statementAliasTableMap[$alias] === $tableName;
|
||||
return $statementAliasTableMap[$alias] === $table;
|
||||
}
|
||||
|
||||
$statementColumnTableMap = $this->getStatementColumnTableMap();
|
||||
if (isset($statementColumnTableMap[$alias])) {
|
||||
return $statementColumnTableMap[$alias] === $tableName;
|
||||
return $statementColumnTableMap[$alias] === $table;
|
||||
}
|
||||
|
||||
$prefixedAlias = $tableName . '.' . $alias;
|
||||
$prefixedAlias = $table . '.' . $alias;
|
||||
return isset($statementAliasTableMap[$prefixedAlias]) || isset($statementColumnTableMap[$prefixedAlias]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given column name or alias of the given table is a valid statement column
|
||||
*
|
||||
* @param array|string $table The table where to look for the column or alias
|
||||
* @param string $name The column name or alias to check
|
||||
* @param string $table The table where to look for the column or alias
|
||||
* @param string $name The column name or alias to check
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
|
@ -784,7 +777,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
&& $this->reassembleStatementColumnAlias($table, $name) === null)
|
||||
|| !$this->validateStatementColumnAssociation($table, $name)
|
||||
) {
|
||||
return parent::hasStatementColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name);
|
||||
return parent::hasStatementColumn($table, $name);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -793,8 +786,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
/**
|
||||
* Validate that the given column is a valid statement column and return it or the actual name if it's an alias
|
||||
*
|
||||
* @param array|string $table The table for which to require the column
|
||||
* @param string $name The name or alias of the column to validate
|
||||
* @param string $table The table for which to require the column
|
||||
* @param string $name The name or alias of the column to validate
|
||||
*
|
||||
* @return string The given column's name
|
||||
*
|
||||
|
@ -807,15 +800,11 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
} elseif (($alias = $this->reassembleStatementColumnAlias($table, $name)) !== null) {
|
||||
$column = $name;
|
||||
} else {
|
||||
return parent::requireStatementColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name);
|
||||
return parent::requireStatementColumn($table, $name);
|
||||
}
|
||||
|
||||
if (! $this->validateStatementColumnAssociation($table, $alias)) {
|
||||
throw new StatementException(
|
||||
'Statement column "%s" not found in table "%s"',
|
||||
$name,
|
||||
$this->removeTablePrefix($this->clearTableAlias($table))
|
||||
);
|
||||
throw new StatementException('Statement column "%s" not found in table "%s"', $name, $table);
|
||||
}
|
||||
|
||||
return $column;
|
||||
|
@ -829,7 +818,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
* The method is called with the same parameters but in reversed order.
|
||||
*
|
||||
* @param string $name The alias or column name to join into $target
|
||||
* @param array|string $target The table to join $name into
|
||||
* @param string $target The table to join $name into
|
||||
* @param RepositoryQUery $query The query to apply the JOIN-clause on
|
||||
*
|
||||
* @return string The resolved alias or $name
|
||||
|
@ -843,7 +832,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
throw new ProgrammingError(
|
||||
'Unable to find a valid table for column "%s" to join into "%s"',
|
||||
$name,
|
||||
$this->removeTablePrefix($this->clearTableAlias($target))
|
||||
$target
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -851,8 +840,10 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
$column = $name;
|
||||
}
|
||||
|
||||
$prefixedTableName = $this->prependTablePrefix($tableName);
|
||||
if ($query->getQuery()->hasJoinedTable($prefixedTableName)) {
|
||||
if (($joinIdentifier = $this->resolveTableAlias($tableName)) === null) {
|
||||
$joinIdentifier = $this->prependTablePrefix($tableName);
|
||||
}
|
||||
if ($query->getQuery()->hasJoinedTable($joinIdentifier)) {
|
||||
return $column;
|
||||
}
|
||||
|
||||
|
@ -861,7 +852,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
throw new ProgrammingError(
|
||||
'Unable to join table "%s" into "%s". Method "%s" not found',
|
||||
$tableName,
|
||||
$this->removeTablePrefix($this->clearTableAlias($target)),
|
||||
$target,
|
||||
$joinMethod
|
||||
);
|
||||
}
|
||||
|
|
|
@ -42,15 +42,6 @@ abstract class LdapRepository extends Repository
|
|||
'groupofuniquenames' => 'groupOfUniqueNames'
|
||||
);
|
||||
|
||||
/**
|
||||
* Object attributes whose value is not distinguished name
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $ambiguousAttributes = array(
|
||||
'posixGroup' => 'memberUid'
|
||||
);
|
||||
|
||||
/**
|
||||
* Create a new LDAP repository object
|
||||
*
|
||||
|
@ -79,15 +70,19 @@ abstract class LdapRepository extends Repository
|
|||
}
|
||||
|
||||
/**
|
||||
* Return whether the given object attribute's value is not a distinguished name
|
||||
* Return whether the given object DN is related to the given base DN
|
||||
*
|
||||
* @param string $objectClass
|
||||
* @param string $attributeName
|
||||
* Will use the current connection's root DN if $baseDn is not given.
|
||||
*
|
||||
* @param string $dn The object DN to check
|
||||
* @param string $baseDn The base DN to compare the object DN with
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAmbiguous($objectClass, $attributeName)
|
||||
protected function isRelatedDn($dn, $baseDn = null)
|
||||
{
|
||||
return isset($this->ambiguousAttributes[$objectClass][$attributeName]);
|
||||
$normalizedDn = strtolower(join(',', array_map('trim', explode(',', $dn))));
|
||||
$normalizedBaseDn = strtolower(join(',', array_map('trim', explode(',', $baseDn ?: $this->ds->getDn()))));
|
||||
return strpos($normalizedDn, $normalizedBaseDn) !== false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace Icinga\Repository;
|
|||
use DateTime;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Data\Filter\FilterExpression;
|
||||
use Icinga\Data\Selectable;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\Exception\QueryException;
|
||||
|
@ -52,6 +53,16 @@ abstract class Repository implements Selectable
|
|||
*/
|
||||
protected $baseTable;
|
||||
|
||||
/**
|
||||
* The virtual tables being provided
|
||||
*
|
||||
* This may be initialized by concrete repository implementations with an array
|
||||
* where a key is the name of a virtual table and its value the real table name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $virtualTables;
|
||||
|
||||
/**
|
||||
* The query columns being provided
|
||||
*
|
||||
|
@ -255,6 +266,32 @@ abstract class Repository implements Selectable
|
|||
return $this->baseTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the virtual tables being provided
|
||||
*
|
||||
* Calls $this->initializeVirtualTables() in case $this->virtualTables is null.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getVirtualTables()
|
||||
{
|
||||
if ($this->virtualTables === null) {
|
||||
$this->virtualTables = $this->initializeVirtualTables();
|
||||
}
|
||||
|
||||
return $this->virtualTables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite this in your repository implementation in case you need to initialize the virtual tables lazily
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function initializeVirtualTables()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query columns being provided
|
||||
*
|
||||
|
@ -581,7 +618,7 @@ abstract class Repository implements Selectable
|
|||
{
|
||||
$converter = $this->getConverter($table, $name, 'persist', $query);
|
||||
if ($converter !== null) {
|
||||
$value = $this->$converter($value);
|
||||
$value = $this->$converter($value, $name, $table, $query);
|
||||
}
|
||||
|
||||
return $value;
|
||||
|
@ -603,7 +640,7 @@ abstract class Repository implements Selectable
|
|||
{
|
||||
$converter = $this->getConverter($table, $name, 'retrieve', $query);
|
||||
if ($converter !== null) {
|
||||
$value = $this->$converter($value);
|
||||
$value = $this->$converter($value, $name, $table, $query);
|
||||
}
|
||||
|
||||
return $value;
|
||||
|
@ -792,7 +829,7 @@ abstract class Repository implements Selectable
|
|||
}
|
||||
|
||||
/**
|
||||
* Validate that the requested table exists
|
||||
* Validate that the requested table exists and resolve it's real name if necessary
|
||||
*
|
||||
* @param string $table The table to validate
|
||||
* @param RepositoryQuery $query An optional query to pass as context
|
||||
|
@ -809,6 +846,11 @@ abstract class Repository implements Selectable
|
|||
throw new ProgrammingError('Table "%s" not found', $table);
|
||||
}
|
||||
|
||||
$virtualTables = $this->getVirtualTables();
|
||||
if (isset($virtualTables[$table])) {
|
||||
$table = $virtualTables[$table];
|
||||
}
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
|
@ -831,8 +873,8 @@ abstract class Repository implements Selectable
|
|||
|
||||
if ($filter->isExpression()) {
|
||||
$column = $filter->getColumn();
|
||||
$filter->setColumn($this->requireFilterColumn($table, $column, $query));
|
||||
$filter->setExpression($this->persistColumn($table, $column, $filter->getExpression()));
|
||||
$filter->setColumn($this->requireFilterColumn($table, $column, $query, $filter));
|
||||
$filter->setExpression($this->persistColumn($table, $column, $filter->getExpression(), $query));
|
||||
} elseif ($filter->isChain()) {
|
||||
foreach ($filter->filters() as $chainOrExpression) {
|
||||
$this->requireFilter($table, $chainOrExpression, $query, false);
|
||||
|
@ -862,7 +904,7 @@ abstract class Repository implements Selectable
|
|||
$columns = array();
|
||||
foreach ($queryColumns[$table] as $alias => $column) {
|
||||
if (! in_array(is_string($alias) ? $alias : $column, $blacklist)) {
|
||||
$columns[$alias] = $column;
|
||||
$columns[$alias] = $this->resolveQueryColumnAlias($table, $alias);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1008,12 +1050,13 @@ abstract class Repository implements Selectable
|
|||
* @param string $table The table where to look for the column or alias
|
||||
* @param string $name The name or alias of the column to validate
|
||||
* @param RepositoryQuery $query An optional query to pass as context (unused by the base implementation)
|
||||
* @param FilterExpression $filter An optional filter to pass as context (unused by the base implementation)
|
||||
*
|
||||
* @return string The given column's name
|
||||
*
|
||||
* @throws QueryException In case the given column is not a valid filter column
|
||||
*/
|
||||
public function requireFilterColumn($table, $name, RepositoryQuery $query = null)
|
||||
public function requireFilterColumn($table, $name, RepositoryQuery $query = null, FilterExpression $filter = null)
|
||||
{
|
||||
if (($column = $this->resolveQueryColumnAlias($table, $name)) !== null) {
|
||||
$alias = $name;
|
||||
|
|
|
@ -47,6 +47,13 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
|||
*/
|
||||
protected $iterator;
|
||||
|
||||
/**
|
||||
* This query's custom aliases
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $customAliases;
|
||||
|
||||
/**
|
||||
* Create a new repository query
|
||||
*
|
||||
|
@ -79,8 +86,8 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
|||
*/
|
||||
public function from($target, array $columns = null)
|
||||
{
|
||||
$target = $this->repository->requireTable($target, $this);
|
||||
$this->query = $this->repository->getDataSource()->select()->from($target);
|
||||
$this->query = $this->repository->getDataSource()->select();
|
||||
$this->query->from($this->repository->requireTable($target, $this));
|
||||
$this->query->columns($this->prepareQueryColumns($target, $columns));
|
||||
$this->target = $target;
|
||||
return $this;
|
||||
|
@ -123,6 +130,7 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
|||
*/
|
||||
protected function prepareQueryColumns($target, array $desiredColumns = null)
|
||||
{
|
||||
$this->customAliases = array();
|
||||
if (empty($desiredColumns)) {
|
||||
$columns = $this->repository->requireAllQueryColumns($target);
|
||||
} else {
|
||||
|
@ -130,9 +138,15 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
|||
foreach ($desiredColumns as $customAlias => $columnAlias) {
|
||||
$resolvedColumn = $this->repository->requireQueryColumn($target, $columnAlias, $this);
|
||||
if ($resolvedColumn !== $columnAlias) {
|
||||
$columns[is_string($customAlias) ? $customAlias : $columnAlias] = $resolvedColumn;
|
||||
if (is_string($customAlias)) {
|
||||
$columns[$customAlias] = $resolvedColumn;
|
||||
$this->customAliases[$customAlias] = $columnAlias;
|
||||
} else {
|
||||
$columns[$columnAlias] = $resolvedColumn;
|
||||
}
|
||||
} elseif (is_string($customAlias)) {
|
||||
$columns[$customAlias] = $columnAlias;
|
||||
$this->customAliases[$customAlias] = $columnAlias;
|
||||
} else {
|
||||
$columns[] = $columnAlias;
|
||||
}
|
||||
|
@ -142,6 +156,24 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
|||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the native column alias for the given custom alias
|
||||
*
|
||||
* If no custom alias is found with the given name, it is returned unchanged.
|
||||
*
|
||||
* @param string $customAlias
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getNativeAlias($customAlias)
|
||||
{
|
||||
if (isset($this->customAliases[$customAlias])) {
|
||||
return $this->customAliases[$customAlias];
|
||||
}
|
||||
|
||||
return $customAlias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this query's available filter columns with their optional label as key
|
||||
*
|
||||
|
@ -174,10 +206,7 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
|||
*/
|
||||
public function where($column, $value = null)
|
||||
{
|
||||
$this->query->where(
|
||||
$this->repository->requireFilterColumn($this->target, $column, $this),
|
||||
$this->repository->persistColumn($this->target, $column, $value, $this)
|
||||
);
|
||||
$this->addFilter(Filter::where($column, $value));
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -466,7 +495,7 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
|||
$result = $this->query->fetchOne();
|
||||
if ($result !== false && $this->repository->providesValueConversion($this->target)) {
|
||||
$columns = $this->getColumns();
|
||||
$column = isset($columns[0]) ? $columns[0] : key($columns);
|
||||
$column = isset($columns[0]) ? $columns[0] : $this->getNativeAlias(key($columns));
|
||||
return $this->repository->retrieveColumn($this->target, $column, $result, $this);
|
||||
}
|
||||
|
||||
|
@ -491,7 +520,12 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
|||
$alias = $column;
|
||||
}
|
||||
|
||||
$result->$alias = $this->repository->retrieveColumn($this->target, $alias, $result->$alias, $this);
|
||||
$result->$alias = $this->repository->retrieveColumn(
|
||||
$this->target,
|
||||
$this->getNativeAlias($alias),
|
||||
$result->$alias,
|
||||
$this
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -513,7 +547,7 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
|||
if (! empty($results) && $this->repository->providesValueConversion($this->target)) {
|
||||
$columns = $this->getColumns();
|
||||
$aliases = array_keys($columns);
|
||||
$column = is_int($aliases[0]) ? $columns[0] : $aliases[0];
|
||||
$column = is_int($aliases[0]) ? $columns[0] : $this->getNativeAlias($aliases[0]);
|
||||
if ($this->repository->providesValueConversion($this->target, $column)) {
|
||||
foreach ($results as & $value) {
|
||||
$value = $this->repository->retrieveColumn($this->target, $column, $value, $this);
|
||||
|
@ -541,8 +575,11 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
|||
if (! empty($results) && $this->repository->providesValueConversion($this->target)) {
|
||||
$columns = $this->getColumns();
|
||||
$aliases = array_keys($columns);
|
||||
$colOne = $aliases[0] !== 0 ? $aliases[0] : $columns[0];
|
||||
$colTwo = count($aliases) < 2 ? $colOne : ($aliases[1] !== 1 ? $aliases[1] : $columns[1]);
|
||||
$colOne = $aliases[0] !== 0 ? $this->getNativeAlias($aliases[0]) : $columns[0];
|
||||
$colTwo = count($aliases) < 2 ? $colOne : (
|
||||
$aliases[1] !== 1 ? $this->getNativeAlias($aliases[1]) : $columns[1]
|
||||
);
|
||||
|
||||
if (
|
||||
$this->repository->providesValueConversion($this->target, $colOne)
|
||||
|| $this->repository->providesValueConversion($this->target, $colTwo)
|
||||
|
@ -587,21 +624,28 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
|||
$alias = $column;
|
||||
}
|
||||
|
||||
$row->$alias = $this->repository->retrieveColumn($this->target, $alias, $row->$alias, $this);
|
||||
$row->$alias = $this->repository->retrieveColumn(
|
||||
$this->target,
|
||||
$this->getNativeAlias($alias),
|
||||
$row->$alias,
|
||||
$this
|
||||
);
|
||||
}
|
||||
|
||||
foreach (($this->getOrder() ?: array()) as $rule) {
|
||||
$nativeAlias = $this->getNativeAlias($rule[0]);
|
||||
if (! array_key_exists($rule[0], $flippedColumns) && property_exists($row, $rule[0])) {
|
||||
if ($this->repository->providesValueConversion($this->target, $rule[0])) {
|
||||
if ($this->repository->providesValueConversion($this->target, $nativeAlias)) {
|
||||
$updateOrder = true;
|
||||
$row->{$rule[0]} = $this->repository->retrieveColumn(
|
||||
$this->target,
|
||||
$rule[0],
|
||||
$row->{$rule[0]}
|
||||
$nativeAlias,
|
||||
$row->{$rule[0]},
|
||||
$this
|
||||
);
|
||||
}
|
||||
} elseif (array_key_exists($rule[0], $flippedColumns)) {
|
||||
if ($this->repository->providesValueConversion($this->target, $rule[0])) {
|
||||
if ($this->repository->providesValueConversion($this->target, $nativeAlias)) {
|
||||
$updateOrder = true;
|
||||
}
|
||||
}
|
||||
|
@ -677,7 +721,12 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
|||
$alias = $column;
|
||||
}
|
||||
|
||||
$row->$alias = $this->repository->retrieveColumn($this->target, $alias, $row->$alias, $this);
|
||||
$row->$alias = $this->repository->retrieveColumn(
|
||||
$this->target,
|
||||
$this->getNativeAlias($alias),
|
||||
$row->$alias,
|
||||
$this
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -186,6 +186,7 @@ class Controller extends ModuleActionController
|
|||
* @param Filterable $filterable The filterable to create a filter editor for
|
||||
* @param array $filterColumns The filter columns to offer to the user
|
||||
* @param array $searchColumns The search columns to utilize for quick searches
|
||||
* @param array $preserveParams The url parameters to preserve
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
|
@ -194,25 +195,27 @@ class Controller extends ModuleActionController
|
|||
protected function setupFilterControl(
|
||||
Filterable $filterable,
|
||||
array $filterColumns = null,
|
||||
array $searchColumns = null
|
||||
array $searchColumns = null,
|
||||
array $preserveParams = null
|
||||
) {
|
||||
$editor = Widget::create('filterEditor')
|
||||
$defaultPreservedParams = array(
|
||||
'limit', // setupPaginationControl()
|
||||
'sort', // setupSortControl()
|
||||
'dir', // setupSortControl()
|
||||
'backend', // Framework
|
||||
'view', // Framework
|
||||
'_dev' // Framework
|
||||
);
|
||||
|
||||
$editor = Widget::create('filterEditor');
|
||||
call_user_func_array(
|
||||
array($editor, 'preserveParams'),
|
||||
array_merge($defaultPreservedParams, $preserveParams ?: array())
|
||||
);
|
||||
|
||||
$editor
|
||||
->setQuery($filterable)
|
||||
->preserveParams(
|
||||
'limit',
|
||||
'sort',
|
||||
'dir',
|
||||
'format',
|
||||
'view',
|
||||
'user',
|
||||
'group',
|
||||
'backend',
|
||||
'stateType',
|
||||
'addColumns',
|
||||
'problems',
|
||||
'_dev'
|
||||
)
|
||||
->ignoreParams('page')
|
||||
->ignoreParams('page') // setupPaginationControl()
|
||||
->setColumns($filterColumns)
|
||||
->setSearchColumns($searchColumns)
|
||||
->handleRequest($this->getRequest());
|
||||
|
|
|
@ -171,7 +171,7 @@ class UrlParams
|
|||
|
||||
protected function urlEncode($value)
|
||||
{
|
||||
return rawurlencode((string) $value);
|
||||
return rawurlencode($value instanceof Url ? $value->getAbsoluteUrl() : (string) $value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -574,7 +574,12 @@ class ListController extends Controller
|
|||
*/
|
||||
protected function filterQuery(DataView $dataView)
|
||||
{
|
||||
$this->setupFilterControl($dataView);
|
||||
$this->setupFilterControl($dataView, null, null, array(
|
||||
'format', // handleFormatRequest()
|
||||
'stateType', // hostsAction() and servicesAction()
|
||||
'addColumns', // addColumns()
|
||||
'problems' // servicegridAction()
|
||||
));
|
||||
$this->handleFormatRequest($dataView);
|
||||
return $dataView;
|
||||
}
|
||||
|
|
|
@ -73,8 +73,8 @@ $stateBadges
|
|||
'host_state' => 2,
|
||||
'host_handled' => 1
|
||||
),
|
||||
'List %u host that is currently in state UNREACHABLE (Acknowledged) in the host group "%s"',
|
||||
'List %u hosts which are currently in state UNREACHABLE (Acknowledged) in the host group "%s"',
|
||||
'List %u host that is currently in state UNREACHABLE (Acknowledged)',
|
||||
'List %u hosts which are currently in state UNREACHABLE (Acknowledged)',
|
||||
array($stats->hosts_unreachable_handled)
|
||||
)
|
||||
->add(
|
||||
|
@ -83,8 +83,8 @@ $stateBadges
|
|||
array(
|
||||
'host_state' => 99
|
||||
),
|
||||
'List %u host that is currently in state UNREACHABLE (Acknowledged)',
|
||||
'List %u hosts which are currently in state UNREACHABLE (Acknowledged)',
|
||||
'List %u host that is currently in state PENDING',
|
||||
'List %u hosts which are currently in state PENDING',
|
||||
array($stats->hosts_pending)
|
||||
);
|
||||
echo $stateBadges->render();
|
||||
|
|
|
@ -75,7 +75,7 @@ class HostserviceproblemsummaryQuery extends IdoQuery
|
|||
's.service_object_id = so.object_id AND so.is_active = 1',
|
||||
array()
|
||||
);
|
||||
$this->select->group(array('so.object_id'));
|
||||
$this->select->group('so.name1');
|
||||
$this->joinedVirtualTables['services'] = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -55,14 +55,14 @@ abstract class IdoQuery extends DbQuery
|
|||
protected $prefix;
|
||||
|
||||
/**
|
||||
* An array to map aliases to table names
|
||||
* An array to map aliases to column names
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $idxAliasColumn;
|
||||
|
||||
/**
|
||||
* An array to map aliases to column names
|
||||
* An array to map aliases to table names
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
|
@ -400,14 +400,14 @@ abstract class IdoQuery extends DbQuery
|
|||
protected static $idoVersion;
|
||||
|
||||
/**
|
||||
* List of columns where the COLLATE SQL-instruction has been removed
|
||||
* List of column aliases mapped to their table where the COLLATE SQL-instruction has been removed
|
||||
*
|
||||
* This list is being populated in case of a PostgreSQL backend only,
|
||||
* to ensure case-insensitive string comparison in WHERE clauses.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $columnsWithoutCollation = array();
|
||||
protected $caseInsensitiveColumns;
|
||||
|
||||
/**
|
||||
* Return true when the column is an aggregate column
|
||||
|
@ -421,26 +421,32 @@ abstract class IdoQuery extends DbQuery
|
|||
}
|
||||
|
||||
/**
|
||||
* Order the result by the given column
|
||||
* Order the result by the given alias
|
||||
*
|
||||
* @param string $columnOrAlias The column or column alias to order by
|
||||
* @param int $dir The sort direction or null to use default direction
|
||||
* @param string $alias The column alias to order by
|
||||
* @param int $dir The sort direction or null to use the default direction
|
||||
*
|
||||
* @return $this Fluent interface
|
||||
* @return $this
|
||||
*/
|
||||
public function order($columnOrAlias, $dir = null)
|
||||
public function order($alias, $dir = null)
|
||||
{
|
||||
$this->requireColumn($columnOrAlias);
|
||||
$this->orderColumns[$columnOrAlias] = $columnOrAlias;
|
||||
if ($this->isCustomvar($columnOrAlias)) {
|
||||
$columnOrAlias = $this->getCustomvarColumnName($columnOrAlias);
|
||||
} elseif ($this->hasAliasName($columnOrAlias)) {
|
||||
$columnOrAlias = $this->aliasToColumnName($columnOrAlias);
|
||||
$this->requireColumn($alias);
|
||||
|
||||
if ($this->isCustomvar($alias)) {
|
||||
$column = $this->getCustomvarColumnName($alias);
|
||||
} elseif ($this->hasAliasName($alias)) {
|
||||
$column = $this->aliasToColumnName($alias);
|
||||
$table = $this->aliasToTableName($alias);
|
||||
if (isset($this->caseInsensitiveColumns[$table][$alias])) {
|
||||
$column = 'LOWER(' . $column . ')';
|
||||
}
|
||||
} else {
|
||||
Logger::info('Can\'t order by column ' . $columnOrAlias);
|
||||
Logger::info('Can\'t order by column ' . $alias);
|
||||
return $this;
|
||||
}
|
||||
return parent::order($columnOrAlias, $dir);
|
||||
|
||||
$this->orderColumns[$alias] = $alias;
|
||||
return parent::order($column, $dir);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -485,22 +491,25 @@ abstract class IdoQuery extends DbQuery
|
|||
if ($filter->getExpression() === '*') {
|
||||
return; // Wildcard only filters are ignored so stop early here to avoid joining a table for nothing
|
||||
}
|
||||
|
||||
$alias = $filter->getColumn();
|
||||
$this->requireColumn($alias);
|
||||
|
||||
if ($this->isCustomvar($alias)) {
|
||||
$column = $this->getCustomvarColumnName($alias);
|
||||
} else {
|
||||
$column = $this->aliasToColumnName($alias);
|
||||
}
|
||||
if (isset($this->columnsWithoutCollation[$alias])) {
|
||||
$expression = $filter->getExpression();
|
||||
if (is_array($expression)) {
|
||||
$filter->setExpression(array_map('strtolower', $expression));
|
||||
} else {
|
||||
$filter->setExpression(strtolower($expression));
|
||||
|
||||
if (isset($this->caseInsensitiveColumns[$this->aliasToTableName($alias)][$alias])) {
|
||||
$column = 'LOWER(' . $column . ')';
|
||||
$expression = $filter->getExpression();
|
||||
if (is_array($expression)) {
|
||||
$filter->setExpression(array_map('strtolower', $expression));
|
||||
} else {
|
||||
$filter->setExpression(strtolower($expression));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$filter->setColumn($column);
|
||||
} else {
|
||||
foreach ($filter->filters() as $filter) {
|
||||
|
@ -524,6 +533,7 @@ abstract class IdoQuery extends DbQuery
|
|||
if ($value === '*') {
|
||||
return $this; // Wildcard only filters are ignored so stop early here to avoid joining a table for nothing
|
||||
}
|
||||
|
||||
$this->requireColumn($condition);
|
||||
$col = $this->getMappedField($condition);
|
||||
if ($col === null) {
|
||||
|
@ -551,27 +561,37 @@ abstract class IdoQuery extends DbQuery
|
|||
}
|
||||
|
||||
/**
|
||||
* Return whether the given alias or column name provides case insensitive value comparison
|
||||
* Return whether the given alias provides case insensitive value comparison
|
||||
*
|
||||
* @param string $aliasOrColumn
|
||||
* @param string $alias
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isCaseInsensitive($aliasOrColumn)
|
||||
public function isCaseInsensitive($alias)
|
||||
{
|
||||
if ($this->isCustomVar($aliasOrColumn)) {
|
||||
if ($this->isCustomVar($alias)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$column = $this->getMappedField($aliasOrColumn) ?: $aliasOrColumn;
|
||||
$column = $this->getMappedField($alias);
|
||||
if (! $column) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! empty($this->columnsWithoutCollation)) {
|
||||
return in_array($column, $this->columnsWithoutCollation) || strpos($column, 'LOWER') !== 0;
|
||||
if (empty($this->caseInsensitiveColumns)) {
|
||||
return preg_match('/ COLLATE .+$/', $column) === 1;
|
||||
}
|
||||
return preg_match('/ COLLATE .+$/', $column) === 1;
|
||||
|
||||
if (strpos($column, 'LOWER') === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$table = $this->aliasToTableName($alias);
|
||||
if (! $table) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isset($this->caseInsensitiveColumns[$table][$alias]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -601,12 +621,14 @@ abstract class IdoQuery extends DbQuery
|
|||
{
|
||||
$this->customVarsJoinTemplate =
|
||||
'%1$s = %2$s.object_id AND LOWER(%2$s.varname) = %3$s';
|
||||
foreach ($this->columnMap as $table => &$columns) {
|
||||
foreach ($columns as $alias => &$column) {
|
||||
if (false !== $pos = strpos($column, ' COLLATE')) {
|
||||
$column = 'LOWER(' . substr($column, 0, $pos) . ')';
|
||||
$this->columnsWithoutCollation[$alias] = true;
|
||||
foreach ($this->columnMap as $table => & $columns) {
|
||||
foreach ($columns as $alias => & $column) {
|
||||
// Using a regex here because COLLATE may occur anywhere in the string
|
||||
$column = preg_replace('/ COLLATE .+$/', '', $column, -1, $count);
|
||||
if ($count > 0) {
|
||||
$this->caseInsensitiveColumns[$table][$alias] = true;
|
||||
}
|
||||
|
||||
$column = preg_replace(
|
||||
'/inet_aton\(([[:word:].]+)\)/i',
|
||||
'(CASE WHEN $1 ~ \'(?:[0-9]{1,3}\\\\.){3}[0-9]{1,3}\' THEN $1::inet - \'0.0.0.0\' ELSE NULL END)',
|
||||
|
|
|
@ -18,6 +18,7 @@ class HostStatus extends DataView
|
|||
'host_address',
|
||||
'host_address6',
|
||||
'host_state',
|
||||
'host_hard_state',
|
||||
'host_state_type',
|
||||
'host_handled',
|
||||
'host_unhandled',
|
||||
|
|
|
@ -15,6 +15,7 @@ class ServiceStatus extends DataView
|
|||
'host_name',
|
||||
'host_display_name',
|
||||
'host_state',
|
||||
'host_hard_state',
|
||||
'host_state_type',
|
||||
'host_last_state_change',
|
||||
'host_address',
|
||||
|
@ -24,6 +25,7 @@ class ServiceStatus extends DataView
|
|||
'service_description',
|
||||
'service_display_name',
|
||||
'service_state',
|
||||
'service_hard_state',
|
||||
'service_in_downtime',
|
||||
'service_acknowledged',
|
||||
'service_handled',
|
||||
|
|
|
@ -44,41 +44,15 @@ class MacroTest extends BaseTestCase
|
|||
$this->assertEquals(Macro::resolveMacros('$service.description$', $svcMock), $svcMock->service_description);
|
||||
}
|
||||
|
||||
public function testCustomvars()
|
||||
{
|
||||
$objectMock = Mockery::mock('object');
|
||||
$objectMock->customvars = array(
|
||||
'customvar' => 'test'
|
||||
);
|
||||
|
||||
$this->assertEquals(Macro::resolveMacros('$CUSTOMVAR$', $objectMock), $objectMock->customvars['customvar']);
|
||||
}
|
||||
|
||||
public function testFaultyMacros()
|
||||
{
|
||||
$hostMock = Mockery::mock('host');
|
||||
$hostMock->host_name = 'test';
|
||||
$hostMock->customvars = array(
|
||||
'host' => 'te',
|
||||
'name' => 'st'
|
||||
);
|
||||
$hostMock->host = 'te';
|
||||
|
||||
$this->assertEquals(
|
||||
Macro::resolveMacros('$$HOSTNAME$ $ HOSTNAME$ $HOST$NAME$', $hostMock),
|
||||
'$test $ HOSTNAME$ teNAME$'
|
||||
);
|
||||
}
|
||||
|
||||
public function testMacrosWithSpecialCharacters()
|
||||
{
|
||||
$objectMock = Mockery::mock('object');
|
||||
$objectMock->customvars = array(
|
||||
'v€ry_sp3c|@l' => 'not too special!'
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
Macro::resolveMacros('$V€RY_SP3C|@L$', $objectMock),
|
||||
$objectMock->customvars['v€ry_sp3c|@l']
|
||||
'$test $ HOSTNAME$ teNAME$',
|
||||
Macro::resolveMacros('$$HOSTNAME$ $ HOSTNAME$ $host$NAME$', $hostMock)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -233,83 +233,6 @@ div.content.users {
|
|||
}
|
||||
}
|
||||
|
||||
div.content.memberships {
|
||||
table.membership-list {
|
||||
th.membership-cancel {
|
||||
width: 8em;
|
||||
padding-right: 0.5em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
td.membership-cancel {
|
||||
text-align: right;
|
||||
|
||||
form button.link-like {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
a.membership-create {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
div.content.groups {
|
||||
table.group-list {
|
||||
th.group-remove {
|
||||
width: 8em;
|
||||
padding-right: 0.5em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
td.group-remove {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
a.group-add {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
div.content.members {
|
||||
table.member-list {
|
||||
th.member-remove {
|
||||
width: 8em;
|
||||
padding-right: 0.5em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
td.member-remove {
|
||||
text-align: right;
|
||||
|
||||
form button.link-like {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
a.member-add {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
form.backend-selection {
|
||||
float: right;
|
||||
|
||||
|
|
Loading…
Reference in New Issue