From 47d1e28478a78eb8d275553cb44693a61b597307 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 27 Feb 2018 13:05:21 +0100 Subject: [runmode] Support permissions supplied by module; fix handling of deleteUrl modules --- modules-available/runmode/inc/runmode.inc.php | 14 +- modules-available/runmode/page.inc.php | 282 +++++++++++++++------ .../runmode/permissions/permissions.json | 5 + .../runmode/templates/machine-selector.html | 31 ++- .../runmode/templates/module-machine-list.html | 4 +- 5 files changed, 242 insertions(+), 94 deletions(-) create mode 100644 modules-available/runmode/permissions/permissions.json diff --git a/modules-available/runmode/inc/runmode.inc.php b/modules-available/runmode/inc/runmode.inc.php index 2c8083ca..174fb675 100644 --- a/modules-available/runmode/inc/runmode.inc.php +++ b/modules-available/runmode/inc/runmode.inc.php @@ -28,7 +28,7 @@ class RunMode /** * @param string $machineuuid - * @param string $moduleId + * @param string|\Module $moduleId * @param string|null $modeId an ID specific to the module to further specify the run mode, NULL to delete the run mode entry * @param string|null $modeData optional, additional data for the run mode * @param bool|null $isClient whether to count the machine as a client (in statistics etc.) NULL for looking at module's general runmode config @@ -284,6 +284,11 @@ class RunModeModuleConfig */ public $deleteUrlSnippet = false; + /** + * @var string|false Permission to check when accessing/assigning + */ + public $permission = false; + public function __construct($file) { $data = json_decode(file_get_contents($file), true); @@ -298,6 +303,7 @@ class RunModeModuleConfig $this->loadType($data, 'noSysconfig', 'boolean'); $this->loadType($data, 'allowGenericEditor', 'boolean'); $this->loadType($data, 'deleteUrlSnippet', 'string'); + $this->loadType($data, 'permission', 'string'); } private function loadType($data, $key, $type) @@ -311,4 +317,10 @@ class RunModeModuleConfig $this->{$key} = $data[$key]; return true; } + + public function userHasPermission($locationId) + { + return $this->permission === false || User::hasPermission($this->permission, $locationId); + } + } diff --git a/modules-available/runmode/page.inc.php b/modules-available/runmode/page.inc.php index 8feb097e..9f2c2f44 100644 --- a/modules-available/runmode/page.inc.php +++ b/modules-available/runmode/page.inc.php @@ -14,79 +14,142 @@ class Page_RunMode extends Page Util::redirect('?do=main'); } $action = Request::post('action', false, 'string'); - if ($action !== false) { - $this->handleAction($action); + if ($action === 'save-mode') { + $this->handleSaveMode(); + } elseif ($action === 'delete-machine') { + $this->handleDeleteMachine(); + } + if (Request::isPost()) { Util::redirect('?do=runmode'); } } - private function handleAction($action) + private function handleSaveMode() { - if ($action === 'save-mode') { - $machines = array_filter(Request::post('machines', [], 'array'), 'is_string'); - $module = Request::post('module', false, 'string'); - $modeId = Request::post('modeid', false, 'string'); - $modConfig = RunMode::getModuleConfig($module); - if ($modConfig === false) { - Message::addError('runmode.module-hasnt-runmode', $module); - return; - } - if (!$modConfig->allowGenericEditor) { - Message::addError('runmode.cannot-edit-module', $module); - return; - } - $test = RunMode::getModeName($module, $modeId); - if ($test === false) { - Message::addError('runmode.invalid-modeid', $module, $modeId); - return; - } - $active = 0; - foreach ($machines as $machine) { - $oldMode = RunMode::getRunMode($machine, 0); - if ($oldMode !== false) { - $oldModule = RunMode::getModuleConfig($oldMode['module']); - if ($oldModule !== false && (!$oldModule->allowGenericEditor || $oldModule->deleteUrlSnippet !== false)) { - Message::addError('runmode.machine-still-assigned', $machine, $oldMode['module']); - continue; - } + $machines = array_unique(array_filter(Request::post('machines', [], 'array'), 'is_string')); + $module = Request::post('module', false, 'string'); + $modeId = Request::post('modeid', false, 'string'); + $modConfig = RunMode::getModuleConfig($module); + if ($modConfig === false) { + Message::addError('runmode.module-hasnt-runmode', $module); + return; + } + if (!$modConfig->allowGenericEditor) { + Message::addError('runmode.cannot-edit-module', $module); + return; + } + $test = RunMode::getModeName($module, $modeId); + if ($test === false) { + Message::addError('runmode.invalid-modeid', $module, $modeId); + return; + } + // Query existing entries first (for delete - see below) + $existing = []; + if ($modConfig->permission !== false) { + $existing = RunMode::getForMode($module, $modeId, true, true); + } + // Before doing anything, check if the user has the proper permission for any location - if not, nothing to do + if (!$modConfig->userHasPermission(null)) { + Message::addError('main.no-permission'); + Util::redirect('?do=runmode'); + } + $active = 0; + foreach ($machines as $machine) { + if (isset($existing[$machine])) { + if (!$modConfig->userHasPermission($existing[$machine]['locationid'])) { + // Machine was already assigned to this runmode, and user has no permission for its location + unset($existing[$machine]); + continue; // So keep it as-is and skip } - $ret = RunMode::setRunMode($machine, $module, $modeId, null, null); - if ($ret) { - $active++; + // User has permission to add this existing machine, keep going so meta data could be updated + unset($existing[$machine]); + } else { + // Not existing yet in this module/mode combo, but check if it is assigned to some other run mode + $machineLocation = false; + $oldMachineMode = RunMode::getRunMode($machine, RunMode::DATA_MACHINE_DATA | RunMode::DATA_DETAILED); + if ($oldMachineMode !== false) { + $machineLocation = $oldMachineMode['locationid']; + $oldModule = RunMode::getModuleConfig($oldMachineMode['module']); + if ($oldModule !== false) { + if ($oldMachineMode['module'] !== $module || $oldMachineMode['modeid'] !== $modeId) { + if (!$oldModule->allowGenericEditor || $oldModule->deleteUrlSnippet !== false) { + Message::addError('runmode.machine-still-assigned', $machine, $oldMachineMode['module']); + continue; + } + } + // Permissions for old runmode + if (!$oldModule->userHasPermission($oldMachineMode['locationid'])) { + // Show same error message as above - might help the user figure out they have no perm to remove it + Message::addError('runmode.machine-still-assigned', $machine, $oldMachineMode['module']); + continue; + } + } } else { - Message::addError('runmode.machine-not-found', $machine); + // Not existing, no old mode - query machine to get location, so we can do a perm-check for new loc + $m = Statistics::getMachine($machine, Machine::NO_DATA); + if ($m !== false) { + $machineLocation = $m->locationid; + } + } + if ($machineLocation !== false && !$modConfig->userHasPermission($machineLocation)) { + Message::addError('runmode.machine-no-permission', $machine); + continue; } } + $ret = RunMode::setRunMode($machine, $module, $modeId, null, null); + if ($ret) { + $active++; + } else { + Message::addError('runmode.machine-not-found', $machine); + } + } + // Make sure inaccessible machines (no permission for location) are preserved on delete + // Add existing but inaccessible to list + foreach ($existing as $e) { + if (!$modConfig->userHasPermission($e['locationid'])) { + $machines[] = $e['machineuuid']; + } + } + if ($modConfig->deleteUrlSnippet === false) { $deleted = Database::exec('DELETE FROM runmode WHERE module = :module AND modeid = :modeId AND machineuuid NOT IN (:machines)', compact('module', 'modeId', 'machines')); - Message::addSuccess('runmode.enabled-removed-save', $active, $deleted); - Util::redirect('?do=runmode&module=' . $module . '&modeid=' . $modeId, true); - } elseif ($action === 'delete-machine') { - $machineuuid = Request::post('machineuuid', false, 'string'); - if ($machineuuid === false) { - Message::addError('main.parameter-missing', 'machineuuid'); - return; - } - $mode = RunMode::getRunMode($machineuuid); - if ($mode === false) { - Message::addError('runmode.machine-not-found', $machineuuid); - return; - } - $modConfig = RunMode::getModuleConfig($mode['module']); - if ($modConfig === false) { - Message::addError('module-hasnt-runmode', $mode['moduleName']); - return; - } - if (!$modConfig->allowGenericEditor) { - Message::addError('runmode.cannot-edit-module', $mode['moduleName']); - return; - } - if (RunMode::setRunMode($machineuuid, null, null)) { - Message::addSuccess('machine-removed', $machineuuid); - } else { - Message::addWarning('machine-not-runmode', $machineuuid); - } + } else { + $deleted = 0; + } + Message::addSuccess('runmode.enabled-removed-save', $active, $deleted); + Util::redirect('?do=runmode&module=' . $module . '&modeid=' . $modeId, true); + } + + private function handleDeleteMachine() + { + $machineuuid = Request::post('machineuuid', false, 'string'); + if ($machineuuid === false) { + Message::addError('main.parameter-missing', 'machineuuid'); + return; + } + $mode = RunMode::getRunMode($machineuuid, RunMode::DATA_MACHINE_DATA | RunMode::DATA_DETAILED); + if ($mode === false) { + Message::addError('runmode.machine-not-found', $machineuuid); + return; + } + $modConfig = RunMode::getModuleConfig($mode['module']); + if ($modConfig === false) { + Message::addError('module-hasnt-runmode', $mode['moduleName']); + return; + } + if (!$modConfig->allowGenericEditor || $modConfig->deleteUrlSnippet !== false) { + Message::addError('runmode.cannot-edit-module', $mode['moduleName']); + return; + } + if (!$modConfig->userHasPermission($mode['locationid'])) { + Message::addError('runmode.machine-no-permission', $machineuuid); + return; + } + if (RunMode::setRunMode($machineuuid, null, null)) { + Message::addSuccess('machine-removed', $machineuuid); + } else { + Message::addWarning('machine-not-runmode', $machineuuid); } } @@ -117,7 +180,13 @@ class Page_RunMode extends Page $modeId = Request::get('modeid', false, 'string'); if ($modeId !== false) { // Show edit page for specific module-mode combo - $this->renderModuleMode($module, $modeId); + $this->renderModuleMode($module, $modeId, $config); + return; + } + // Permissions + if (!$config->userHasPermission(null) && !User::hasPermission('list-all')) { + Message::addError('main.no-permission'); + Util::redirect('?do=runmode'); return; } // Show list of machines with assigned mode for this module @@ -152,6 +221,16 @@ class Page_RunMode extends Page $modules[$row['module']]['list'][] = $row; } foreach ($modules as $moduleId => $rows) { + if ($onlyModule === false) { + // Permissions - not required if rendering specific module, since it's been already done + if ($rows['config']->userHasPermission(null)) { + $disabled = ''; + } elseif (User::hasPermission('list-all')) { + $disabled = 'disabled'; + } else { + continue; + } // + } $module = Module::get($moduleId); if ($module === false) continue; @@ -161,7 +240,8 @@ class Page_RunMode extends Page 'modulename' => $module->getDisplayName(), 'module' => $moduleId, 'canedit' => $config !== false && $config->allowGenericEditor && $config->deleteUrlSnippet === false, - 'deleteUrl' => $config->deleteUrlSnippet + 'deleteUrl' => $config->deleteUrlSnippet, + 'disabled' => $disabled, )); } } @@ -169,8 +249,9 @@ class Page_RunMode extends Page /** * @param \Module $module * @param string $modeId + * @param \RunModeModuleConfig $config */ - private function renderModuleMode($module, $modeId) + private function renderModuleMode($module, $modeId, $config) { $moduleId = $module->getIdentifier(); $modeName = RunMode::getModeName($moduleId, $modeId); @@ -186,39 +267,78 @@ class Page_RunMode extends Page Message::addError('runmode.cannot-edit-module', $moduleId); Util::redirect($redirect); } + // Permissions + if ($config->userHasPermission(null)) { + $disabled = ''; + } elseif (User::hasPermission('list-all')) { + $disabled = 'disabled'; + } else { + Message::addError('main.no-permission'); + Util::redirect('?do=runmode'); + return; + } + $machines = RunMode::getForMode($module, $modeId, true); + if ($config->permission !== false) { + $allowed = User::getAllowedLocations($config->permission); + $machines = array_values(array_filter($machines, function ($item) use ($allowed) { + return in_array($item['locationid'], $allowed); + })); + } Render::addTemplate('machine-selector', [ 'module' => $moduleId, 'modeid' => $modeId, 'moduleName' => $module->getDisplayName(), 'modeName' => $modeName, - 'machines' => json_encode(RunMode::getForMode($module, $modeId, true)), + 'machines' => json_encode($machines), 'redirect' => $redirect, + 'disabled' => $disabled, + 'add-only' => $config->deleteUrlSnippet !== false, ]); } protected function doAjax() { $action = Request::any('action', false, 'string'); + if ($action !== 'getmachines') + return; + $query = Request::get('query', false, 'string'); + if (strlen($query) < 2) + return; - if ($action === 'getmachines') { - $query = Request::get('query', false, 'string'); - - $result = Database::simpleQuery('SELECT m.machineuuid, m.macaddr, m.clientip, m.hostname, m.locationid, ' - . 'r.module, r.modeid ' - . 'FROM machine m ' - . 'LEFT JOIN runmode r USING (machineuuid) ' - . 'WHERE machineuuid LIKE :query ' - . ' OR macaddr LIKE :query ' - . ' OR clientip LIKE :query ' - . ' OR hostname LIKE :query ' - . ' LIMIT 100', ['query' => "%$query%"]); + User::load(); + $config = RunMode::getModuleConfig(Request::any('module', '', 'string')); + $returnObject = ['machines' => []]; - $returnObject = [ - 'machines' => $result->fetchAll(PDO::FETCH_ASSOC) - ]; + if ($config !== false) { + $params = ['query' => "%$query%"]; + if ($config->permission === false) { + // Global + $condition = '1'; + } else { + $params['locations'] = User::getAllowedLocations($config->permission); + $condition = 'locationid IN (:locations)'; + if (in_array(0, $params['locations'])) { + $condition .= ' OR locationid IS NULL'; + } + } + if ($config->permission === false || !empty($params['locations'])) { + $result = Database::simpleQuery("SELECT m.machineuuid, m.macaddr, m.clientip, m.hostname, m.locationid, + r.module, r.modeid + FROM machine m + LEFT JOIN runmode r USING (machineuuid) + WHERE ($condition) AND (machineuuid LIKE :query + OR macaddr LIKE :query + OR clientip LIKE :query + OR hostname LIKE :query) + LIMIT 100", $params); - echo json_encode($returnObject); + $returnObject = [ + 'machines' => $result->fetchAll(PDO::FETCH_ASSOC) + ]; + } } + echo json_encode($returnObject); + } } \ No newline at end of file diff --git a/modules-available/runmode/permissions/permissions.json b/modules-available/runmode/permissions/permissions.json new file mode 100644 index 00000000..53dfdab0 --- /dev/null +++ b/modules-available/runmode/permissions/permissions.json @@ -0,0 +1,5 @@ +{ + "list-all": { + "location-aware": false + } +} \ No newline at end of file diff --git a/modules-available/runmode/templates/machine-selector.html b/modules-available/runmode/templates/machine-selector.html index 7f37f5a2..8b608f7e 100644 --- a/modules-available/runmode/templates/machine-selector.html +++ b/modules-available/runmode/templates/machine-selector.html @@ -1,12 +1,7 @@

{{lang_assignRunmodeToMachine}}

{{moduleName}} // {{modeName}}

-

{{lang_assignMachineIntroText}}

- +

{{lang_assignMachineIntroText}}

{{lang_addNewMachines}}

@@ -15,13 +10,18 @@ -
- +
+{{#add-only}} +

{{lang_existingClients}}

+
+{{/add-only}} +