246 lines
9.0 KiB
PHP
246 lines
9.0 KiB
PHP
<?php
|
|
/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
|
|
|
|
namespace Icinga\Authentication;
|
|
|
|
use Generator;
|
|
use Icinga\Application\Config;
|
|
use Icinga\Application\Logger;
|
|
use Icinga\Exception\ConfigurationError;
|
|
use Icinga\Exception\NotReadableError;
|
|
use Icinga\Data\ConfigObject;
|
|
use Icinga\User;
|
|
use Icinga\Util\StringHelper;
|
|
|
|
/**
|
|
* Retrieve restrictions and permissions for users
|
|
*/
|
|
class AdmissionLoader
|
|
{
|
|
const LEGACY_PERMISSIONS = [
|
|
'admin' => 'application/announcements',
|
|
'application/stacktraces' => 'user/application/stacktraces',
|
|
'application/share/navigation' => 'user/share/navigation',
|
|
// Migrating config/application/* would include config/modules, so that's skipped
|
|
//'config/application/*' => 'config/*',
|
|
'config/application/general' => 'config/general',
|
|
'config/application/resources' => 'config/resources',
|
|
'config/application/navigation' => 'config/navigation',
|
|
'config/application/userbackend' => 'config/access-control/users',
|
|
'config/application/usergroupbackend' => 'config/access-control/groups',
|
|
'config/authentication/*' => 'config/access-control/*',
|
|
'config/authentication/users/*' => 'config/access-control/users',
|
|
'config/authentication/users/show' => 'config/access-control/users',
|
|
'config/authentication/users/add' => 'config/access-control/users',
|
|
'config/authentication/users/edit' => 'config/access-control/users',
|
|
'config/authentication/users/remove' => 'config/access-control/users',
|
|
'config/authentication/groups/*' => 'config/access-control/groups',
|
|
'config/authentication/groups/show' => 'config/access-control/groups',
|
|
'config/authentication/groups/edit' => 'config/access-control/groups',
|
|
'config/authentication/groups/add' => 'config/access-control/groups',
|
|
'config/authentication/groups/remove' => 'config/access-control/groups',
|
|
'config/authentication/roles/*' => 'config/access-control/roles',
|
|
'config/authentication/roles/show' => 'config/access-control/roles',
|
|
'config/authentication/roles/add' => 'config/access-control/roles',
|
|
'config/authentication/roles/edit' => 'config/access-control/roles',
|
|
'config/authentication/roles/remove' => 'config/access-control/roles'
|
|
];
|
|
|
|
/** @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 array $userGroups
|
|
* @param ConfigObject $section
|
|
*
|
|
* @return bool
|
|
*/
|
|
protected function match($username, $userGroups, ConfigObject $section)
|
|
{
|
|
$username = strtolower($username);
|
|
if (! empty($section->users)) {
|
|
$users = array_map('strtolower', StringHelper::trimSplit($section->users));
|
|
if (in_array('*', $users)) {
|
|
return true;
|
|
}
|
|
|
|
if (in_array($username, $users)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (! empty($section->groups)) {
|
|
$groups = array_map('strtolower', StringHelper::trimSplit($section->groups));
|
|
foreach ($userGroups as $userGroup) {
|
|
if (in_array(strtolower($userGroup), $groups)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = $section->permissions ? StringHelper::trimSplit($section->permissions) : [];
|
|
$refusals = $section->refusals ? StringHelper::trimSplit($section->refusals) : [];
|
|
|
|
list($permissions, $newRefusals) = self::migrateLegacyPermissions($permissions);
|
|
if (! empty($newRefusals)) {
|
|
array_push($refusals, ...$newRefusals);
|
|
}
|
|
|
|
$restrictions = $section->toArray();
|
|
unset($restrictions['users'], $restrictions['groups']);
|
|
unset($restrictions['parent'], $restrictions['unrestricted']);
|
|
unset($restrictions['refusals'], $restrictions['permissions']);
|
|
|
|
$role = new Role();
|
|
$this->roles[$name] = $role
|
|
->setName($name)
|
|
->setRefusals($refusals)
|
|
->setPermissions($permissions)
|
|
->setRestrictions($restrictions)
|
|
->setIsUnrestricted($section->get('unrestricted', false));
|
|
|
|
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
|
|
*
|
|
* @param User $user
|
|
*/
|
|
public function applyRoles(User $user)
|
|
{
|
|
if ($this->roleConfig === null) {
|
|
return;
|
|
}
|
|
|
|
$username = $user->getUsername();
|
|
$userGroups = $user->getGroups();
|
|
|
|
$roles = [];
|
|
$permissions = [];
|
|
$restrictions = [];
|
|
$assignedRoles = [];
|
|
$isUnrestricted = false;
|
|
foreach ($this->roleConfig as $roleName => $roleConfig) {
|
|
$assigned = $this->match($username, $userGroups, $roleConfig);
|
|
if ($assigned) {
|
|
$assignedRoles[] = $roleName;
|
|
}
|
|
|
|
if (! isset($roles[$roleName]) && $assigned) {
|
|
foreach ($this->loadRole($roleName, $roleConfig) as $role) {
|
|
/** @var Role $role */
|
|
if (isset($roles[$role->getName()])) {
|
|
continue;
|
|
}
|
|
|
|
$roles[$role->getName()] = $role;
|
|
|
|
$permissions = array_merge(
|
|
$permissions,
|
|
array_diff($role->getPermissions(), $permissions)
|
|
);
|
|
|
|
$roleRestrictions = $role->getRestrictions();
|
|
foreach ($roleRestrictions as $name => & $restriction) {
|
|
$restriction = str_replace('$user:local_name$', $user->getLocalUsername(), $restriction);
|
|
$restrictions[$name][] = $restriction;
|
|
}
|
|
|
|
$role->setRestrictions($roleRestrictions);
|
|
|
|
if (! $isUnrestricted) {
|
|
$isUnrestricted = $role->isUnrestricted();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$user->setAdditional('assigned_roles', $assignedRoles);
|
|
|
|
$user->setIsUnrestricted($isUnrestricted);
|
|
$user->setRestrictions($isUnrestricted ? [] : $restrictions);
|
|
$user->setPermissions($permissions);
|
|
$user->setRoles(array_values($roles));
|
|
}
|
|
|
|
public static function migrateLegacyPermissions(array $permissions)
|
|
{
|
|
$migratedGrants = [];
|
|
$refusals = [];
|
|
|
|
foreach ($permissions as $permission) {
|
|
if (array_key_exists($permission, self::LEGACY_PERMISSIONS)) {
|
|
$migratedGrants[] = self::LEGACY_PERMISSIONS[$permission];
|
|
} elseif ($permission === 'no-user/password-change') {
|
|
$refusals[] = 'user/password-change';
|
|
} else {
|
|
$migratedGrants[] = $permission;
|
|
}
|
|
}
|
|
|
|
return [$migratedGrants, $refusals];
|
|
}
|
|
}
|