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
|
dn: cn=Users,ou=groups,dc=icinga,dc=org
|
||||||
objectClass: groupOfUniqueNames
|
objectClass: groupOfUniqueNames
|
||||||
cn: Users
|
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=Jane Smith,ou=people,dc=icinga,dc=org
|
||||||
uniqueMember: cn=John Q. Public,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
|
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':
|
icingaweb2::config::general { 'authentication':
|
||||||
source => $name,
|
source => $name,
|
||||||
|
replace => false,
|
||||||
}
|
}
|
||||||
|
|
||||||
icingaweb2::config::general { [ 'config', 'resources', 'roles' ]:
|
icingaweb2::config::general { [ 'config', 'resources', 'roles' ]:
|
||||||
|
|
|
@ -44,7 +44,7 @@ class GroupController extends AuthBackendController
|
||||||
'backend',
|
'backend',
|
||||||
array(
|
array(
|
||||||
'autosubmit' => true,
|
'autosubmit' => true,
|
||||||
'label' => $this->translate('Usergroup Backend'),
|
'label' => $this->translate('User Group Backend'),
|
||||||
'multiOptions' => array_combine($backendNames, $backendNames),
|
'multiOptions' => array_combine($backendNames, $backendNames),
|
||||||
'value' => $this->params->get('backend')
|
'value' => $this->params->get('backend')
|
||||||
)
|
)
|
||||||
|
@ -66,7 +66,7 @@ class GroupController extends AuthBackendController
|
||||||
$this->setupLimitControl();
|
$this->setupLimitControl();
|
||||||
$this->setupSortControl(
|
$this->setupSortControl(
|
||||||
array(
|
array(
|
||||||
'group_name' => $this->translate('Group'),
|
'group_name' => $this->translate('User Group'),
|
||||||
'created_at' => $this->translate('Created at'),
|
'created_at' => $this->translate('Created at'),
|
||||||
'last_modified' => $this->translate('Last modified')
|
'last_modified' => $this->translate('Last modified')
|
||||||
),
|
),
|
||||||
|
@ -97,7 +97,7 @@ class GroupController extends AuthBackendController
|
||||||
->from('group_membership', array('user_name'))
|
->from('group_membership', array('user_name'))
|
||||||
->where('group_name', $groupName);
|
->where('group_name', $groupName);
|
||||||
|
|
||||||
$this->setupFilterControl($members, null, array('user'));
|
$this->setupFilterControl($members, null, array('user'), array('group'));
|
||||||
$this->setupPaginationControl($members);
|
$this->setupPaginationControl($members);
|
||||||
$this->setupLimitControl();
|
$this->setupLimitControl();
|
||||||
$this->setupSortControl(
|
$this->setupSortControl(
|
||||||
|
@ -134,7 +134,7 @@ class GroupController extends AuthBackendController
|
||||||
$removeForm->addElement('button', 'btn_submit', array(
|
$removeForm->addElement('button', 'btn_submit', array(
|
||||||
'escape' => false,
|
'escape' => false,
|
||||||
'type' => 'submit',
|
'type' => 'submit',
|
||||||
'class' => 'link-like spinner',
|
'class' => 'link-button spinner',
|
||||||
'value' => 'btn_submit',
|
'value' => 'btn_submit',
|
||||||
'decorators' => array('ViewHelper'),
|
'decorators' => array('ViewHelper'),
|
||||||
'label' => $this->view->icon('trash'),
|
'label' => $this->view->icon('trash'),
|
||||||
|
|
|
@ -44,7 +44,7 @@ class UserController extends AuthBackendController
|
||||||
'backend',
|
'backend',
|
||||||
array(
|
array(
|
||||||
'autosubmit' => true,
|
'autosubmit' => true,
|
||||||
'label' => $this->translate('Authentication Backend'),
|
'label' => $this->translate('User Backend'),
|
||||||
'multiOptions' => array_combine($backendNames, $backendNames),
|
'multiOptions' => array_combine($backendNames, $backendNames),
|
||||||
'value' => $this->params->get('backend')
|
'value' => $this->params->get('backend')
|
||||||
)
|
)
|
||||||
|
@ -99,7 +99,8 @@ class UserController extends AuthBackendController
|
||||||
$this->setupFilterControl(
|
$this->setupFilterControl(
|
||||||
$memberships,
|
$memberships,
|
||||||
array('group_name' => t('User Group')),
|
array('group_name' => t('User Group')),
|
||||||
array('group_name')
|
array('group'),
|
||||||
|
array('user')
|
||||||
);
|
);
|
||||||
$this->setupPaginationControl($memberships);
|
$this->setupPaginationControl($memberships);
|
||||||
$this->setupLimitControl();
|
$this->setupLimitControl();
|
||||||
|
@ -140,7 +141,7 @@ class UserController extends AuthBackendController
|
||||||
$removeForm->addElement('button', 'btn_submit', array(
|
$removeForm->addElement('button', 'btn_submit', array(
|
||||||
'escape' => false,
|
'escape' => false,
|
||||||
'type' => 'submit',
|
'type' => 'submit',
|
||||||
'class' => 'link-like spinner',
|
'class' => 'link-button spinner',
|
||||||
'value' => 'btn_submit',
|
'value' => 'btn_submit',
|
||||||
'decorators' => array('ViewHelper'),
|
'decorators' => array('ViewHelper'),
|
||||||
'label' => $this->view->icon('trash'),
|
'label' => $this->view->icon('trash'),
|
||||||
|
@ -259,6 +260,7 @@ class UserController extends AuthBackendController
|
||||||
$alreadySeen[$groupName] = null;
|
$alreadySeen[$groupName] = null;
|
||||||
$groups[] = (object) array(
|
$groups[] = (object) array(
|
||||||
'group_name' => $groupName,
|
'group_name' => $groupName,
|
||||||
|
'group' => $groupName,
|
||||||
'backend' => $backend
|
'backend' => $backend
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ class LoginForm extends Form
|
||||||
if ($this->created) {
|
if ($this->created) {
|
||||||
$redirect = $this->getElement('redirect')->getValue();
|
$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;
|
$redirect = static::REDIRECT_URL;
|
||||||
}
|
}
|
||||||
return Url::fromPath($redirect);
|
return Url::fromPath($redirect);
|
||||||
|
|
|
@ -4,30 +4,24 @@ use Icinga\Data\Extensible;
|
||||||
use Icinga\Data\Reducible;
|
use Icinga\Data\Reducible;
|
||||||
|
|
||||||
if (! $this->compact): ?>
|
if (! $this->compact): ?>
|
||||||
<div class="controls">
|
<div class="controls separated dont-print">
|
||||||
<?= $this->tabs; ?>
|
<?= $tabs; ?>
|
||||||
<div class="grid dont-print">
|
<div class="grid">
|
||||||
<div class="col-1-3 text-left">
|
|
||||||
<?= $this->limiter ?>
|
<?= $this->limiter ?>
|
||||||
</div>
|
|
||||||
<div class="col-1-3">
|
|
||||||
<?= $this->paginator ?>
|
<?= $this->paginator ?>
|
||||||
</div>
|
|
||||||
<div class="col-1-3 text-right">
|
|
||||||
<?= $this->sortBox ?>
|
<?= $this->sortBox ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<?= $this->backendSelection; ?>
|
<?= $this->backendSelection; ?>
|
||||||
<?= $this->filterEditor; ?>
|
<?= $this->filterEditor; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
<div class="content groups">
|
<div class="content">
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
if (! isset($backend)) {
|
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;
|
return;
|
||||||
} else {
|
} else {
|
||||||
$extensible = $this->hasPermission('config/authentication/groups/add') && $backend instanceof Extensible;
|
$extensible = $this->hasPermission('config/authentication/groups/add') && $backend instanceof Extensible;
|
||||||
|
@ -43,38 +37,46 @@ if (! isset($backend)) {
|
||||||
array(
|
array(
|
||||||
'class' => 'button-link',
|
'class' => 'button-link',
|
||||||
'data-base-target' => '_next',
|
'data-base-target' => '_next',
|
||||||
'icon' => 'plus',
|
'icon' => 'plus'
|
||||||
'title' => $this->translate('Create a new user group')
|
|
||||||
)
|
)
|
||||||
) ?>
|
) ?>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php
|
<?php if (! $groups->hasResult()): ?>
|
||||||
// @TODO(el): Remove $firstRow stuff
|
<p><?= $this->translate('No user groups found matching the filter'); ?></p>
|
||||||
$firstRow = true;
|
</div>
|
||||||
foreach ($groups as $group): ?>
|
<?php endif ?>
|
||||||
<?php if ($firstRow): ?>
|
|
||||||
<?php $firstRow = false; ?>
|
|
||||||
<table data-base-target="_next" class="action-table listing-table">
|
<table data-base-target="_next" class="action-table listing-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="group-name"><?= $this->translate('Group'); ?></th>
|
<th><?= $this->translate('User Group'); ?></th>
|
||||||
<?php if ($reducible): ?>
|
<?php if ($reducible): ?>
|
||||||
<th class="group-remove"><?= $this->translate('Remove'); ?></th>
|
<th><?= $this->translate('Remove'); ?></th>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php endif ?>
|
<?php foreach ($groups as $group): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="group-name"><?= $this->qlink($group->group_name, 'group/show', array(
|
<td>
|
||||||
|
<?= $this->qlink(
|
||||||
|
$group->group_name,
|
||||||
|
'group/show',
|
||||||
|
array(
|
||||||
'backend' => $backend->getName(),
|
'backend' => $backend->getName(),
|
||||||
'group' => $group->group_name
|
'group' => $group->group_name
|
||||||
), array(
|
),
|
||||||
'title' => sprintf($this->translate('Show detailed information for group %s'), $group->group_name)
|
array(
|
||||||
)); ?></td>
|
'title' => sprintf(
|
||||||
|
$this->translate('Show detailed information for user group %s'),
|
||||||
|
$group->group_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
); ?>
|
||||||
|
</td>
|
||||||
<?php if ($reducible): ?>
|
<?php if ($reducible): ?>
|
||||||
<td class="group-remove">
|
<td class="icon-col">
|
||||||
<?= $this->qlink(
|
<?= $this->qlink(
|
||||||
null,
|
null,
|
||||||
'group/remove',
|
'group/remove',
|
||||||
|
@ -83,7 +85,7 @@ foreach ($groups as $group): ?>
|
||||||
'group' => $group->group_name
|
'group' => $group->group_name
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'title' => sprintf($this->translate('Remove group %s'), $group->group_name),
|
'title' => sprintf($this->translate('Remove user group %s'), $group->group_name),
|
||||||
'icon' => 'trash'
|
'icon' => 'trash'
|
||||||
)
|
)
|
||||||
); ?>
|
); ?>
|
||||||
|
@ -91,10 +93,6 @@ foreach ($groups as $group): ?>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
<?php if ($groups->hasResult()): ?>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<?php else: ?>
|
|
||||||
<p><?= $this->translate('No groups found matching the filter'); ?></p>
|
|
||||||
<?php endif ?>
|
|
||||||
</div>
|
</div>
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
use Icinga\Data\Extensible;
|
use Icinga\Data\Extensible;
|
||||||
use Icinga\Data\Updatable;
|
use Icinga\Data\Updatable;
|
||||||
use Icinga\Data\Selectable;
|
|
||||||
|
|
||||||
$extensible = $this->hasPermission('config/authentication/groups/add') && $backend instanceof Extensible;
|
$extensible = $this->hasPermission('config/authentication/groups/add') && $backend instanceof Extensible;
|
||||||
|
|
||||||
|
@ -24,7 +23,7 @@ if ($this->hasPermission('config/authentication/groups/edit') && $backend instan
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<div class="controls">
|
<div class="controls separated dont-print">
|
||||||
<?php if (! $this->compact): ?>
|
<?php if (! $this->compact): ?>
|
||||||
<?= $tabs; ?>
|
<?= $tabs; ?>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
@ -39,58 +38,56 @@ if ($this->hasPermission('config/authentication/groups/edit') && $backend instan
|
||||||
<td><?= $group->last_modified === null ? '-' : $this->formatDateTime($group->last_modified); ?></td>
|
<td><?= $group->last_modified === null ? '-' : $this->formatDateTime($group->last_modified); ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<h2><?= $this->translate('Members'); ?></h2>
|
|
||||||
<?php if (! $this->compact): ?>
|
<?php if (! $this->compact): ?>
|
||||||
<?= $this->sortBox; ?>
|
<h2><?= $this->translate('Members'); ?></h2>
|
||||||
<?php endif ?>
|
<div class="grid">
|
||||||
<?= $this->limiter; ?>
|
<?= $this->limiter; ?>
|
||||||
<?= $this->paginator; ?>
|
<?= $this->paginator; ?>
|
||||||
<?php if (! $this->compact): ?>
|
<?= $this->sortBox; ?>
|
||||||
|
</div>
|
||||||
<?= $this->filterEditor; ?>
|
<?= $this->filterEditor; ?>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="content members" data-base-target="_next">
|
<div class="content">
|
||||||
<?php if ($extensible): ?>
|
<?php if ($extensible): ?>
|
||||||
<?= $this->qlink(
|
<?= $this->qlink(
|
||||||
$this->translate('Add User to Group') ,
|
$this->translate('Add New Member'),
|
||||||
'group/addmember',
|
'group/addmember',
|
||||||
null,
|
|
||||||
array(
|
array(
|
||||||
'class' => 'button-link',
|
'backend' => $backend->getName(),
|
||||||
'data-base-target' => '_next',
|
'group' => $group->group_name
|
||||||
|
),
|
||||||
|
array(
|
||||||
'icon' => 'plus',
|
'icon' => 'plus',
|
||||||
'title' => $this->translate('Add user to group')
|
'class' => 'button-link'
|
||||||
)
|
)
|
||||||
) ?>
|
) ?>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php
|
<?php if (! $members->hasResult()): ?>
|
||||||
|
<p><?= $this->translate('No group member found matching the filter'); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php return; endif ?>
|
||||||
|
|
||||||
$firstRow = true;
|
|
||||||
foreach ($members as $member): ?>
|
|
||||||
<?php if ($firstRow): ?>
|
|
||||||
<?php $firstRow = false; ?>
|
|
||||||
<table data-base-target="_next" class="action-table listing-table">
|
<table data-base-target="_next" class="action-table listing-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="member-name"><?= $this->translate('Username'); ?></th>
|
<th><?= $this->translate('Username'); ?></th>
|
||||||
<?php if (isset($removeForm)): ?>
|
<?php if (isset($removeForm)): ?>
|
||||||
<th class="member-remove"><?= $this->translate('Remove'); ?></th>
|
<th><?= $this->translate('Remove'); ?></th>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php endif ?>
|
<?php foreach ($members as $member): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="member-name">
|
<td>
|
||||||
<?php if (
|
<?php if (
|
||||||
$this->hasPermission('config/authentication/users/show')
|
$this->hasPermission('config/authentication/users/show')
|
||||||
&& method_exists($backend, 'getUserBackend')
|
&& ($userBackend = $backend->getUserBackendName($member->user_name)) !== null
|
||||||
&& ($userBackend = $backend->getUserBackend()) !== null
|
|
||||||
&& $userBackend instanceof Selectable
|
|
||||||
): ?>
|
): ?>
|
||||||
<?= $this->qlink($member->user_name, 'user/show', array(
|
<?= $this->qlink($member->user_name, 'user/show', array(
|
||||||
'backend' => $userBackend->getName(),
|
'backend' => $userBackend,
|
||||||
'user' => $member->user_name
|
'user' => $member->user_name
|
||||||
), array(
|
), array(
|
||||||
'title' => sprintf($this->translate('Show detailed information about %s'), $member->user_name)
|
'title' => sprintf($this->translate('Show detailed information about %s'), $member->user_name)
|
||||||
|
@ -106,10 +103,6 @@ foreach ($members as $member): ?>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
<?php if ($members->hasResult()): ?>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<?php else: ?>
|
|
||||||
<p><?= $this->translate('No group member found matching the filter'); ?></p>
|
|
||||||
<?php endif ?>
|
|
||||||
</div>
|
</div>
|
|
@ -1,41 +1,34 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Icinga\Data\Extensible;
|
use Icinga\Data\Extensible;
|
||||||
use Icinga\Data\Reducible;
|
use Icinga\Data\Reducible;
|
||||||
|
|
||||||
if (! $this->compact): ?>
|
if (! $this->compact): ?>
|
||||||
<div class="controls">
|
<div class="controls separated dont-print">
|
||||||
<?= $this->tabs ?>
|
<?= $tabs ?>
|
||||||
<div class="grid dont-print">
|
<div class="grid">
|
||||||
<div class="col-1-3 text-left">
|
|
||||||
<?= $this->limiter ?>
|
<?= $this->limiter ?>
|
||||||
</div>
|
|
||||||
<div class="col-1-3">
|
|
||||||
<?= $this->paginator ?>
|
<?= $this->paginator ?>
|
||||||
</div>
|
|
||||||
<div class="col-1-3 text-right">
|
|
||||||
<?= $this->sortBox ?>
|
<?= $this->sortBox ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<?= $this->backendSelection ?>
|
<?= $this->backendSelection ?>
|
||||||
<?= $this->filterEditor ?>
|
<?= $this->filterEditor ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<div class="content">
|
<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
|
<?php
|
||||||
|
|
||||||
|
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;
|
$extensible = $this->hasPermission('config/authentication/users/add') && $backend instanceof Extensible;
|
||||||
$reducible = $this->hasPermission('config/authentication/users/remove') && $backend instanceof Reducible;
|
$reducible = $this->hasPermission('config/authentication/users/remove') && $backend instanceof Reducible;
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<?php if ($extensible): ?>
|
<?php if ($extensible): ?>
|
||||||
<?= $this->qlink(
|
<?= $this->qlink(
|
||||||
$this->translate('Add a New User') ,
|
$this->translate('Add a New User') ,
|
||||||
|
@ -44,12 +37,17 @@ $reducible = $this->hasPermission('config/authentication/users/remove') && $back
|
||||||
array(
|
array(
|
||||||
'class' => 'button-link',
|
'class' => 'button-link',
|
||||||
'data-base-target' => '_next',
|
'data-base-target' => '_next',
|
||||||
'icon' => 'plus',
|
'icon' => 'plus'
|
||||||
'title' => $this->translate('Create a new user')
|
|
||||||
)
|
)
|
||||||
) ?>
|
) ?>
|
||||||
<?php endif ?>
|
<?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>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th><?= $this->translate('Username') ?></th>
|
<th><?= $this->translate('Username') ?></th>
|
||||||
|
|
|
@ -22,7 +22,7 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<div class="controls">
|
<div class="controls separated dont-print">
|
||||||
<?php if (! $this->compact): ?>
|
<?php if (! $this->compact): ?>
|
||||||
<?= $tabs; ?>
|
<?= $tabs; ?>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
@ -41,47 +41,48 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc
|
||||||
<td><?= $user->last_modified === null ? '-' : $this->formatDateTime($user->last_modified); ?></td>
|
<td><?= $user->last_modified === null ? '-' : $this->formatDateTime($user->last_modified); ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<h2><?= $this->translate('Group Memberships'); ?></h2>
|
|
||||||
<?php if (! $this->compact): ?>
|
<?php if (! $this->compact): ?>
|
||||||
<?= $this->sortBox; ?>
|
<h2><?= $this->translate('Group Memberships'); ?></h2>
|
||||||
<?php endif ?>
|
<div class="grid">
|
||||||
<?= $this->limiter; ?>
|
<?= $this->limiter; ?>
|
||||||
<?= $this->paginator; ?>
|
<?= $this->paginator; ?>
|
||||||
<?php if (! $this->compact): ?>
|
<?= $this->sortBox; ?>
|
||||||
|
</div>
|
||||||
<?= $this->filterEditor; ?>
|
<?= $this->filterEditor; ?>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="content memberships">
|
<div class="content">
|
||||||
<?php if ($showCreateMembershipLink): ?>
|
<?php if ($showCreateMembershipLink): ?>
|
||||||
<?= $this->qlink(
|
<?= $this->qlink(
|
||||||
$this->translate('Add User to Group') ,
|
$this->translate('Create New Membership'),
|
||||||
'user/createmembership',
|
'user/createmembership',
|
||||||
array('backend' => $backend->getName()),
|
|
||||||
array(
|
array(
|
||||||
'class' => 'button-link',
|
'backend' => $backend->getName(),
|
||||||
'data-base-target' => '_next',
|
'user' => $user->user_name
|
||||||
|
),
|
||||||
|
array(
|
||||||
'icon' => 'plus',
|
'icon' => 'plus',
|
||||||
'title' => $this->translate('Add user to user group')
|
'class' => 'button-link'
|
||||||
)
|
)
|
||||||
) ?>
|
) ?>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
<?php
|
|
||||||
// @TODO(el): Remove that $firstRow thingy
|
<?php if (! $memberships->hasResult()): ?>
|
||||||
$firstRow = true;
|
<p><?= $this->translate('No memberships found matching the filter'); ?></p>
|
||||||
foreach ($memberships as $membership): ?>
|
</div>
|
||||||
<?php if ($firstRow): ?>
|
<?php return; endif ?>
|
||||||
<?php $firstRow = false; ?>
|
|
||||||
<table data-base-target="_next" class="action-table listing-table">
|
<table data-base-target="_next" class="action-table listing-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="membership-group"><?= $this->translate('Group'); ?></th>
|
<th><?= $this->translate('Group'); ?></th>
|
||||||
<th class="membership-cancel"><?= $this->translate('Cancel', 'group.membership'); ?></th>
|
<th><?= $this->translate('Cancel', 'group.membership'); ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php endif ?>
|
<?php foreach ($memberships as $membership): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="membership-group">
|
<td>
|
||||||
<?php if ($this->hasPermission('config/authentication/groups/show') && $membership->backend instanceof Selectable): ?>
|
<?php if ($this->hasPermission('config/authentication/groups/show') && $membership->backend instanceof Selectable): ?>
|
||||||
<?= $this->qlink($membership->group_name, 'group/show', array(
|
<?= $this->qlink($membership->group_name, 'group/show', array(
|
||||||
'backend' => $membership->backend->getName(),
|
'backend' => $membership->backend->getName(),
|
||||||
|
@ -93,7 +94,7 @@ foreach ($memberships as $membership): ?>
|
||||||
<?= $this->escape($membership->group_name); ?>
|
<?= $this->escape($membership->group_name); ?>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
</td>
|
</td>
|
||||||
<td class="membership-cancel" data-base-target="_self">
|
<td class="icon-col" data-base-target="_self">
|
||||||
<?php if (isset($removeForm) && $membership->backend instanceof Reducible): ?>
|
<?php if (isset($removeForm) && $membership->backend instanceof Reducible): ?>
|
||||||
<?= $removeForm->setAction($this->url('group/removemember', array(
|
<?= $removeForm->setAction($this->url('group/removemember', array(
|
||||||
'backend' => $membership->backend->getName(),
|
'backend' => $membership->backend->getName(),
|
||||||
|
@ -105,10 +106,6 @@ foreach ($memberships as $membership): ?>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
<?php if ($memberships->hasResult()): ?>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<?php else: ?>
|
|
||||||
<p><?= $this->translate('No memberships found matching the filter'); ?></p>
|
|
||||||
<?php endif ?>
|
|
||||||
</div>
|
</div>
|
|
@ -495,6 +495,6 @@ The first release candidate of Icinga Web 2 introduces the following non-backwar
|
||||||
the file remains unchanged.
|
the file remains unchanged.
|
||||||
|
|
||||||
* The location of a user's preferences has been changed from
|
* The location of a user's preferences has been changed from
|
||||||
**<config-dir>/preferences/<username>.ini** to
|
**<config-dir>/preferences/<username>.ini** to
|
||||||
**<config-dir>/preferences/<username>/config.ini**.
|
**<config-dir>/preferences/<username>/config.ini**.
|
||||||
The content of the file remains unchanged.
|
The content of the file remains unchanged.
|
||||||
|
|
|
@ -25,7 +25,7 @@ Packager: Icinga Team <info@icinga.org>
|
||||||
%if 0%{?suse_version}
|
%if 0%{?suse_version}
|
||||||
%define wwwconfigdir %{_sysconfdir}/apache2/conf.d
|
%define wwwconfigdir %{_sysconfdir}/apache2/conf.d
|
||||||
%define wwwuser wwwrun
|
%define wwwuser wwwrun
|
||||||
%define zend php5-ZendFramework
|
%define zend %{name}-vendor-Zend
|
||||||
%if 0%{?suse_version} == 1110
|
%if 0%{?suse_version} == 1110
|
||||||
%define php php53
|
%define php php53
|
||||||
Requires: apache2-mod_php53
|
Requires: apache2-mod_php53
|
||||||
|
@ -83,7 +83,6 @@ Requires: %{php}-gd %{php}-intl
|
||||||
%{?rhel:Requires: php-pecl-imagick}
|
%{?rhel:Requires: php-pecl-imagick}
|
||||||
%{?suse_version:Requires: %{php}-gettext %{php}-json %{php}-openssl %{php}-posix}
|
%{?suse_version:Requires: %{php}-gettext %{php}-json %{php}-openssl %{php}-posix}
|
||||||
Requires: %{zend}
|
Requires: %{zend}
|
||||||
Obsoletes: %{name}-vendor-zend
|
|
||||||
Requires: %{zend}-Db-Adapter-Pdo-Mysql
|
Requires: %{zend}-Db-Adapter-Pdo-Mysql
|
||||||
Requires: %{zend}-Db-Adapter-Pdo-Pgsql
|
Requires: %{zend}-Db-Adapter-Pdo-Pgsql
|
||||||
|
|
||||||
|
|
|
@ -136,6 +136,16 @@ class Logger
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the logging level being used
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getLevel()
|
||||||
|
{
|
||||||
|
return $this->level;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register the given message as config error
|
* Register the given message as config error
|
||||||
*
|
*
|
||||||
|
|
|
@ -130,6 +130,7 @@ class DbUserBackend extends DbRepository implements UserBackendInterface, Inspec
|
||||||
*/
|
*/
|
||||||
public function insert($table, array $bind)
|
public function insert($table, array $bind)
|
||||||
{
|
{
|
||||||
|
$this->requireTable($table);
|
||||||
$bind['created_at'] = date('Y-m-d H:i:s');
|
$bind['created_at'] = date('Y-m-d H:i:s');
|
||||||
$this->ds->insert(
|
$this->ds->insert(
|
||||||
$this->prependTablePrefix($table),
|
$this->prependTablePrefix($table),
|
||||||
|
@ -150,6 +151,7 @@ class DbUserBackend extends DbRepository implements UserBackendInterface, Inspec
|
||||||
*/
|
*/
|
||||||
public function update($table, array $bind, Filter $filter = null)
|
public function update($table, array $bind, Filter $filter = null)
|
||||||
{
|
{
|
||||||
|
$this->requireTable($table);
|
||||||
$bind['last_modified'] = date('Y-m-d H:i:s');
|
$bind['last_modified'] = date('Y-m-d H:i:s');
|
||||||
if ($filter) {
|
if ($filter) {
|
||||||
$filter = $this->requireFilter($table, $filter);
|
$filter = $this->requireFilter($table, $filter);
|
||||||
|
|
|
@ -12,7 +12,6 @@ use Icinga\Exception\ProgrammingError;
|
||||||
use Icinga\Repository\LdapRepository;
|
use Icinga\Repository\LdapRepository;
|
||||||
use Icinga\Repository\RepositoryQuery;
|
use Icinga\Repository\RepositoryQuery;
|
||||||
use Icinga\Protocol\Ldap\LdapException;
|
use Icinga\Protocol\Ldap\LdapException;
|
||||||
use Icinga\Protocol\Ldap\Expression;
|
|
||||||
use Icinga\User;
|
use Icinga\User;
|
||||||
|
|
||||||
class LdapUserBackend extends LdapRepository implements UserBackendInterface, Inspectable
|
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
|
* Set the objectClass where to look for users
|
||||||
*
|
*
|
||||||
* Sets also the base table name for the underlying repository.
|
|
||||||
*
|
|
||||||
* @param string $userClass
|
* @param string $userClass
|
||||||
*
|
*
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setUserClass($userClass)
|
public function setUserClass($userClass)
|
||||||
{
|
{
|
||||||
$this->baseTable = $this->userClass = $this->getNormedAttribute($userClass);
|
$this->userClass = $this->getNormedAttribute($userClass);
|
||||||
return $this;
|
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);
|
if ($this->userClass === null) {
|
||||||
$query->getQuery()->setBase($this->baseDn);
|
throw new ProgrammingError('It is required to set the object class where to find users first');
|
||||||
if ($this->filter) {
|
|
||||||
$query->getQuery()->where(new Expression($this->filter));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $query;
|
return array(
|
||||||
|
'user' => $this->userClass
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -216,13 +213,10 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In
|
||||||
*
|
*
|
||||||
* @return array
|
* @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()
|
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) {
|
if ($this->userNameAttribute === null) {
|
||||||
throw new ProgrammingError('It is required to set a attribute name where to find a user\'s name first');
|
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(
|
return array(
|
||||||
$this->userClass => array(
|
'user' => array(
|
||||||
'user' => $this->userNameAttribute,
|
'user' => $this->userNameAttribute,
|
||||||
'user_name' => $this->userNameAttribute,
|
'user_name' => $this->userNameAttribute,
|
||||||
'is_active' => $isActiveAttribute,
|
'is_active' => $isActiveAttribute,
|
||||||
|
@ -269,15 +263,9 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In
|
||||||
* Initialize this repository's conversion rules
|
* Initialize this repository's conversion rules
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*
|
|
||||||
* @throws ProgrammingError In case $this->userClass has not been set yet
|
|
||||||
*/
|
*/
|
||||||
protected function initializeConversionRules()
|
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()) {
|
if ($this->ds->getCapabilities()->isActiveDirectory()) {
|
||||||
$stateConverter = 'user_account_control';
|
$stateConverter = 'user_account_control';
|
||||||
} else {
|
} else {
|
||||||
|
@ -285,7 +273,7 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In
|
||||||
}
|
}
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
$this->userClass => array(
|
'user' => array(
|
||||||
'is_active' => $stateConverter,
|
'is_active' => $stateConverter,
|
||||||
'created_at' => 'generalized_time',
|
'created_at' => 'generalized_time',
|
||||||
'last_modified' => 'generalized_time'
|
'last_modified' => 'generalized_time'
|
||||||
|
@ -330,14 +318,46 @@ class LdapUserBackend extends LdapRepository implements UserBackendInterface, In
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Validate that the requested table exists
|
||||||
* @param Inspection $info Optional inspection to fill with diagnostic info
|
|
||||||
*
|
*
|
||||||
* @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()
|
->getQuery()
|
||||||
->setUsePagedResults(false)
|
->setUsePagedResults(false)
|
||||||
->fetchDn();
|
->fetchDn();
|
||||||
|
|
||||||
if ($userDn === null) {
|
if ($userDn === null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$testCredentialsResult = $this->ds->testCredentials($userDn, $password);
|
$validCredentials = $this->ds->testCredentials($userDn, $password);
|
||||||
if ($testCredentialsResult) {
|
if ($validCredentials) {
|
||||||
$user->setAdditional('ldap_dn', $userDn);
|
$user->setAdditional('ldap_dn', $userDn);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $testCredentialsResult;
|
return $validCredentials;
|
||||||
} catch (LdapException $e) {
|
} catch (LdapException $e) {
|
||||||
throw new AuthenticationException(
|
throw new AuthenticationException(
|
||||||
'Failed to authenticate user "%s" against backend "%s". An exception was thrown:',
|
'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 $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
|
* Join group into group_membership
|
||||||
*
|
*
|
||||||
|
@ -238,7 +250,7 @@ class DbUserGroupBackend extends DbRepository implements UserGroupBackendInterfa
|
||||||
*/
|
*/
|
||||||
protected function joinGroupMembership(RepositoryQuery $query)
|
protected function joinGroupMembership(RepositoryQuery $query)
|
||||||
{
|
{
|
||||||
$query->getQuery()->join(
|
$query->getQuery()->joinLeft(
|
||||||
$this->requireTable('group_membership'),
|
$this->requireTable('group_membership'),
|
||||||
'g.id = gm.group_id',
|
'g.id = gm.group_id',
|
||||||
array()
|
array()
|
||||||
|
|
|
@ -5,10 +5,11 @@ namespace Icinga\Authentication\UserGroup;
|
||||||
|
|
||||||
use Icinga\Authentication\User\UserBackend;
|
use Icinga\Authentication\User\UserBackend;
|
||||||
use Icinga\Authentication\User\LdapUserBackend;
|
use Icinga\Authentication\User\LdapUserBackend;
|
||||||
|
use Icinga\Application\Logger;
|
||||||
use Icinga\Data\ConfigObject;
|
use Icinga\Data\ConfigObject;
|
||||||
use Icinga\Exception\ConfigurationError;
|
use Icinga\Exception\ConfigurationError;
|
||||||
use Icinga\Exception\ProgrammingError;
|
use Icinga\Exception\ProgrammingError;
|
||||||
use Icinga\Protocol\Ldap\Expression;
|
use Icinga\Protocol\Ldap\LdapException;
|
||||||
use Icinga\Repository\LdapRepository;
|
use Icinga\Repository\LdapRepository;
|
||||||
use Icinga\Repository\RepositoryQuery;
|
use Icinga\Repository\RepositoryQuery;
|
||||||
use Icinga\User;
|
use Icinga\User;
|
||||||
|
@ -71,6 +72,13 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
||||||
*/
|
*/
|
||||||
protected $groupMemberAttribute;
|
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
|
* 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
|
* Set the objectClass where to look for groups
|
||||||
*
|
*
|
||||||
* Sets also the base table name for the underlying repository.
|
|
||||||
*
|
|
||||||
* @param string $groupClass
|
* @param string $groupClass
|
||||||
*
|
*
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setGroupClass($groupClass)
|
public function setGroupClass($groupClass)
|
||||||
{
|
{
|
||||||
$this->baseTable = $this->groupClass = $this->getNormedAttribute($groupClass);
|
$this->groupClass = $this->getNormedAttribute($groupClass);
|
||||||
return $this;
|
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);
|
if ($this->ambiguousMemberAttribute === null) {
|
||||||
$query->getQuery()->setBase($this->groupBaseDn);
|
if ($this->groupClass === null) {
|
||||||
if ($this->groupFilter) {
|
throw new ProgrammingError(
|
||||||
// TODO(jom): This should differentiate between groups and their memberships
|
'It is required to set the objectClass where to look for groups first'
|
||||||
$query->getQuery()->where(new Expression($this->groupFilter));
|
);
|
||||||
|
} elseif ($this->groupMemberAttribute === null) {
|
||||||
|
throw new ProgrammingError(
|
||||||
|
'It is required to set a attribute name where to find a group\'s members first'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $query;
|
$sampleValue = $this->ds
|
||||||
|
->select()
|
||||||
|
->from($this->groupClass, array($this->groupMemberAttribute))
|
||||||
|
->setUnfoldAttribute($this->groupMemberAttribute)
|
||||||
|
->setBase($this->groupBaseDn)
|
||||||
|
->fetchOne();
|
||||||
|
$this->ambiguousMemberAttribute = !$this->isRelatedDn($sampleValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
* @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()
|
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) {
|
if ($this->groupNameAttribute === null) {
|
||||||
throw new ProgrammingError('It is required to set a attribute name where to find a group\'s name first');
|
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()) {
|
if ($this->ds->getCapabilities()->isActiveDirectory()) {
|
||||||
$createdAtAttribute = 'whenCreated';
|
$createdAtAttribute = 'whenCreated';
|
||||||
|
@ -409,7 +449,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
||||||
'created_at' => $createdAtAttribute,
|
'created_at' => $createdAtAttribute,
|
||||||
'last_modified' => $lastModifiedAttribute
|
'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()
|
protected function initializeFilterColumns()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
t('Username') => 'user',
|
t('Username') => 'user_name',
|
||||||
t('User Group') => 'group_name',
|
t('User Group') => 'group_name',
|
||||||
t('Created At') => 'created_at',
|
t('Created At') => 'created_at',
|
||||||
t('Last Modified') => 'last_modified'
|
t('Last Modified') => 'last_modified'
|
||||||
|
@ -431,31 +471,68 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
||||||
* Initialize this repository's conversion rules
|
* Initialize this repository's conversion rules
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*
|
|
||||||
* @throws ProgrammingError In case $this->groupClass has not been set yet
|
|
||||||
*/
|
*/
|
||||||
protected function initializeConversionRules()
|
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(
|
$rules = array(
|
||||||
$this->groupClass => array(
|
'group' => array(
|
||||||
|
'created_at' => 'generalized_time',
|
||||||
|
'last_modified' => 'generalized_time'
|
||||||
|
),
|
||||||
|
'group_membership' => array(
|
||||||
'created_at' => 'generalized_time',
|
'created_at' => 'generalized_time',
|
||||||
'last_modified' => 'generalized_time'
|
'last_modified' => 'generalized_time'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
if (! $this->isAmbiguous($this->groupClass, $this->groupMemberAttribute)) {
|
if (! $this->isMemberAttributeAmbiguous()) {
|
||||||
$rules[$this->groupClass][] = 'user_name';
|
$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 $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
|
* Return the uid for the given distinguished name
|
||||||
*
|
*
|
||||||
|
@ -475,11 +552,8 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
||||||
/**
|
/**
|
||||||
* Validate that the requested table exists
|
* 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 string $table The table to validate
|
||||||
* @param RepositoryQuery $query An optional query to pass as context
|
* @param RepositoryQuery $query An optional query to pass as context
|
||||||
* (unused by the base implementation)
|
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*
|
*
|
||||||
|
@ -487,12 +561,14 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
||||||
*/
|
*/
|
||||||
public function requireTable($table, RepositoryQuery $query = null)
|
public function requireTable($table, RepositoryQuery $query = null)
|
||||||
{
|
{
|
||||||
$table = parent::requireTable($table, $query);
|
if ($query !== null) {
|
||||||
if ($table === 'group' || $table === 'group_membership') {
|
$query->getQuery()->setBase($this->groupBaseDn);
|
||||||
$table = $this->groupClass;
|
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)
|
public function getMemberships(User $user)
|
||||||
{
|
{
|
||||||
if ($this->isAmbiguous($this->groupClass, $this->groupMemberAttribute)) {
|
if ($this->isMemberAttributeAmbiguous()) {
|
||||||
$queryValue = $user->getUsername();
|
$queryValue = $user->getUsername();
|
||||||
} elseif (($queryValue = $user->getAdditional('ldap_dn')) === null) {
|
} elseif (($queryValue = $user->getAdditional('ldap_dn')) === null) {
|
||||||
$userQuery = $this->ds
|
$userQuery = $this->ds
|
||||||
|
@ -535,7 +611,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
||||||
->setBase($this->userBaseDn)
|
->setBase($this->userBaseDn)
|
||||||
->setUsePagedResults(false);
|
->setUsePagedResults(false);
|
||||||
if ($this->userFilter) {
|
if ($this->userFilter) {
|
||||||
$userQuery->where(new Expression($this->userFilter));
|
$userQuery->setNativeFilter($this->userFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($queryValue = $userQuery->fetchDn()) === null) {
|
if (($queryValue = $userQuery->fetchDn()) === null) {
|
||||||
|
@ -549,7 +625,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
||||||
->where($this->groupMemberAttribute, $queryValue)
|
->where($this->groupMemberAttribute, $queryValue)
|
||||||
->setBase($this->groupBaseDn);
|
->setBase($this->groupBaseDn);
|
||||||
if ($this->groupFilter) {
|
if ($this->groupFilter) {
|
||||||
$groupQuery->where(new Expression($this->groupFilter));
|
$groupQuery->setNativeFilter($this->groupFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
$groups = array();
|
$groups = array();
|
||||||
|
@ -560,6 +636,21 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
||||||
return $groups;
|
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
|
* Apply the given configuration on this backend
|
||||||
*
|
*
|
||||||
|
@ -607,7 +698,7 @@ class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInt
|
||||||
|
|
||||||
return $this
|
return $this
|
||||||
->setGroupBaseDn($config->base_dn)
|
->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))
|
->setGroupClass($config->get('group_class', $defaults->group_class))
|
||||||
->setUserClass($config->get('user_class', $defaults->user_class))
|
->setUserClass($config->get('user_class', $defaults->user_class))
|
||||||
->setGroupNameAttribute($config->get('group_name_attribute', $defaults->group_name_attribute))
|
->setGroupNameAttribute($config->get('group_name_attribute', $defaults->group_name_attribute))
|
||||||
|
|
|
@ -34,4 +34,13 @@ interface UserGroupBackendInterface
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getMemberships(User $user);
|
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 $commandName;
|
||||||
protected $actionName;
|
protected $actionName;
|
||||||
|
|
||||||
private $config;
|
protected $config;
|
||||||
|
|
||||||
private $configs;
|
protected $configs;
|
||||||
|
|
||||||
protected $defaultActionName = 'default';
|
protected $defaultActionName = 'default';
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ abstract class Command
|
||||||
|
|
||||||
public function fail($msg)
|
public function fail($msg)
|
||||||
{
|
{
|
||||||
throw new IcingaException($msg);
|
throw new IcingaException('%s', $msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDefaultActionName()
|
public function getDefaultActionName()
|
||||||
|
|
|
@ -130,6 +130,7 @@ class DbConnection implements Selectable, Extensible, Updatable, Reducible, Insp
|
||||||
'username' => $this->config->username,
|
'username' => $this->config->username,
|
||||||
'password' => $this->config->password,
|
'password' => $this->config->password,
|
||||||
'dbname' => $this->config->dbname,
|
'dbname' => $this->config->dbname,
|
||||||
|
'charset' => $this->config->charset,
|
||||||
'persistent' => (bool) $this->config->get('persistent', false),
|
'persistent' => (bool) $this->config->get('persistent', false),
|
||||||
'options' => & $genericAdapterOptions,
|
'options' => & $genericAdapterOptions,
|
||||||
'driver_options' => & $driverOptions
|
'driver_options' => & $driverOptions
|
||||||
|
|
|
@ -288,9 +288,14 @@ class DbQuery extends SimpleQuery
|
||||||
$expression = $this->valueToTimestamp($expression);
|
$expression = $this->valueToTimestamp($expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_array($expression) && $sign === '=') {
|
if (is_array($expression)) {
|
||||||
// TODO: Should we support this? Doesn't work for blub*
|
if ($sign === '=') {
|
||||||
return $col . ' IN (' . $this->escapeForSql($expression) . ')';
|
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) {
|
} elseif ($sign === '=' && strpos($expression, '*') !== false) {
|
||||||
if ($expression === '*') {
|
if ($expression === '*') {
|
||||||
// We'll ignore such filters as it prevents index usage and because "*" means anything, anything means
|
// 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;
|
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) {
|
foreach ($this->filters as $filter) {
|
||||||
if ($filter instanceof FilterExpression) {
|
if ($filter instanceof FilterExpression) {
|
||||||
$columns[] = $filter->getColumn();
|
$column= $filter->getColumn();
|
||||||
|
if (! in_array($column, $columns, true)) {
|
||||||
|
$columns[] = $column;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$columns += $filter->listFilteredColumns();
|
$columns = $filter->listFilteredColumns($columns);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// array_unique?
|
|
||||||
return $columns;
|
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)
|
ldap_error($ds)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$cap = new LdapCapabilities(
|
|
||||||
$connection->cleanupAttributes(
|
$cap = new LdapCapabilities($connection->cleanupAttributes(ldap_get_attributes($ds, $entry), $fields));
|
||||||
ldap_get_attributes($ds, $entry),
|
|
||||||
array_flip($fields)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return $cap;
|
return $cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,14 @@ use Exception;
|
||||||
use ArrayIterator;
|
use ArrayIterator;
|
||||||
use Icinga\Application\Config;
|
use Icinga\Application\Config;
|
||||||
use Icinga\Application\Logger;
|
use Icinga\Application\Logger;
|
||||||
use Icinga\Application\Platform;
|
|
||||||
use Icinga\Data\ConfigObject;
|
use Icinga\Data\ConfigObject;
|
||||||
use Icinga\Data\Inspectable;
|
use Icinga\Data\Inspectable;
|
||||||
use Icinga\Data\Inspection;
|
use Icinga\Data\Inspection;
|
||||||
use Icinga\Data\Selectable;
|
use Icinga\Data\Selectable;
|
||||||
use Icinga\Data\Sortable;
|
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\Exception\ProgrammingError;
|
||||||
use Icinga\Protocol\Ldap\LdapException;
|
use Icinga\Protocol\Ldap\LdapException;
|
||||||
|
|
||||||
|
@ -377,14 +378,7 @@ class LdapConnection implements Selectable, Inspectable
|
||||||
}
|
}
|
||||||
|
|
||||||
$ds = $this->getConnection();
|
$ds = $this->getConnection();
|
||||||
$results = @ldap_search(
|
$results = $this->ldapSearch($query, array('dn'));
|
||||||
$ds,
|
|
||||||
$query->getBase() ?: $this->getDn(),
|
|
||||||
(string) $query,
|
|
||||||
array('dn'),
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($results === false) {
|
if ($results === false) {
|
||||||
if (ldap_errno($ds) !== self::LDAP_NO_SUCH_OBJECT) {
|
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)
|
public function fetchOne(LdapQuery $query, array $fields = null)
|
||||||
{
|
{
|
||||||
$row = (array) $this->fetchRow($query, $fields);
|
$row = $this->fetchRow($query, $fields);
|
||||||
return array_shift($row) ?: false;
|
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 {
|
} else {
|
||||||
foreach ($query->getOrder() as $rule) {
|
foreach ($query->getOrder() as $rule) {
|
||||||
if (! in_array($rule[0], $fields)) {
|
if (! in_array($rule[0], $fields, true)) {
|
||||||
$fields[] = $rule[0];
|
$fields[] = $rule[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$results = @ldap_search(
|
$unfoldAttribute = $query->getUnfoldAttribute();
|
||||||
$ds,
|
if ($unfoldAttribute) {
|
||||||
$query->getBase() ?: $this->rootDn,
|
foreach ($query->getFilter()->listFilteredColumns() as $filterColumn) {
|
||||||
(string) $query,
|
$fieldKey = array_search($filterColumn, $fields, true);
|
||||||
|
if ($fieldKey === false || is_string($fieldKey)) {
|
||||||
|
$fields[] = $filterColumn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$results = $this->ldapSearch(
|
||||||
|
$query,
|
||||||
array_values($fields),
|
array_values($fields),
|
||||||
0, // Attributes and values
|
0,
|
||||||
$serverSorting && $limit ? $offset + $limit : 0
|
$serverSorting && $limit ? $offset + $limit : 0
|
||||||
);
|
);
|
||||||
if ($results === false) {
|
if ($results === false) {
|
||||||
|
@ -727,18 +749,13 @@ class LdapConnection implements Selectable, Inspectable
|
||||||
$count = 0;
|
$count = 0;
|
||||||
$entries = array();
|
$entries = array();
|
||||||
$entry = ldap_first_entry($ds, $results);
|
$entry = ldap_first_entry($ds, $results);
|
||||||
$unfoldAttribute = $query->getUnfoldAttribute();
|
|
||||||
do {
|
do {
|
||||||
if ($unfoldAttribute) {
|
if ($unfoldAttribute) {
|
||||||
$rows = $this->cleanupAttributes(
|
$rows = $this->cleanupAttributes(ldap_get_attributes($ds, $entry), $fields, $unfoldAttribute);
|
||||||
ldap_get_attributes($ds, $entry),
|
|
||||||
array_flip($fields),
|
|
||||||
$unfoldAttribute
|
|
||||||
);
|
|
||||||
|
|
||||||
if (is_array($rows)) {
|
if (is_array($rows)) {
|
||||||
// TODO: Register the DN the same way as a section name in the ArrayDatasource!
|
// TODO: Register the DN the same way as a section name in the ArrayDatasource!
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
|
if ($query->getFilter()->matches($row)) {
|
||||||
$count += 1;
|
$count += 1;
|
||||||
if (! $serverSorting || $offset === 0 || $offset < $count) {
|
if (! $serverSorting || $offset === 0 || $offset < $count) {
|
||||||
$entries[] = $row;
|
$entries[] = $row;
|
||||||
|
@ -748,6 +765,7 @@ class LdapConnection implements Selectable, Inspectable
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$count += 1;
|
$count += 1;
|
||||||
if (! $serverSorting || $offset === 0 || $offset < $count) {
|
if (! $serverSorting || $offset === 0 || $offset < $count) {
|
||||||
|
@ -759,7 +777,7 @@ class LdapConnection implements Selectable, Inspectable
|
||||||
if (! $serverSorting || $offset === 0 || $offset < $count) {
|
if (! $serverSorting || $offset === 0 || $offset < $count) {
|
||||||
$entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
|
$entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
|
||||||
ldap_get_attributes($ds, $entry),
|
ldap_get_attributes($ds, $entry),
|
||||||
array_flip($fields)
|
$fields
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -799,8 +817,6 @@ class LdapConnection implements Selectable, Inspectable
|
||||||
|
|
||||||
$limit = $query->getLimit();
|
$limit = $query->getLimit();
|
||||||
$offset = $query->hasOffset() ? $query->getOffset() : 0;
|
$offset = $query->hasOffset() ? $query->getOffset() : 0;
|
||||||
$queryString = (string) $query;
|
|
||||||
$base = $query->getBase() ?: $this->rootDn;
|
|
||||||
|
|
||||||
if ($fields === null) {
|
if ($fields === null) {
|
||||||
$fields = $query->getColumns();
|
$fields = $query->getColumns();
|
||||||
|
@ -811,16 +827,25 @@ class LdapConnection implements Selectable, Inspectable
|
||||||
$serverSorting = false;//$this->getCapabilities()->hasOid(LdapCapabilities::LDAP_SERVER_SORT_OID);
|
$serverSorting = false;//$this->getCapabilities()->hasOid(LdapCapabilities::LDAP_SERVER_SORT_OID);
|
||||||
if (! $serverSorting && $query->hasOrder()) {
|
if (! $serverSorting && $query->hasOrder()) {
|
||||||
foreach ($query->getOrder() as $rule) {
|
foreach ($query->getOrder() as $rule) {
|
||||||
if (! in_array($rule[0], $fields)) {
|
if (! in_array($rule[0], $fields, true)) {
|
||||||
$fields[] = $rule[0];
|
$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;
|
$count = 0;
|
||||||
$cookie = '';
|
$cookie = '';
|
||||||
$entries = array();
|
$entries = array();
|
||||||
$unfoldAttribute = $query->getUnfoldAttribute();
|
|
||||||
do {
|
do {
|
||||||
// Do not request the pagination control as a critical extension, as we want the
|
// 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
|
// 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(
|
$results = $this->ldapSearch(
|
||||||
$ds,
|
$query,
|
||||||
$base,
|
|
||||||
$queryString,
|
|
||||||
array_values($fields),
|
array_values($fields),
|
||||||
0, // Attributes and values
|
0,
|
||||||
$serverSorting && $limit ? $offset + $limit : 0
|
$serverSorting && $limit ? $offset + $limit : 0
|
||||||
);
|
);
|
||||||
if ($results === false) {
|
if ($results === false) {
|
||||||
|
@ -850,14 +873,15 @@ class LdapConnection implements Selectable, Inspectable
|
||||||
|
|
||||||
throw new LdapException(
|
throw new LdapException(
|
||||||
'LDAP query "%s" (base %s) failed. Error: %s',
|
'LDAP query "%s" (base %s) failed. Error: %s',
|
||||||
$queryString,
|
(string) $query,
|
||||||
$base,
|
$query->getBase() ?: $this->getDn(),
|
||||||
ldap_error($ds)
|
ldap_error($ds)
|
||||||
);
|
);
|
||||||
} elseif (ldap_count_entries($ds, $results) === 0) {
|
} elseif (ldap_count_entries($ds, $results) === 0) {
|
||||||
if (in_array(
|
if (in_array(
|
||||||
ldap_errno($ds),
|
ldap_errno($ds),
|
||||||
array(static::LDAP_SIZELIMIT_EXCEEDED, static::LDAP_ADMINLIMIT_EXCEEDED)
|
array(static::LDAP_SIZELIMIT_EXCEEDED, static::LDAP_ADMINLIMIT_EXCEEDED),
|
||||||
|
true
|
||||||
)) {
|
)) {
|
||||||
Logger::warning(
|
Logger::warning(
|
||||||
'Unable to request more than %u results. Does the server allow paged search requests? (%s)',
|
'Unable to request more than %u results. Does the server allow paged search requests? (%s)',
|
||||||
|
@ -872,15 +896,11 @@ class LdapConnection implements Selectable, Inspectable
|
||||||
$entry = ldap_first_entry($ds, $results);
|
$entry = ldap_first_entry($ds, $results);
|
||||||
do {
|
do {
|
||||||
if ($unfoldAttribute) {
|
if ($unfoldAttribute) {
|
||||||
$rows = $this->cleanupAttributes(
|
$rows = $this->cleanupAttributes(ldap_get_attributes($ds, $entry), $fields, $unfoldAttribute);
|
||||||
ldap_get_attributes($ds, $entry),
|
|
||||||
array_flip($fields),
|
|
||||||
$unfoldAttribute
|
|
||||||
);
|
|
||||||
|
|
||||||
if (is_array($rows)) {
|
if (is_array($rows)) {
|
||||||
// TODO: Register the DN the same way as a section name in the ArrayDatasource!
|
// TODO: Register the DN the same way as a section name in the ArrayDatasource!
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
|
if ($query->getFilter()->matches($row)) {
|
||||||
$count += 1;
|
$count += 1;
|
||||||
if (! $serverSorting || $offset === 0 || $offset < $count) {
|
if (! $serverSorting || $offset === 0 || $offset < $count) {
|
||||||
$entries[] = $row;
|
$entries[] = $row;
|
||||||
|
@ -890,6 +910,7 @@ class LdapConnection implements Selectable, Inspectable
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$count += 1;
|
$count += 1;
|
||||||
if (! $serverSorting || $offset === 0 || $offset < $count) {
|
if (! $serverSorting || $offset === 0 || $offset < $count) {
|
||||||
|
@ -901,7 +922,7 @@ class LdapConnection implements Selectable, Inspectable
|
||||||
if (! $serverSorting || $offset === 0 || $offset < $count) {
|
if (! $serverSorting || $offset === 0 || $offset < $count) {
|
||||||
$entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
|
$entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
|
||||||
ldap_get_attributes($ds, $entry),
|
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
|
// 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
|
// the server: https://www.ietf.org/rfc/rfc2696.txt
|
||||||
ldap_control_paged_result($ds, 0, false, $cookie);
|
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()) {
|
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.
|
// 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)
|
// This does also apply the virtual alias handling. (Since an LDAP server does not handle such)
|
||||||
$loweredFieldMap = array();
|
$loweredFieldMap = array();
|
||||||
foreach ($requestedFields as $name => $alias) {
|
foreach ($requestedFields as $alias => $name) {
|
||||||
$loweredFieldMap[strtolower($name)] = is_string($alias) ? $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();
|
$cleanedAttributes = array();
|
||||||
|
@ -982,12 +1013,18 @@ class LdapConnection implements Selectable, Inspectable
|
||||||
$requestedAttributeName = isset($loweredFieldMap[strtolower($attribute_name)])
|
$requestedAttributeName = isset($loweredFieldMap[strtolower($attribute_name)])
|
||||||
? $loweredFieldMap[strtolower($attribute_name)]
|
? $loweredFieldMap[strtolower($attribute_name)]
|
||||||
: $attribute_name;
|
: $attribute_name;
|
||||||
|
if (is_array($requestedAttributeName)) {
|
||||||
|
foreach ($requestedAttributeName as $requestedName) {
|
||||||
|
$cleanedAttributes[$requestedName] = $attribute_value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
$cleanedAttributes[$requestedAttributeName] = $attribute_value;
|
$cleanedAttributes[$requestedAttributeName] = $attribute_value;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The result may not contain all requested fields, so populate the cleaned
|
// The result may not contain all requested fields, so populate the cleaned
|
||||||
// result with the missing fields and their value being set to null
|
// 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)) {
|
if (! is_string($alias)) {
|
||||||
$alias = $name;
|
$alias = $name;
|
||||||
}
|
}
|
||||||
|
@ -1003,6 +1040,14 @@ class LdapConnection implements Selectable, Inspectable
|
||||||
&& isset($cleanedAttributes[$unfoldAttribute])
|
&& isset($cleanedAttributes[$unfoldAttribute])
|
||||||
&& is_array($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];
|
$values = $cleanedAttributes[$unfoldAttribute];
|
||||||
unset($cleanedAttributes[$unfoldAttribute]);
|
unset($cleanedAttributes[$unfoldAttribute]);
|
||||||
$baseRow = (object) $cleanedAttributes;
|
$baseRow = (object) $cleanedAttributes;
|
||||||
|
@ -1010,6 +1055,10 @@ class LdapConnection implements Selectable, Inspectable
|
||||||
foreach ($values as $value) {
|
foreach ($values as $value) {
|
||||||
$row = clone $baseRow;
|
$row = clone $baseRow;
|
||||||
$row->{$unfoldAttribute} = $value;
|
$row->{$unfoldAttribute} = $value;
|
||||||
|
foreach ($siblings as $sibling) {
|
||||||
|
$row->{$sibling} = $value;
|
||||||
|
}
|
||||||
|
|
||||||
$rows[] = $row;
|
$rows[] = $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1119,6 +1168,77 @@ class LdapConnection implements Selectable, Inspectable
|
||||||
return $ds;
|
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
|
* Create an LDAP entry
|
||||||
*
|
*
|
||||||
|
@ -1184,6 +1304,93 @@ class LdapConnection implements Selectable, Inspectable
|
||||||
return $dir;
|
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
|
* Inspect if this LDAP Connection is working as expected
|
||||||
*
|
*
|
||||||
|
|
|
@ -4,23 +4,12 @@
|
||||||
namespace Icinga\Protocol\Ldap;
|
namespace Icinga\Protocol\Ldap;
|
||||||
|
|
||||||
use Icinga\Data\SimpleQuery;
|
use Icinga\Data\SimpleQuery;
|
||||||
use Icinga\Data\Filter\Filter;
|
|
||||||
use Icinga\Exception\NotImplementedError;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LDAP query class
|
* LDAP query class
|
||||||
*/
|
*/
|
||||||
class LdapQuery extends SimpleQuery
|
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
|
* The base dn being used for this query
|
||||||
*
|
*
|
||||||
|
@ -42,12 +31,18 @@ class LdapQuery extends SimpleQuery
|
||||||
*/
|
*/
|
||||||
protected $unfoldAttribute;
|
protected $unfoldAttribute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This query's native LDAP filter
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $nativeFilter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize this query
|
* Initialize this query
|
||||||
*/
|
*/
|
||||||
protected function init()
|
protected function init()
|
||||||
{
|
{
|
||||||
$this->filters = array();
|
|
||||||
$this->usePagedResults = false;
|
$this->usePagedResults = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,6 +115,29 @@ class LdapQuery extends SimpleQuery
|
||||||
return $this->unfoldAttribute;
|
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
|
* 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)
|
public function from($target, array $fields = null)
|
||||||
{
|
{
|
||||||
$this->filters['objectClass'] = $target;
|
$this->where('objectClass', $target);
|
||||||
return parent::from($target, $fields);
|
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
|
* 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
|
* @return string
|
||||||
*
|
|
||||||
* @throws LdapException In case the objectClass filter does not exist
|
|
||||||
*/
|
*/
|
||||||
protected function renderFilter()
|
public function renderFilter()
|
||||||
{
|
{
|
||||||
if (! isset($this->filters['objectClass'])) {
|
$filter = $this->ds->renderFilter($this->filter);
|
||||||
throw new LdapException('Object class is mandatory');
|
if ($this->nativeFilter) {
|
||||||
|
$filter = '(&(' . $this->nativeFilter . ')' . $filter . ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
$parts = array();
|
return $filter;
|
||||||
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] . ')';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,6 +6,7 @@ namespace Icinga\Repository;
|
||||||
use Icinga\Data\Db\DbConnection;
|
use Icinga\Data\Db\DbConnection;
|
||||||
use Icinga\Data\Extensible;
|
use Icinga\Data\Extensible;
|
||||||
use Icinga\Data\Filter\Filter;
|
use Icinga\Data\Filter\Filter;
|
||||||
|
use Icinga\Data\Filter\FilterExpression;
|
||||||
use Icinga\Data\Reducible;
|
use Icinga\Data\Reducible;
|
||||||
use Icinga\Data\Updatable;
|
use Icinga\Data\Updatable;
|
||||||
use Icinga\Exception\IcingaException;
|
use Icinga\Exception\IcingaException;
|
||||||
|
@ -93,20 +94,20 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
||||||
protected $statementColumnAliasMap;
|
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,
|
* This list is being populated in case of a PostgreSQL backend only,
|
||||||
* to ensure case-insensitive string comparison in WHERE clauses.
|
* to ensure case-insensitive string comparison in WHERE clauses.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $columnsWithoutCollation;
|
protected $caseInsensitiveColumns;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new DB repository object
|
* Create a new DB repository object
|
||||||
*
|
*
|
||||||
* In case $this->queryColumns has already been initialized, this initializes
|
* 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
|
* @param DbConnection $ds The datasource to use
|
||||||
*/
|
*/
|
||||||
|
@ -114,7 +115,6 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
||||||
{
|
{
|
||||||
parent::__construct($ds);
|
parent::__construct($ds);
|
||||||
|
|
||||||
$this->columnsWithoutCollation = array();
|
|
||||||
if ($ds->getDbType() === 'pgsql' && $this->queryColumns !== null) {
|
if ($ds->getDbType() === 'pgsql' && $this->queryColumns !== null) {
|
||||||
$this->queryColumns = $this->removeCollateInstruction($this->queryColumns);
|
$this->queryColumns = $this->removeCollateInstruction($this->queryColumns);
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
||||||
/**
|
/**
|
||||||
* Return the query columns being provided
|
* 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
|
* @return array
|
||||||
*/
|
*/
|
||||||
|
@ -174,11 +174,12 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
||||||
*/
|
*/
|
||||||
protected function removeCollateInstruction($queryColumns)
|
protected function removeCollateInstruction($queryColumns)
|
||||||
{
|
{
|
||||||
foreach ($queryColumns as & $columns) {
|
foreach ($queryColumns as $table => & $columns) {
|
||||||
foreach ($columns as & $column) {
|
foreach ($columns as $alias => & $column) {
|
||||||
|
// Using a regex here because COLLATE may occur anywhere in the string
|
||||||
$column = preg_replace('/ COLLATE .+$/', '', $column, -1, $count);
|
$column = preg_replace('/ COLLATE .+$/', '', $column, -1, $count);
|
||||||
if ($count > 0) {
|
if ($count > 0) {
|
||||||
$this->columnsWithoutCollation[] = $column;
|
$this->caseInsensitiveColumns[$table][is_string($alias) ? $alias : $column] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -254,18 +255,25 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
||||||
* Return the given table with its alias being applied
|
* Return the given table with its alias being applied
|
||||||
*
|
*
|
||||||
* @param array|string $table
|
* @param array|string $table
|
||||||
|
* @param string $virtualTable
|
||||||
*
|
*
|
||||||
* @return array|string
|
* @return array|string
|
||||||
*/
|
*/
|
||||||
protected function applyTableAlias($table)
|
protected function applyTableAlias($table, $virtualTable = null)
|
||||||
{
|
{
|
||||||
$tableAliases = $this->getTableAliases();
|
$tableAliases = $this->getTableAliases();
|
||||||
if (is_array($table) || !isset($tableAliases[($nonPrefixedTable = $this->removeTablePrefix($table))])) {
|
if (! is_array($table)) {
|
||||||
return $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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the given table with its alias being cleared
|
* Return the given table with its alias being cleared
|
||||||
|
@ -294,10 +302,16 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
||||||
*
|
*
|
||||||
* @param string $table
|
* @param string $table
|
||||||
* @param array $bind
|
* @param array $bind
|
||||||
|
*
|
||||||
|
* @return int The number of affected rows
|
||||||
*/
|
*/
|
||||||
public function insert($table, array $bind)
|
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 string $table
|
||||||
* @param array $bind
|
* @param array $bind
|
||||||
* @param Filter $filter
|
* @param Filter $filter
|
||||||
|
*
|
||||||
|
* @return int The number of affected rows
|
||||||
*/
|
*/
|
||||||
public function update($table, array $bind, Filter $filter = null)
|
public function update($table, array $bind, Filter $filter = null)
|
||||||
{
|
{
|
||||||
|
$this->requireTable($table);
|
||||||
|
|
||||||
if ($filter) {
|
if ($filter) {
|
||||||
$filter = $this->requireFilter($table, $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 string $table
|
||||||
* @param Filter $filter
|
* @param Filter $filter
|
||||||
|
*
|
||||||
|
* @return int The number of affected rows
|
||||||
*/
|
*/
|
||||||
public function delete($table, Filter $filter = null)
|
public function delete($table, Filter $filter = null)
|
||||||
{
|
{
|
||||||
|
$this->requireTable($table);
|
||||||
|
|
||||||
if ($filter) {
|
if ($filter) {
|
||||||
$filter = $this->requireFilter($table, $filter);
|
$filter = $this->requireFilter($table, $filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->ds->delete($this->prependTablePrefix($table), $filter);
|
return $this->ds->delete($this->prependTablePrefix($table), $filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -468,7 +494,7 @@ 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
|
* 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.
|
* may be possible that columns from another table where joined in which would otherwise not being converted.
|
||||||
*
|
*
|
||||||
* @param array|string $table
|
* @param string $table
|
||||||
* @param string $column
|
* @param string $column
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
|
@ -477,10 +503,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
||||||
{
|
{
|
||||||
if ($column !== null) {
|
if ($column !== null) {
|
||||||
if ($this->validateQueryColumnAssociation($table, $column)) {
|
if ($this->validateQueryColumnAssociation($table, $column)) {
|
||||||
return parent::providesValueConversion(
|
return parent::providesValueConversion($table, $column);
|
||||||
$this->removeTablePrefix($this->clearTableAlias($table)),
|
|
||||||
$column
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($tableName = $this->findTableName($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)
|
protected function getConverter($table, $name, $context, RepositoryQuery $query = null)
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
($query !== null && $this->validateQueryColumnAssociation($table, $name))
|
! ($query !== null && $this->validateQueryColumnAssociation($table, $name))
|
||||||
|| ($query === null && $this->validateStatementColumnAssociation($table, $name))
|
&& !($query === null && $this->validateStatementColumnAssociation($table, $name))
|
||||||
) {
|
) {
|
||||||
$table = $this->removeTablePrefix($this->clearTableAlias($table));
|
|
||||||
} else {
|
|
||||||
$table = $this->findTableName($name);
|
$table = $this->findTableName($name);
|
||||||
if (! $table) {
|
if (! $table) {
|
||||||
throw new ProgrammingError('Column name validation seems to have failed. Did you require the column?');
|
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)
|
public function requireTable($table, RepositoryQuery $query = null)
|
||||||
{
|
{
|
||||||
|
$virtualTable = null;
|
||||||
$statementColumns = $this->getStatementColumns();
|
$statementColumns = $this->getStatementColumns();
|
||||||
if (! isset($statementColumns[$table])) {
|
if (! isset($statementColumns[$table])) {
|
||||||
$table = parent::requireTable($table);
|
$newTable = parent::requireTable($table);
|
||||||
|
if ($newTable !== $table) {
|
||||||
|
$virtualTable = $table;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->prependTablePrefix($this->applyTableAlias($table));
|
$table = $newTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->prependTablePrefix($this->applyTableAlias($table, $virtualTable));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recurse the given filter, require each column for the given table and convert all values
|
* Return the alias for the given table or null if none has been defined
|
||||||
*
|
*
|
||||||
* In case of a PostgreSQL connection, this applies LOWER() on the column and strtolower()
|
* @param string $table
|
||||||
* 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return this repository's query columns of the given table mapped to their respective aliases
|
|
||||||
*
|
|
||||||
* @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
|
|
||||||
*
|
*
|
||||||
* @return string|null
|
* @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
|
* 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 $table
|
||||||
* @param string $column
|
* @param string $column
|
||||||
*
|
*
|
||||||
* @return string|null
|
* @return string|null
|
||||||
*/
|
*/
|
||||||
public function reassembleQueryColumnAlias($table, $column)
|
public function reassembleQueryColumnAlias($table, $column)
|
||||||
{
|
{
|
||||||
$alias = parent::reassembleQueryColumnAlias($this->removeTablePrefix($this->clearTableAlias($table)), $column);
|
$alias = parent::reassembleQueryColumnAlias($table, $column);
|
||||||
if (
|
if (
|
||||||
$alias === null
|
$alias === null
|
||||||
&& !$this->validateQueryColumnAssociation($table, $column)
|
&& !$this->validateQueryColumnAssociation($table, $column)
|
||||||
|
@ -633,29 +614,13 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
||||||
return $alias;
|
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
|
* 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
|
* Attempts to join the given column from a different table if its association to the given table cannot be
|
||||||
* verified.
|
* 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 string $name The name or alias of the column to validate
|
||||||
* @param RepositoryQuery $query An optional query to pass as context,
|
* @param RepositoryQuery $query An optional query to pass as context,
|
||||||
* if not given no join will be attempted
|
* 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)
|
public function requireQueryColumn($table, $name, RepositoryQuery $query = null)
|
||||||
{
|
{
|
||||||
if ($query === null || $this->validateQueryColumnAssociation($table, $name)) {
|
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);
|
return $this->joinColumn($name, $table, $query);
|
||||||
|
@ -677,34 +642,64 @@ 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
|
* 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
|
* 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 string $name The name or alias of the column to validate
|
||||||
* @param RepositoryQuery $query An optional query to pass as context,
|
* @param RepositoryQuery $query An optional query to pass as context,
|
||||||
* if not given the column is considered being used for a statement filter
|
* 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
|
* @return string The given column's name
|
||||||
*
|
*
|
||||||
* @throws QueryException In case the given column is not a valid filter column
|
* @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) {
|
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)) {
|
if (! empty($this->caseInsensitiveColumns)) {
|
||||||
return parent::requireFilterColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name, $query);
|
if ($joined) {
|
||||||
|
$table = $this->findTableName($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->joinColumn($name, $table, $query);
|
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 $column;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the statement column name for the given alias or null in case the alias does not exist
|
* Return the statement column name for the given alias or null in case the alias does not exist
|
||||||
*
|
*
|
||||||
* @param array|string $table
|
* @param string $table
|
||||||
* @param string $alias
|
* @param string $alias
|
||||||
*
|
*
|
||||||
* @return string|null
|
* @return string|null
|
||||||
|
@ -716,7 +711,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
||||||
return $statementAliasColumnMap[$alias];
|
return $statementAliasColumnMap[$alias];
|
||||||
}
|
}
|
||||||
|
|
||||||
$prefixedAlias = $this->removeTablePrefix($this->clearTableAlias($table)) . '.' . $alias;
|
$prefixedAlias = $table . '.' . $alias;
|
||||||
if (isset($statementAliasColumnMap[$prefixedAlias])) {
|
if (isset($statementAliasColumnMap[$prefixedAlias])) {
|
||||||
return $statementAliasColumnMap[$prefixedAlias];
|
return $statementAliasColumnMap[$prefixedAlias];
|
||||||
}
|
}
|
||||||
|
@ -725,7 +720,7 @@ 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
|
* 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 $table
|
||||||
* @param string $column
|
* @param string $column
|
||||||
*
|
*
|
||||||
* @return string|null
|
* @return string|null
|
||||||
|
@ -737,7 +732,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
||||||
return $statementColumnAliasMap[$column];
|
return $statementColumnAliasMap[$column];
|
||||||
}
|
}
|
||||||
|
|
||||||
$prefixedColumn = $this->removeTablePrefix($this->clearTableAlias($table)) . '.' . $column;
|
$prefixedColumn = $table . '.' . $column;
|
||||||
if (isset($statementColumnAliasMap[$prefixedColumn])) {
|
if (isset($statementColumnAliasMap[$prefixedColumn])) {
|
||||||
return $statementColumnAliasMap[$prefixedColumn];
|
return $statementColumnAliasMap[$prefixedColumn];
|
||||||
}
|
}
|
||||||
|
@ -746,33 +741,31 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
||||||
/**
|
/**
|
||||||
* Return whether the given alias or statement column name is available in the given table
|
* Return whether the given alias or statement column name is available in the given table
|
||||||
*
|
*
|
||||||
* @param array|string $table
|
* @param string $table
|
||||||
* @param string $alias
|
* @param string $alias
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function validateStatementColumnAssociation($table, $alias)
|
public function validateStatementColumnAssociation($table, $alias)
|
||||||
{
|
{
|
||||||
$tableName = $this->removeTablePrefix($this->clearTableAlias($table));
|
|
||||||
|
|
||||||
$statementAliasTableMap = $this->getStatementAliasTableMap();
|
$statementAliasTableMap = $this->getStatementAliasTableMap();
|
||||||
if (isset($statementAliasTableMap[$alias])) {
|
if (isset($statementAliasTableMap[$alias])) {
|
||||||
return $statementAliasTableMap[$alias] === $tableName;
|
return $statementAliasTableMap[$alias] === $table;
|
||||||
}
|
}
|
||||||
|
|
||||||
$statementColumnTableMap = $this->getStatementColumnTableMap();
|
$statementColumnTableMap = $this->getStatementColumnTableMap();
|
||||||
if (isset($statementColumnTableMap[$alias])) {
|
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 isset($statementAliasTableMap[$prefixedAlias]) || isset($statementColumnTableMap[$prefixedAlias]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether the given column name or alias of the given table is a valid statement column
|
* 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 $table The table where to look for the column or alias
|
||||||
* @param string $name The column name or alias to check
|
* @param string $name The column name or alias to check
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
|
@ -784,7 +777,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
||||||
&& $this->reassembleStatementColumnAlias($table, $name) === null)
|
&& $this->reassembleStatementColumnAlias($table, $name) === null)
|
||||||
|| !$this->validateStatementColumnAssociation($table, $name)
|
|| !$this->validateStatementColumnAssociation($table, $name)
|
||||||
) {
|
) {
|
||||||
return parent::hasStatementColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name);
|
return parent::hasStatementColumn($table, $name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -793,7 +786,7 @@ 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
|
* 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 $table The table for which to require the column
|
||||||
* @param string $name The name or alias of the column to validate
|
* @param string $name The name or alias of the column to validate
|
||||||
*
|
*
|
||||||
* @return string The given column's name
|
* @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) {
|
} elseif (($alias = $this->reassembleStatementColumnAlias($table, $name)) !== null) {
|
||||||
$column = $name;
|
$column = $name;
|
||||||
} else {
|
} else {
|
||||||
return parent::requireStatementColumn($this->removeTablePrefix($this->clearTableAlias($table)), $name);
|
return parent::requireStatementColumn($table, $name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $this->validateStatementColumnAssociation($table, $alias)) {
|
if (! $this->validateStatementColumnAssociation($table, $alias)) {
|
||||||
throw new StatementException(
|
throw new StatementException('Statement column "%s" not found in table "%s"', $name, $table);
|
||||||
'Statement column "%s" not found in table "%s"',
|
|
||||||
$name,
|
|
||||||
$this->removeTablePrefix($this->clearTableAlias($table))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $column;
|
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.
|
* 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 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
|
* @param RepositoryQUery $query The query to apply the JOIN-clause on
|
||||||
*
|
*
|
||||||
* @return string The resolved alias or $name
|
* @return string The resolved alias or $name
|
||||||
|
@ -843,7 +832,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
||||||
throw new ProgrammingError(
|
throw new ProgrammingError(
|
||||||
'Unable to find a valid table for column "%s" to join into "%s"',
|
'Unable to find a valid table for column "%s" to join into "%s"',
|
||||||
$name,
|
$name,
|
||||||
$this->removeTablePrefix($this->clearTableAlias($target))
|
$target
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -851,8 +840,10 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
||||||
$column = $name;
|
$column = $name;
|
||||||
}
|
}
|
||||||
|
|
||||||
$prefixedTableName = $this->prependTablePrefix($tableName);
|
if (($joinIdentifier = $this->resolveTableAlias($tableName)) === null) {
|
||||||
if ($query->getQuery()->hasJoinedTable($prefixedTableName)) {
|
$joinIdentifier = $this->prependTablePrefix($tableName);
|
||||||
|
}
|
||||||
|
if ($query->getQuery()->hasJoinedTable($joinIdentifier)) {
|
||||||
return $column;
|
return $column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -861,7 +852,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
||||||
throw new ProgrammingError(
|
throw new ProgrammingError(
|
||||||
'Unable to join table "%s" into "%s". Method "%s" not found',
|
'Unable to join table "%s" into "%s". Method "%s" not found',
|
||||||
$tableName,
|
$tableName,
|
||||||
$this->removeTablePrefix($this->clearTableAlias($target)),
|
$target,
|
||||||
$joinMethod
|
$joinMethod
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,15 +42,6 @@ abstract class LdapRepository extends Repository
|
||||||
'groupofuniquenames' => 'groupOfUniqueNames'
|
'groupofuniquenames' => 'groupOfUniqueNames'
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* Object attributes whose value is not distinguished name
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $ambiguousAttributes = array(
|
|
||||||
'posixGroup' => 'memberUid'
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new LDAP repository object
|
* 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
|
* Will use the current connection's root DN if $baseDn is not given.
|
||||||
* @param string $attributeName
|
*
|
||||||
|
* @param string $dn The object DN to check
|
||||||
|
* @param string $baseDn The base DN to compare the object DN with
|
||||||
*
|
*
|
||||||
* @return bool
|
* @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 DateTime;
|
||||||
use Icinga\Application\Logger;
|
use Icinga\Application\Logger;
|
||||||
use Icinga\Data\Filter\Filter;
|
use Icinga\Data\Filter\Filter;
|
||||||
|
use Icinga\Data\Filter\FilterExpression;
|
||||||
use Icinga\Data\Selectable;
|
use Icinga\Data\Selectable;
|
||||||
use Icinga\Exception\ProgrammingError;
|
use Icinga\Exception\ProgrammingError;
|
||||||
use Icinga\Exception\QueryException;
|
use Icinga\Exception\QueryException;
|
||||||
|
@ -52,6 +53,16 @@ abstract class Repository implements Selectable
|
||||||
*/
|
*/
|
||||||
protected $baseTable;
|
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
|
* The query columns being provided
|
||||||
*
|
*
|
||||||
|
@ -255,6 +266,32 @@ abstract class Repository implements Selectable
|
||||||
return $this->baseTable;
|
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
|
* Return the query columns being provided
|
||||||
*
|
*
|
||||||
|
@ -581,7 +618,7 @@ abstract class Repository implements Selectable
|
||||||
{
|
{
|
||||||
$converter = $this->getConverter($table, $name, 'persist', $query);
|
$converter = $this->getConverter($table, $name, 'persist', $query);
|
||||||
if ($converter !== null) {
|
if ($converter !== null) {
|
||||||
$value = $this->$converter($value);
|
$value = $this->$converter($value, $name, $table, $query);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $value;
|
return $value;
|
||||||
|
@ -603,7 +640,7 @@ abstract class Repository implements Selectable
|
||||||
{
|
{
|
||||||
$converter = $this->getConverter($table, $name, 'retrieve', $query);
|
$converter = $this->getConverter($table, $name, 'retrieve', $query);
|
||||||
if ($converter !== null) {
|
if ($converter !== null) {
|
||||||
$value = $this->$converter($value);
|
$value = $this->$converter($value, $name, $table, $query);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $value;
|
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 string $table The table to validate
|
||||||
* @param RepositoryQuery $query An optional query to pass as context
|
* @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);
|
throw new ProgrammingError('Table "%s" not found', $table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$virtualTables = $this->getVirtualTables();
|
||||||
|
if (isset($virtualTables[$table])) {
|
||||||
|
$table = $virtualTables[$table];
|
||||||
|
}
|
||||||
|
|
||||||
return $table;
|
return $table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -831,8 +873,8 @@ abstract class Repository implements Selectable
|
||||||
|
|
||||||
if ($filter->isExpression()) {
|
if ($filter->isExpression()) {
|
||||||
$column = $filter->getColumn();
|
$column = $filter->getColumn();
|
||||||
$filter->setColumn($this->requireFilterColumn($table, $column, $query));
|
$filter->setColumn($this->requireFilterColumn($table, $column, $query, $filter));
|
||||||
$filter->setExpression($this->persistColumn($table, $column, $filter->getExpression()));
|
$filter->setExpression($this->persistColumn($table, $column, $filter->getExpression(), $query));
|
||||||
} elseif ($filter->isChain()) {
|
} elseif ($filter->isChain()) {
|
||||||
foreach ($filter->filters() as $chainOrExpression) {
|
foreach ($filter->filters() as $chainOrExpression) {
|
||||||
$this->requireFilter($table, $chainOrExpression, $query, false);
|
$this->requireFilter($table, $chainOrExpression, $query, false);
|
||||||
|
@ -862,7 +904,7 @@ abstract class Repository implements Selectable
|
||||||
$columns = array();
|
$columns = array();
|
||||||
foreach ($queryColumns[$table] as $alias => $column) {
|
foreach ($queryColumns[$table] as $alias => $column) {
|
||||||
if (! in_array(is_string($alias) ? $alias : $column, $blacklist)) {
|
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 $table The table where to look for the column or alias
|
||||||
* @param string $name The name or alias of the column to validate
|
* @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 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
|
* @return string The given column's name
|
||||||
*
|
*
|
||||||
* @throws QueryException In case the given column is not a valid filter column
|
* @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) {
|
if (($column = $this->resolveQueryColumnAlias($table, $name)) !== null) {
|
||||||
$alias = $name;
|
$alias = $name;
|
||||||
|
|
|
@ -47,6 +47,13 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
||||||
*/
|
*/
|
||||||
protected $iterator;
|
protected $iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This query's custom aliases
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $customAliases;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new repository query
|
* Create a new repository query
|
||||||
*
|
*
|
||||||
|
@ -79,8 +86,8 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
||||||
*/
|
*/
|
||||||
public function from($target, array $columns = null)
|
public function from($target, array $columns = null)
|
||||||
{
|
{
|
||||||
$target = $this->repository->requireTable($target, $this);
|
$this->query = $this->repository->getDataSource()->select();
|
||||||
$this->query = $this->repository->getDataSource()->select()->from($target);
|
$this->query->from($this->repository->requireTable($target, $this));
|
||||||
$this->query->columns($this->prepareQueryColumns($target, $columns));
|
$this->query->columns($this->prepareQueryColumns($target, $columns));
|
||||||
$this->target = $target;
|
$this->target = $target;
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -123,6 +130,7 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
||||||
*/
|
*/
|
||||||
protected function prepareQueryColumns($target, array $desiredColumns = null)
|
protected function prepareQueryColumns($target, array $desiredColumns = null)
|
||||||
{
|
{
|
||||||
|
$this->customAliases = array();
|
||||||
if (empty($desiredColumns)) {
|
if (empty($desiredColumns)) {
|
||||||
$columns = $this->repository->requireAllQueryColumns($target);
|
$columns = $this->repository->requireAllQueryColumns($target);
|
||||||
} else {
|
} else {
|
||||||
|
@ -130,9 +138,15 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
||||||
foreach ($desiredColumns as $customAlias => $columnAlias) {
|
foreach ($desiredColumns as $customAlias => $columnAlias) {
|
||||||
$resolvedColumn = $this->repository->requireQueryColumn($target, $columnAlias, $this);
|
$resolvedColumn = $this->repository->requireQueryColumn($target, $columnAlias, $this);
|
||||||
if ($resolvedColumn !== $columnAlias) {
|
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)) {
|
} elseif (is_string($customAlias)) {
|
||||||
$columns[$customAlias] = $columnAlias;
|
$columns[$customAlias] = $columnAlias;
|
||||||
|
$this->customAliases[$customAlias] = $columnAlias;
|
||||||
} else {
|
} else {
|
||||||
$columns[] = $columnAlias;
|
$columns[] = $columnAlias;
|
||||||
}
|
}
|
||||||
|
@ -142,6 +156,24 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
||||||
return $columns;
|
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
|
* 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)
|
public function where($column, $value = null)
|
||||||
{
|
{
|
||||||
$this->query->where(
|
$this->addFilter(Filter::where($column, $value));
|
||||||
$this->repository->requireFilterColumn($this->target, $column, $this),
|
|
||||||
$this->repository->persistColumn($this->target, $column, $value, $this)
|
|
||||||
);
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,7 +495,7 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
||||||
$result = $this->query->fetchOne();
|
$result = $this->query->fetchOne();
|
||||||
if ($result !== false && $this->repository->providesValueConversion($this->target)) {
|
if ($result !== false && $this->repository->providesValueConversion($this->target)) {
|
||||||
$columns = $this->getColumns();
|
$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);
|
return $this->repository->retrieveColumn($this->target, $column, $result, $this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -491,7 +520,12 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
||||||
$alias = $column;
|
$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)) {
|
if (! empty($results) && $this->repository->providesValueConversion($this->target)) {
|
||||||
$columns = $this->getColumns();
|
$columns = $this->getColumns();
|
||||||
$aliases = array_keys($columns);
|
$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)) {
|
if ($this->repository->providesValueConversion($this->target, $column)) {
|
||||||
foreach ($results as & $value) {
|
foreach ($results as & $value) {
|
||||||
$value = $this->repository->retrieveColumn($this->target, $column, $value, $this);
|
$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)) {
|
if (! empty($results) && $this->repository->providesValueConversion($this->target)) {
|
||||||
$columns = $this->getColumns();
|
$columns = $this->getColumns();
|
||||||
$aliases = array_keys($columns);
|
$aliases = array_keys($columns);
|
||||||
$colOne = $aliases[0] !== 0 ? $aliases[0] : $columns[0];
|
$colOne = $aliases[0] !== 0 ? $this->getNativeAlias($aliases[0]) : $columns[0];
|
||||||
$colTwo = count($aliases) < 2 ? $colOne : ($aliases[1] !== 1 ? $aliases[1] : $columns[1]);
|
$colTwo = count($aliases) < 2 ? $colOne : (
|
||||||
|
$aliases[1] !== 1 ? $this->getNativeAlias($aliases[1]) : $columns[1]
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
$this->repository->providesValueConversion($this->target, $colOne)
|
$this->repository->providesValueConversion($this->target, $colOne)
|
||||||
|| $this->repository->providesValueConversion($this->target, $colTwo)
|
|| $this->repository->providesValueConversion($this->target, $colTwo)
|
||||||
|
@ -587,21 +624,28 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
||||||
$alias = $column;
|
$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) {
|
foreach (($this->getOrder() ?: array()) as $rule) {
|
||||||
|
$nativeAlias = $this->getNativeAlias($rule[0]);
|
||||||
if (! array_key_exists($rule[0], $flippedColumns) && property_exists($row, $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;
|
$updateOrder = true;
|
||||||
$row->{$rule[0]} = $this->repository->retrieveColumn(
|
$row->{$rule[0]} = $this->repository->retrieveColumn(
|
||||||
$this->target,
|
$this->target,
|
||||||
$rule[0],
|
$nativeAlias,
|
||||||
$row->{$rule[0]}
|
$row->{$rule[0]},
|
||||||
|
$this
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} elseif (array_key_exists($rule[0], $flippedColumns)) {
|
} elseif (array_key_exists($rule[0], $flippedColumns)) {
|
||||||
if ($this->repository->providesValueConversion($this->target, $rule[0])) {
|
if ($this->repository->providesValueConversion($this->target, $nativeAlias)) {
|
||||||
$updateOrder = true;
|
$updateOrder = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -677,7 +721,12 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
||||||
$alias = $column;
|
$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 Filterable $filterable The filterable to create a filter editor for
|
||||||
* @param array $filterColumns The filter columns to offer to the user
|
* @param array $filterColumns The filter columns to offer to the user
|
||||||
* @param array $searchColumns The search columns to utilize for quick searches
|
* @param array $searchColumns The search columns to utilize for quick searches
|
||||||
|
* @param array $preserveParams The url parameters to preserve
|
||||||
*
|
*
|
||||||
* @return $this
|
* @return $this
|
||||||
*
|
*
|
||||||
|
@ -194,25 +195,27 @@ class Controller extends ModuleActionController
|
||||||
protected function setupFilterControl(
|
protected function setupFilterControl(
|
||||||
Filterable $filterable,
|
Filterable $filterable,
|
||||||
array $filterColumns = null,
|
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)
|
->setQuery($filterable)
|
||||||
->preserveParams(
|
->ignoreParams('page') // setupPaginationControl()
|
||||||
'limit',
|
|
||||||
'sort',
|
|
||||||
'dir',
|
|
||||||
'format',
|
|
||||||
'view',
|
|
||||||
'user',
|
|
||||||
'group',
|
|
||||||
'backend',
|
|
||||||
'stateType',
|
|
||||||
'addColumns',
|
|
||||||
'problems',
|
|
||||||
'_dev'
|
|
||||||
)
|
|
||||||
->ignoreParams('page')
|
|
||||||
->setColumns($filterColumns)
|
->setColumns($filterColumns)
|
||||||
->setSearchColumns($searchColumns)
|
->setSearchColumns($searchColumns)
|
||||||
->handleRequest($this->getRequest());
|
->handleRequest($this->getRequest());
|
||||||
|
|
|
@ -171,7 +171,7 @@ class UrlParams
|
||||||
|
|
||||||
protected function urlEncode($value)
|
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)
|
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);
|
$this->handleFormatRequest($dataView);
|
||||||
return $dataView;
|
return $dataView;
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,8 +73,8 @@ $stateBadges
|
||||||
'host_state' => 2,
|
'host_state' => 2,
|
||||||
'host_handled' => 1
|
'host_handled' => 1
|
||||||
),
|
),
|
||||||
'List %u host that is 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) in the host group "%s"',
|
'List %u hosts which are currently in state UNREACHABLE (Acknowledged)',
|
||||||
array($stats->hosts_unreachable_handled)
|
array($stats->hosts_unreachable_handled)
|
||||||
)
|
)
|
||||||
->add(
|
->add(
|
||||||
|
@ -83,8 +83,8 @@ $stateBadges
|
||||||
array(
|
array(
|
||||||
'host_state' => 99
|
'host_state' => 99
|
||||||
),
|
),
|
||||||
'List %u host that is currently in state UNREACHABLE (Acknowledged)',
|
'List %u host that is currently in state PENDING',
|
||||||
'List %u hosts which are currently in state UNREACHABLE (Acknowledged)',
|
'List %u hosts which are currently in state PENDING',
|
||||||
array($stats->hosts_pending)
|
array($stats->hosts_pending)
|
||||||
);
|
);
|
||||||
echo $stateBadges->render();
|
echo $stateBadges->render();
|
||||||
|
|
|
@ -75,7 +75,7 @@ class HostserviceproblemsummaryQuery extends IdoQuery
|
||||||
's.service_object_id = so.object_id AND so.is_active = 1',
|
's.service_object_id = so.object_id AND so.is_active = 1',
|
||||||
array()
|
array()
|
||||||
);
|
);
|
||||||
$this->select->group(array('so.object_id'));
|
$this->select->group('so.name1');
|
||||||
$this->joinedVirtualTables['services'] = true;
|
$this->joinedVirtualTables['services'] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,14 +55,14 @@ abstract class IdoQuery extends DbQuery
|
||||||
protected $prefix;
|
protected $prefix;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array to map aliases to table names
|
* An array to map aliases to column names
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $idxAliasColumn;
|
protected $idxAliasColumn;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array to map aliases to column names
|
* An array to map aliases to table names
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
|
@ -400,14 +400,14 @@ abstract class IdoQuery extends DbQuery
|
||||||
protected static $idoVersion;
|
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,
|
* This list is being populated in case of a PostgreSQL backend only,
|
||||||
* to ensure case-insensitive string comparison in WHERE clauses.
|
* to ensure case-insensitive string comparison in WHERE clauses.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $columnsWithoutCollation = array();
|
protected $caseInsensitiveColumns;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true when the column is an aggregate column
|
* 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 string $alias The column alias to order by
|
||||||
* @param int $dir The sort direction or null to use default direction
|
* @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->requireColumn($alias);
|
||||||
$this->orderColumns[$columnOrAlias] = $columnOrAlias;
|
|
||||||
if ($this->isCustomvar($columnOrAlias)) {
|
if ($this->isCustomvar($alias)) {
|
||||||
$columnOrAlias = $this->getCustomvarColumnName($columnOrAlias);
|
$column = $this->getCustomvarColumnName($alias);
|
||||||
} elseif ($this->hasAliasName($columnOrAlias)) {
|
} elseif ($this->hasAliasName($alias)) {
|
||||||
$columnOrAlias = $this->aliasToColumnName($columnOrAlias);
|
$column = $this->aliasToColumnName($alias);
|
||||||
|
$table = $this->aliasToTableName($alias);
|
||||||
|
if (isset($this->caseInsensitiveColumns[$table][$alias])) {
|
||||||
|
$column = 'LOWER(' . $column . ')';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger::info('Can\'t order by column ' . $columnOrAlias);
|
Logger::info('Can\'t order by column ' . $alias);
|
||||||
return $this;
|
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() === '*') {
|
if ($filter->getExpression() === '*') {
|
||||||
return; // Wildcard only filters are ignored so stop early here to avoid joining a table for nothing
|
return; // Wildcard only filters are ignored so stop early here to avoid joining a table for nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
$alias = $filter->getColumn();
|
$alias = $filter->getColumn();
|
||||||
$this->requireColumn($alias);
|
$this->requireColumn($alias);
|
||||||
|
|
||||||
if ($this->isCustomvar($alias)) {
|
if ($this->isCustomvar($alias)) {
|
||||||
$column = $this->getCustomvarColumnName($alias);
|
$column = $this->getCustomvarColumnName($alias);
|
||||||
} else {
|
} else {
|
||||||
$column = $this->aliasToColumnName($alias);
|
$column = $this->aliasToColumnName($alias);
|
||||||
}
|
if (isset($this->caseInsensitiveColumns[$this->aliasToTableName($alias)][$alias])) {
|
||||||
if (isset($this->columnsWithoutCollation[$alias])) {
|
$column = 'LOWER(' . $column . ')';
|
||||||
$expression = $filter->getExpression();
|
$expression = $filter->getExpression();
|
||||||
if (is_array($expression)) {
|
if (is_array($expression)) {
|
||||||
$filter->setExpression(array_map('strtolower', $expression));
|
$filter->setExpression(array_map('strtolower', $expression));
|
||||||
} else {
|
} else {
|
||||||
$filter->setExpression(strtolower($expression));
|
$filter->setExpression(strtolower($expression));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$filter->setColumn($column);
|
$filter->setColumn($column);
|
||||||
} else {
|
} else {
|
||||||
foreach ($filter->filters() as $filter) {
|
foreach ($filter->filters() as $filter) {
|
||||||
|
@ -524,6 +533,7 @@ abstract class IdoQuery extends DbQuery
|
||||||
if ($value === '*') {
|
if ($value === '*') {
|
||||||
return $this; // Wildcard only filters are ignored so stop early here to avoid joining a table for nothing
|
return $this; // Wildcard only filters are ignored so stop early here to avoid joining a table for nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->requireColumn($condition);
|
$this->requireColumn($condition);
|
||||||
$col = $this->getMappedField($condition);
|
$col = $this->getMappedField($condition);
|
||||||
if ($col === null) {
|
if ($col === null) {
|
||||||
|
@ -551,29 +561,39 @@ 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
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isCaseInsensitive($aliasOrColumn)
|
public function isCaseInsensitive($alias)
|
||||||
{
|
{
|
||||||
if ($this->isCustomVar($aliasOrColumn)) {
|
if ($this->isCustomVar($alias)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$column = $this->getMappedField($aliasOrColumn) ?: $aliasOrColumn;
|
$column = $this->getMappedField($alias);
|
||||||
if (! $column) {
|
if (! $column) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! empty($this->columnsWithoutCollation)) {
|
if (empty($this->caseInsensitiveColumns)) {
|
||||||
return in_array($column, $this->columnsWithoutCollation) || strpos($column, 'LOWER') !== 0;
|
|
||||||
}
|
|
||||||
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]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply oracle specific query initialization
|
* Apply oracle specific query initialization
|
||||||
*/
|
*/
|
||||||
|
@ -603,10 +623,12 @@ abstract class IdoQuery extends DbQuery
|
||||||
'%1$s = %2$s.object_id AND LOWER(%2$s.varname) = %3$s';
|
'%1$s = %2$s.object_id AND LOWER(%2$s.varname) = %3$s';
|
||||||
foreach ($this->columnMap as $table => & $columns) {
|
foreach ($this->columnMap as $table => & $columns) {
|
||||||
foreach ($columns as $alias => & $column) {
|
foreach ($columns as $alias => & $column) {
|
||||||
if (false !== $pos = strpos($column, ' COLLATE')) {
|
// Using a regex here because COLLATE may occur anywhere in the string
|
||||||
$column = 'LOWER(' . substr($column, 0, $pos) . ')';
|
$column = preg_replace('/ COLLATE .+$/', '', $column, -1, $count);
|
||||||
$this->columnsWithoutCollation[$alias] = true;
|
if ($count > 0) {
|
||||||
|
$this->caseInsensitiveColumns[$table][$alias] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$column = preg_replace(
|
$column = preg_replace(
|
||||||
'/inet_aton\(([[:word:].]+)\)/i',
|
'/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)',
|
'(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_address',
|
||||||
'host_address6',
|
'host_address6',
|
||||||
'host_state',
|
'host_state',
|
||||||
|
'host_hard_state',
|
||||||
'host_state_type',
|
'host_state_type',
|
||||||
'host_handled',
|
'host_handled',
|
||||||
'host_unhandled',
|
'host_unhandled',
|
||||||
|
|
|
@ -15,6 +15,7 @@ class ServiceStatus extends DataView
|
||||||
'host_name',
|
'host_name',
|
||||||
'host_display_name',
|
'host_display_name',
|
||||||
'host_state',
|
'host_state',
|
||||||
|
'host_hard_state',
|
||||||
'host_state_type',
|
'host_state_type',
|
||||||
'host_last_state_change',
|
'host_last_state_change',
|
||||||
'host_address',
|
'host_address',
|
||||||
|
@ -24,6 +25,7 @@ class ServiceStatus extends DataView
|
||||||
'service_description',
|
'service_description',
|
||||||
'service_display_name',
|
'service_display_name',
|
||||||
'service_state',
|
'service_state',
|
||||||
|
'service_hard_state',
|
||||||
'service_in_downtime',
|
'service_in_downtime',
|
||||||
'service_acknowledged',
|
'service_acknowledged',
|
||||||
'service_handled',
|
'service_handled',
|
||||||
|
|
|
@ -44,41 +44,15 @@ class MacroTest extends BaseTestCase
|
||||||
$this->assertEquals(Macro::resolveMacros('$service.description$', $svcMock), $svcMock->service_description);
|
$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()
|
public function testFaultyMacros()
|
||||||
{
|
{
|
||||||
$hostMock = Mockery::mock('host');
|
$hostMock = Mockery::mock('host');
|
||||||
$hostMock->host_name = 'test';
|
$hostMock->host_name = 'test';
|
||||||
$hostMock->customvars = array(
|
$hostMock->host = 'te';
|
||||||
'host' => 'te',
|
|
||||||
'name' => 'st'
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
Macro::resolveMacros('$$HOSTNAME$ $ HOSTNAME$ $HOST$NAME$', $hostMock),
|
'$test $ HOSTNAME$ teNAME$',
|
||||||
'$test $ HOSTNAME$ teNAME$'
|
Macro::resolveMacros('$$HOSTNAME$ $ HOSTNAME$ $host$NAME$', $hostMock)
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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']
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
form.backend-selection {
|
||||||
float: right;
|
float: right;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue