diff --git a/application/forms/Security/RoleDbForm.php b/application/forms/Security/RoleDbForm.php new file mode 100644 index 000000000..2149fe43d --- /dev/null +++ b/application/forms/Security/RoleDbForm.php @@ -0,0 +1,330 @@ +db)->with('parent')->filter(Filter::equal('name', $this->getIdentifier()))->first(); + + if (! $role) { + return false; + } + + $values = [ + 'name' => $role->name, + 'unrestricted' => (int) $role->unrestricted + ]; + + $users = []; + $groups = []; + $permissions = []; + $refusals = []; + $restrictions = []; + + foreach ($role->users as $user) { + $users[] = $user->user_name; + } + + foreach ($role->groups as $group) { + $groups[] = $group->group_name; + } + + foreach ($role->permissions as $permission) { + if ($permission->allowed) { + $permissions[$permission->permission] = true; + + if ($permission->permission === '*') { + $values[self::WILDCARD_NAME] = 1; + } + } + + if ($permission->denied) { + $refusals[$permission->permission] = true; + } + } + + foreach ($role->restrictions as $restriction) { + $restrictions[$restriction->restriction] = $restriction->filter; + } + + if ($role->parent) { + $values['parent'] = $role->parent->name; + } + + if ($users) { + sort($users); + $values['users'] = implode(',', $users); + } + + if ($groups) { + sort($groups); + $values['groups'] = implode(',', $groups); + } + + if ($permissions || $refusals) { + foreach ($this->providedPermissions as $moduleName => $permissionList) { + $hasFullPerm = false; + + foreach ($permissionList as $name => $spec) { + if (array_key_exists($name, $permissions)) { + $values[$this->filterName($name)] = 1; + + if (isset($spec['isFullPerm'])) { + $hasFullPerm = true; + } + } + + if (array_key_exists($name, $refusals)) { + $values[$this->filterName(self::DENY_PREFIX . $name)] = 1; + } + } + + if ($hasFullPerm) { + unset($values[$this->filterName(Manager::MODULE_PERMISSION_NS . $moduleName)]); + } + } + } + + if ($restrictions) { + foreach ($this->providedRestrictions as $restrictionList) { + foreach ($restrictionList as $name => $spec) { + if (array_key_exists($name, $restrictions)) { + $values[$this->filterName($name)] = $restrictions[$name]; + } + } + } + } + + return (object) $values; + } + + protected function entryExists(): bool + { + return Role::on($this->db)->filter(Filter::equal('name', $this->getIdentifier()))->count() > 0; + } + + protected function insertEntry(): void + { + $values = $this->getValues(); + + $this->db->transaction(function (Connection $db) use ($values) { + $db->prepexec( + (new Insert()) + ->into('icingaweb_role') + ->columns(['parent_id', 'name', 'unrestricted', 'ctime']) + ->values([ + $this->queryRoleId($db, $values['parent']), + $values['name'], + $values['unrestricted'] ? 'y' : 'n', + (new DateTime())->getTimestamp() * 1000 + ]) + ); + + $this->insertChildTables($db, $db->lastInsertId(), $values); + }); + } + + protected function updateEntry(): void + { + $values = $this->getValues(); + + $this->db->transaction(function (Connection $db) use ($values) { + $id = $this->queryRoleId($db, $this->getIdentifier()); + + $db->prepexec( + (new Update()) + ->table('icingaweb_role') + ->set([ + 'parent_id' => $this->queryRoleId($db, $values['parent']), + 'name' => $values['name'], + 'unrestricted' => $values['unrestricted'] ? 'y' : 'n', + 'mtime' => (new DateTime())->getTimestamp() * 1000 + ]) + ->where(['id = ?' => $id]) + ); + + $db->prepexec((new Delete())->from('icingaweb_role_user')->where(['role_id = ?' => $id])); + $db->prepexec((new Delete())->from('icingaweb_role_group')->where(['role_id = ?' => $id])); + $db->prepexec((new Delete())->from('icingaweb_role_permission')->where(['role_id = ?' => $id])); + $db->prepexec((new Delete())->from('icingaweb_role_restriction')->where(['role_id = ?' => $id])); + + $this->insertChildTables($db, $id, $values); + }); + } + + /** + * Query the ID of a role + * + * @param Connection $db Database to operate on + * @param ?string $name Target role name + * + * @return ?int Target role ID or null + */ + private function queryRoleId(Connection $db, ?string $name): ?int + { + if ($name !== null) { + $role = Role::on($db)->filter(Filter::equal('name', $name))->columns('id')->first(); + + if ($role) { + return $role->id; + } + } + + return null; + } + + /** + * Populate icingaweb_role_* tables for a new role + * + * @param Connection $db Database to operate on + * @param int $id Role ID + * @param array $values Role data as from {@link getValues()} + */ + private function insertChildTables(Connection $db, int $id, array $values): void + { + $permissions = StringHelper::trimSplit($values['permissions']); + $refusals = StringHelper::trimSplit($values['refusals']); + $permissionsAndRefusals = []; + + foreach (StringHelper::trimSplit($values['users']) as $user) { + $db->prepexec( + (new Insert()) + ->into('icingaweb_role_user') + ->columns(['role_id', 'user_name']) + ->values([$id, $user]) + ); + } + + foreach (StringHelper::trimSplit($values['groups']) as $group) { + $db->prepexec( + (new Insert()) + ->into('icingaweb_role_group') + ->columns(['role_id', 'group_name']) + ->values([$id, $group]) + ); + } + + foreach ([$permissions, $refusals] as $permissionsOrRefusals) { + foreach ($permissionsOrRefusals as $permissionOrRefusal) { + $permissionsAndRefusals[$permissionOrRefusal] = ['allowed' => 'n', 'denied' => 'n']; + } + } + + foreach ($permissions as $permission) { + $permissionsAndRefusals[$permission]['allowed'] = 'y'; + } + + foreach ($refusals as $refusal) { + $permissionsAndRefusals[$refusal]['denied'] = 'y'; + } + + foreach ($permissionsAndRefusals as $name => $authz) { + $db->prepexec( + (new Insert()) + ->into('icingaweb_role_permission') + ->columns(['role_id', 'permission', 'allowed', 'denied']) + ->values([$id, $name, $authz['allowed'], $authz['denied']]) + ); + } + + foreach ($this->providedRestrictions as $restrictionList) { + foreach ($restrictionList as $name => $_) { + if (isset($values[$name])) { + $db->prepexec( + (new Insert()) + ->into('icingaweb_role_restriction') + ->columns(['role_id', 'restriction', 'filter']) + ->values([$id, $name, $values[$name]]) + ); + } + } + } + } + + protected function deleteEntry(): void + { + $this->db->prepexec((new Delete())->from('icingaweb_role')->where(['name = ?' => $this->getIdentifier()])); + } + + protected function collectRoles(): array + { + $roles = []; + $name = $this->getIdentifier(); + + if ($name === null) { + foreach (Role::on($this->db)->orderBy('name')->columns('name') as $role) { + $roles[$role->name] = $role->name; + } + } else { + $query = (new Select()) + ->with( + (new Select()) + ->from('icingaweb_role') + ->where(['name = ?' => $name]) + ->columns(['id', 'parent_id']) + ->union( // handle circular relationships + (new Select()) + ->from(['r' => 'icingaweb_role']) + ->join('rl', 'rl.id = r.parent_id') + ->columns(['r.id', 'r.parent_id']) + ), + 'rl', + true + ) + ->from('icingaweb_role') + ->where(['id NOT IN ?' => (new Select())->from('rl')->columns('id')]) + ->orderBy('name') + ->columns('name'); + + foreach ($this->db->select($query) as $row) { + $roles[$row->name] = $row->name; + } + } + + return $roles; + } + + protected function onRenameSuccess(string $oldName, ?string $newName): void + { + // Already handled by the database schema + } + + /** + * Set the database where the roles are stored + * + * @param Connection $db + * + * @return $this + */ + public function setDb(Connection $db): self + { + $this->db = $db; + + return $this; + } +}