summaryrefslogtreecommitdiffstats
path: root/library/Icinga/Authentication/AdmissionLoader.php
diff options
context:
space:
mode:
Diffstat (limited to 'library/Icinga/Authentication/AdmissionLoader.php')
-rw-r--r--library/Icinga/Authentication/AdmissionLoader.php249
1 files changed, 249 insertions, 0 deletions
diff --git a/library/Icinga/Authentication/AdmissionLoader.php b/library/Icinga/Authentication/AdmissionLoader.php
new file mode 100644
index 0000000..0c3fd3f
--- /dev/null
+++ b/library/Icinga/Authentication/AdmissionLoader.php
@@ -0,0 +1,249 @@
+<?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];
+ }
+}