mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-28 16:24:04 +02:00
Auth: Support single inheritance in roles
This commit is contained in:
parent
87d741265e
commit
bdd0f204f0
@ -157,6 +157,19 @@ class RoleForm extends RepositoryForm
|
|||||||
'description' => $this->translate('The name of the role')
|
'description' => $this->translate('The name of the role')
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
$this->addElement(
|
||||||
|
'select',
|
||||||
|
'parent',
|
||||||
|
[
|
||||||
|
'label' => $this->translate('Inherit From'),
|
||||||
|
'description' => $this->translate('Choose a role from which to inherit privileges'),
|
||||||
|
'value' => '',
|
||||||
|
'multiOptions' => array_merge(
|
||||||
|
['' => $this->translate('None', 'parent role')],
|
||||||
|
$this->collectRoles()
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
$this->addElement(
|
$this->addElement(
|
||||||
'textarea',
|
'textarea',
|
||||||
'users',
|
'users',
|
||||||
@ -322,6 +335,7 @@ class RoleForm extends RepositoryForm
|
|||||||
}
|
}
|
||||||
|
|
||||||
$values = [
|
$values = [
|
||||||
|
'parent' => $role->parent,
|
||||||
'name' => $role->name,
|
'name' => $role->name,
|
||||||
'users' => $role->users,
|
'users' => $role->users,
|
||||||
'groups' => $role->groups,
|
'groups' => $role->groups,
|
||||||
@ -431,6 +445,36 @@ class RoleForm extends RepositoryForm
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function collectRoles()
|
||||||
|
{
|
||||||
|
// Function to get all connected children. Used to avoid reference loops
|
||||||
|
$getChildren = function ($name, $children = []) use (&$getChildren) {
|
||||||
|
foreach ($this->repository->select()->where('parent', $name) as $child) {
|
||||||
|
if (isset($children[$child->name])) {
|
||||||
|
// Don't follow already established loops here,
|
||||||
|
// the user should be able to solve such in the UI
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$children[$child->name] = true;
|
||||||
|
$children = $getChildren($child->name, $children);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $children;
|
||||||
|
};
|
||||||
|
|
||||||
|
$children = $this->getIdentifier() !== null ? $getChildren($this->getIdentifier()) : [];
|
||||||
|
|
||||||
|
$names = [];
|
||||||
|
foreach ($this->repository->select() as $role) {
|
||||||
|
if ($role->name !== $this->getIdentifier() && ! isset($children[$role->name])) {
|
||||||
|
$names[] = $role->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_combine($names, $names);
|
||||||
|
}
|
||||||
|
|
||||||
public function isValid($formData)
|
public function isValid($formData)
|
||||||
{
|
{
|
||||||
$valid = parent::isValid($formData);
|
$valid = parent::isValid($formData);
|
||||||
@ -452,6 +496,14 @@ class RoleForm extends RepositoryForm
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (($newName = $this->getValue('name')) !== $this->getIdentifier()) {
|
||||||
|
$this->repository->update(
|
||||||
|
$this->getBaseTable(),
|
||||||
|
['parent' => $newName],
|
||||||
|
Filter::where('parent', $this->getIdentifier())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (ConfigFormEventsHook::runOnSuccess($this) === false) {
|
if (ConfigFormEventsHook::runOnSuccess($this) === false) {
|
||||||
Notification::error($this->translate(
|
Notification::error($this->translate(
|
||||||
'Configuration successfully stored. Though, one or more module hooks failed to run.'
|
'Configuration successfully stored. Though, one or more module hooks failed to run.'
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
<th><?= $this->translate('Name') ?></th>
|
<th><?= $this->translate('Name') ?></th>
|
||||||
<th><?= $this->translate('Users') ?></th>
|
<th><?= $this->translate('Users') ?></th>
|
||||||
<th><?= $this->translate('Groups') ?></th>
|
<th><?= $this->translate('Groups') ?></th>
|
||||||
|
<th><?= $this->translate('Inherits From') ?></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -44,6 +45,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td><?= $this->escape($role->users) ?></td>
|
<td><?= $this->escape($role->users) ?></td>
|
||||||
<td><?= $this->escape($role->groups) ?></td>
|
<td><?= $this->escape($role->groups) ?></td>
|
||||||
|
<td><?= $this->escape($role->parent) ?></td>
|
||||||
<td class="icon-col text-right">
|
<td class="icon-col text-right">
|
||||||
<?= $this->qlink(
|
<?= $this->qlink(
|
||||||
'',
|
'',
|
||||||
|
@ -3,9 +3,10 @@
|
|||||||
|
|
||||||
namespace Icinga\Authentication;
|
namespace Icinga\Authentication;
|
||||||
|
|
||||||
|
use Generator;
|
||||||
use Icinga\Application\Config;
|
use Icinga\Application\Config;
|
||||||
use Icinga\Application\Logger;
|
use Icinga\Application\Logger;
|
||||||
use Icinga\Authentication\Role;
|
use Icinga\Exception\ConfigurationError;
|
||||||
use Icinga\Exception\NotReadableError;
|
use Icinga\Exception\NotReadableError;
|
||||||
use Icinga\Data\ConfigObject;
|
use Icinga\Data\ConfigObject;
|
||||||
use Icinga\User;
|
use Icinga\User;
|
||||||
@ -16,7 +17,24 @@ use Icinga\Util\StringHelper;
|
|||||||
*/
|
*/
|
||||||
class AdmissionLoader
|
class AdmissionLoader
|
||||||
{
|
{
|
||||||
|
/** @var Role[] */
|
||||||
|
protected $roles;
|
||||||
|
|
||||||
|
/** @var ConfigObject */
|
||||||
|
protected $roleConfig;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->roleConfig = Config::app('roles');
|
||||||
|
} catch (NotReadableError $e) {
|
||||||
|
Logger::error('Can\'t access roles configuration. An exception was thrown:', $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Whether the user or groups are a member of the role
|
||||||
|
*
|
||||||
* @param string $username
|
* @param string $username
|
||||||
* @param array $userGroups
|
* @param array $userGroups
|
||||||
* @param ConfigObject $section
|
* @param ConfigObject $section
|
||||||
@ -31,10 +49,12 @@ class AdmissionLoader
|
|||||||
if (in_array('*', $users)) {
|
if (in_array('*', $users)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (in_array($username, $users)) {
|
if (in_array($username, $users)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! empty($section->groups)) {
|
if (! empty($section->groups)) {
|
||||||
$groups = array_map('strtolower', StringHelper::trimSplit($section->groups));
|
$groups = array_map('strtolower', StringHelper::trimSplit($section->groups));
|
||||||
foreach ($userGroups as $userGroup) {
|
foreach ($userGroups as $userGroup) {
|
||||||
@ -43,9 +63,70 @@ class AdmissionLoader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process role configuration and yield resulting roles
|
||||||
|
*
|
||||||
|
* This will also resolve any parent-child relationships.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param ConfigObject $section
|
||||||
|
*
|
||||||
|
* @return Generator
|
||||||
|
* @throws ConfigurationError
|
||||||
|
*/
|
||||||
|
protected function loadRole($name, ConfigObject $section)
|
||||||
|
{
|
||||||
|
if (! isset($this->roles[$name])) {
|
||||||
|
$permissions = StringHelper::trimSplit($section->permissions);
|
||||||
|
$refusals = StringHelper::trimSplit($section->refusals);
|
||||||
|
|
||||||
|
$restrictions = $section->toArray();
|
||||||
|
unset($restrictions['users'], $restrictions['groups']);
|
||||||
|
unset($restrictions['refusals'], $restrictions['permissions']);
|
||||||
|
|
||||||
|
$role = new Role();
|
||||||
|
$this->roles[$name] = $role
|
||||||
|
->setName($name)
|
||||||
|
->setRefusals($refusals)
|
||||||
|
->setPermissions($permissions)
|
||||||
|
->setRestrictions($restrictions);
|
||||||
|
|
||||||
|
if (isset($section->parent)) {
|
||||||
|
$parentName = $section->parent;
|
||||||
|
if (! $this->roleConfig->hasSection($parentName)) {
|
||||||
|
Logger::error(
|
||||||
|
'Failed to parse authentication configuration: Missing parent role "%s" (required by "%s")',
|
||||||
|
$parentName,
|
||||||
|
$name
|
||||||
|
);
|
||||||
|
throw new ConfigurationError(
|
||||||
|
t('Unable to parse authentication configuration. Check the log for more details.')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->loadRole($parentName, $this->roleConfig->getSection($parentName)) as $parent) {
|
||||||
|
if ($parent->getName() === $parentName) {
|
||||||
|
$role->setParent($parent);
|
||||||
|
$parent->addChild($role);
|
||||||
|
|
||||||
|
// Only yield main role once fully assembled
|
||||||
|
yield $role;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield $parent;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
yield $role;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
yield $this->roles[$name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply permissions, restrictions and roles to the given user
|
* Apply permissions, restrictions and roles to the given user
|
||||||
*
|
*
|
||||||
@ -53,51 +134,36 @@ class AdmissionLoader
|
|||||||
*/
|
*/
|
||||||
public function applyRoles(User $user)
|
public function applyRoles(User $user)
|
||||||
{
|
{
|
||||||
$username = $user->getUsername();
|
if ($this->roleConfig === null) {
|
||||||
try {
|
|
||||||
$roles = Config::app('roles');
|
|
||||||
} catch (NotReadableError $e) {
|
|
||||||
Logger::error(
|
|
||||||
'Can\'t get permissions and restrictions for user \'%s\'. An exception was thrown:',
|
|
||||||
$username,
|
|
||||||
$e
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$username = $user->getUsername();
|
||||||
$userGroups = $user->getGroups();
|
$userGroups = $user->getGroups();
|
||||||
$permissions = array();
|
|
||||||
$restrictions = array();
|
$roles = [];
|
||||||
$roleObjs = array();
|
$permissions = [];
|
||||||
foreach ($roles as $roleName => $role) {
|
$restrictions = [];
|
||||||
if ($this->match($username, $userGroups, $role)) {
|
foreach ($this->roleConfig as $roleName => $roleConfig) {
|
||||||
$permissionsFromRole = StringHelper::trimSplit($role->permissions);
|
if (! isset($roles[$roleName]) && $this->match($username, $userGroups, $roleConfig)) {
|
||||||
$refusals = StringHelper::trimSplit($role->refusals);
|
foreach ($this->loadRole($roleName, $roleConfig) as $role) {
|
||||||
|
/** @var Role $role */
|
||||||
|
$roles[$role->getName()] = $role;
|
||||||
|
|
||||||
$permissions = array_merge(
|
$permissions = array_merge(
|
||||||
$permissions,
|
$permissions,
|
||||||
array_diff($permissionsFromRole, $permissions)
|
array_diff($role->getPermissions(), $permissions)
|
||||||
);
|
);
|
||||||
$restrictionsFromRole = $role->toArray();
|
|
||||||
unset($restrictionsFromRole['users']);
|
foreach ($role->getRestrictions() as $name => $restriction) {
|
||||||
unset($restrictionsFromRole['groups']);
|
|
||||||
unset($restrictionsFromRole['refusals']);
|
|
||||||
unset($restrictionsFromRole['permissions']);
|
|
||||||
foreach ($restrictionsFromRole as $name => $restriction) {
|
|
||||||
if (! isset($restrictions[$name])) {
|
|
||||||
$restrictions[$name] = array();
|
|
||||||
}
|
|
||||||
$restrictions[$name][] = $restriction;
|
$restrictions[$name][] = $restriction;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$roleObj = new Role();
|
|
||||||
$roleObjs[] = $roleObj
|
|
||||||
->setName($roleName)
|
|
||||||
->setRefusals($refusals)
|
|
||||||
->setPermissions($permissionsFromRole)
|
|
||||||
->setRestrictions($restrictionsFromRole);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$user->setPermissions($permissions);
|
|
||||||
$user->setRestrictions($restrictions);
|
$user->setRestrictions($restrictions);
|
||||||
$user->setRoles($roleObjs);
|
$user->setPermissions($permissions);
|
||||||
|
$user->setRoles(array_values($roles));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,20 @@ class Role
|
|||||||
*/
|
*/
|
||||||
protected $name;
|
protected $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The role from which to inherit privileges
|
||||||
|
*
|
||||||
|
* @var Role
|
||||||
|
*/
|
||||||
|
protected $parent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The roles to which privileges are inherited
|
||||||
|
*
|
||||||
|
* @var Role[]
|
||||||
|
*/
|
||||||
|
protected $children;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Permissions of the role
|
* Permissions of the role
|
||||||
*
|
*
|
||||||
@ -57,6 +71,68 @@ class Role
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the role from which privileges are inherited
|
||||||
|
*
|
||||||
|
* @return Role
|
||||||
|
*/
|
||||||
|
public function getParent()
|
||||||
|
{
|
||||||
|
return $this->parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the role from which to inherit privileges
|
||||||
|
*
|
||||||
|
* @param Role $parent
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setParent(Role $parent)
|
||||||
|
{
|
||||||
|
$this->parent = $parent;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the roles to which privileges are inherited
|
||||||
|
*
|
||||||
|
* @return Role[]
|
||||||
|
*/
|
||||||
|
public function getChildren()
|
||||||
|
{
|
||||||
|
return $this->children;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the roles to which inherit privileges
|
||||||
|
*
|
||||||
|
* @param Role[] $children
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setChildren(array $children)
|
||||||
|
{
|
||||||
|
$this->children = $children;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a role to which inherit privileges
|
||||||
|
*
|
||||||
|
* @param Role $role
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addChild(Role $role)
|
||||||
|
{
|
||||||
|
$this->children[] = $role;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the permissions of the role
|
* Get the permissions of the role
|
||||||
*
|
*
|
||||||
@ -145,10 +221,11 @@ class Role
|
|||||||
* Whether this role grants the given permission
|
* Whether this role grants the given permission
|
||||||
*
|
*
|
||||||
* @param string $permission
|
* @param string $permission
|
||||||
|
* @param bool $ignoreParent Only evaluate the role's own permissions
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function grants($permission)
|
public function grants($permission, $ignoreParent = false)
|
||||||
{
|
{
|
||||||
foreach ($this->permissions as $grantedPermission) {
|
foreach ($this->permissions as $grantedPermission) {
|
||||||
if ($this->match($grantedPermission, $permission)) {
|
if ($this->match($grantedPermission, $permission)) {
|
||||||
@ -156,6 +233,10 @@ class Role
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! $ignoreParent && $this->getParent() !== null) {
|
||||||
|
return $this->getParent()->grants($permission);
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,10 +244,11 @@ class Role
|
|||||||
* Whether this role denies the given permission
|
* Whether this role denies the given permission
|
||||||
*
|
*
|
||||||
* @param string $permission
|
* @param string $permission
|
||||||
|
* @param bool $ignoreParent Only evaluate the role's own refusals
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function denies($permission)
|
public function denies($permission, $ignoreParent = false)
|
||||||
{
|
{
|
||||||
foreach ($this->refusals as $refusedPermission) {
|
foreach ($this->refusals as $refusedPermission) {
|
||||||
if ($this->match($refusedPermission, $permission, false)) {
|
if ($this->match($refusedPermission, $permission, false)) {
|
||||||
@ -174,6 +256,10 @@ class Role
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! $ignoreParent && $this->getParent() !== null) {
|
||||||
|
return $this->getParent()->denies($permission);
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ class RolesConfig extends IniRepository
|
|||||||
{
|
{
|
||||||
$columns = [
|
$columns = [
|
||||||
'roles' => [
|
'roles' => [
|
||||||
|
'parent',
|
||||||
'name',
|
'name',
|
||||||
'users',
|
'users',
|
||||||
'groups',
|
'groups',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user