From 603b3676d20d414611986708535bcb0d1bfe3b00 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 27 Mar 2024 13:05:37 +0100 Subject: [PATCH] acl frontend started --- Core/API/ApiKeyAPI.class.php | 52 ++++--- cli.php | 10 +- react/admin-panel/src/AdminDashboard.jsx | 11 +- react/admin-panel/src/elements/sidebar.js | 10 +- .../src/views/access-control-list.js | 143 ++++++++++++++++++ react/admin-panel/src/views/log-view.js | 9 +- 6 files changed, 199 insertions(+), 36 deletions(-) create mode 100644 react/admin-panel/src/views/access-control-list.js diff --git a/Core/API/ApiKeyAPI.class.php b/Core/API/ApiKeyAPI.class.php index e7842be..e90a356 100644 --- a/Core/API/ApiKeyAPI.class.php +++ b/Core/API/ApiKeyAPI.class.php @@ -3,12 +3,25 @@ namespace Core\API { use Core\Objects\Context; + use Core\Objects\DatabaseEntity\ApiKey; abstract class ApiKeyAPI extends Request { public function __construct(Context $context, bool $externalCall = false, array $params = array()) { parent::__construct($context, $externalCall, $params); } + + protected function fetchAPIKey(int $apiKeyId): ApiKey|bool { + $sql = $this->context->getSQL(); + $apiKey = ApiKey::find($sql, $apiKeyId); + if ($apiKey === false) { + return $this->createError("Error fetching API-Key details: " . $sql->getLastError()); + } else if ($apiKey === null) { + return $this->createError("API-Key does not exit"); + } + + return $apiKey; + } } } @@ -110,22 +123,16 @@ namespace Core\API\ApiKey { } public function _execute(): bool { - $sql = $this->context->getSQL(); - $id = $this->getParam("id"); - $apiKey = ApiKey::find($sql, $id); - if ($apiKey === false) { - return $this->createError("Error fetching API-Key details: " . $sql->getLastError()); - } else if ($apiKey === null) { - return $this->createError("API-Key does not exit"); + $apiKey = $this->fetchAPIKey($this->getParam("id")); + if ($apiKey) { + $sql = $this->context->getSQL(); + $this->success = $apiKey->refresh($sql, 30) !== false; + $this->lastError = $sql->getLastError(); + + if ($this->success) { + $this->result["validUntil"] = $apiKey->getValidUntil()->getTimestamp(); + } } - - $this->success = $apiKey->refresh($sql, 30) !== false; - $this->lastError = $sql->getLastError(); - - if ($this->success) { - $this->result["validUntil"] = $apiKey->getValidUntil()->getTimestamp(); - } - return $this->success; } @@ -144,18 +151,13 @@ namespace Core\API\ApiKey { } public function _execute(): bool { - $sql = $this->context->getSQL(); - $id = $this->getParam("id"); - $apiKey = ApiKey::find($sql, $id); - if ($apiKey === false) { - return $this->createError("Error fetching API-Key details: " . $sql->getLastError()); - } else if ($apiKey === null) { - return $this->createError("API-Key does not exit"); + $apiKey = $this->fetchAPIKey($this->getParam("id")); + if ($apiKey) { + $sql = $this->context->getSQL(); + $this->success = $apiKey->revoke($sql); + $this->lastError = $sql->getLastError(); } - $this->success = $apiKey->revoke($sql); - $this->lastError = $sql->getLastError(); - return $this->success; } diff --git a/cli.php b/cli.php index ae4cdd8..d76cf84 100644 --- a/cli.php +++ b/cli.php @@ -13,6 +13,8 @@ use Core\Driver\SQL\Condition\CondIn; use Core\Driver\SQL\Expression\DateSub; use Core\Driver\SQL\SQL; use Core\Objects\ConnectionData; + +// TODO: is this available in all installations? use JetBrains\PhpStorm\NoReturn; function printLine(string $line = ""): void { @@ -41,6 +43,7 @@ if (!$context->isCLI()) { _exit("Can only be executed via CLI"); } +$dockerYaml = null; $database = $context->getConfig()->getDatabase(); if ($database->getProperty("isDocker", false) && !is_file("/.dockerenv")) { if (function_exists("yaml_parse")) { @@ -181,6 +184,7 @@ function handleDatabase(array $argv): void { $command = array_merge(["docker", "exec", "-it", $containerName], $command); } + var_dump($command); $process = proc_open($command, $descriptorSpec, $pipes, null, $env); if (is_resource($process)) { @@ -797,7 +801,7 @@ function onFrontend(array $argv): void { $argv = $_SERVER['argv']; $registeredCommands = [ "help" => ["handler" => "printHelp"], - "db" => ["handler" => "handleDatabase", "requiresDocker" => ["shell", "import", "export"]], + "db" => ["handler" => "handleDatabase"], "routes" => ["handler" => "onRoutes"], "maintenance" => ["handler" => "onMaintenance"], "test" => ["handler" => "onTest"], @@ -814,11 +818,13 @@ if (count($argv) < 2) { $command = $argv[1]; if (array_key_exists($command, $registeredCommands)) { + // TODO: do we need this? if ($database->getProperty("isDocker", false) && !is_file("/.dockerenv")) { $requiresDocker = in_array($argv[2] ?? null, $registeredCommands[$command]["requiresDocker"] ?? []); if ($requiresDocker) { - printLine("Detected docker environment in config, running docker exec..."); $containerName = $dockerYaml["services"]["php"]["container_name"]; + printLine("Detected docker environment in config, running docker exec for container: $containerName"); + var_dump($argv); $command = array_merge(["docker", "exec", "-it", $containerName, "php"], $argv); $proc = proc_open($command, [1 => STDOUT, 2 => STDERR], $pipes); exit(proc_close($proc)); diff --git a/react/admin-panel/src/AdminDashboard.jsx b/react/admin-panel/src/AdminDashboard.jsx index b7f92d3..d89da14 100644 --- a/react/admin-panel/src/AdminDashboard.jsx +++ b/react/admin-panel/src/AdminDashboard.jsx @@ -12,13 +12,14 @@ import './res/adminlte.min.css'; // views import View404 from "./views/404"; -import LogView from "./views/log-view"; +import clsx from "clsx"; const Overview = lazy(() => import('./views/overview')); const UserListView = lazy(() => import('./views/user/user-list')); const UserEditView = lazy(() => import('./views/user/user-edit')); const GroupListView = lazy(() => import('./views/group-list')); const EditGroupView = lazy(() => import('./views/group-edit')); - +const LogView = lazy(() => import("./views/log-view")); +const AccessControlList = lazy(() => import("./views/access-control-list")); export default function AdminDashboard(props) { @@ -57,6 +58,11 @@ export default function AdminDashboard(props) { hideDialog: hideDialog }; + // add fixed-layout to body, I don't want to rewrite my base.twig template + if (!document.body.className.includes("layout-fixed")) { + document.body.className = clsx(document.body.className, "layout-fixed"); + } + return
@@ -72,6 +78,7 @@ export default function AdminDashboard(props) { }/> }/> }/> + }/> } /> diff --git a/react/admin-panel/src/elements/sidebar.js b/react/admin-panel/src/elements/sidebar.js index c07dc02..6fe17a7 100644 --- a/react/admin-panel/src/elements/sidebar.js +++ b/react/admin-panel/src/elements/sidebar.js @@ -40,6 +40,10 @@ export default function Sidebar(props) { "name": "admin.settings", "icon": "tools" }, + "acl": { + "name": "admin.acl", + "icon": "door-open" + }, "logs": { "name": "admin.logs", "icon": "file-medical-alt" @@ -71,7 +75,7 @@ export default function Sidebar(props) { ); - return ( + return <> - ) + } diff --git a/react/admin-panel/src/views/access-control-list.js b/react/admin-panel/src/views/access-control-list.js new file mode 100644 index 0000000..7006e75 --- /dev/null +++ b/react/admin-panel/src/views/access-control-list.js @@ -0,0 +1,143 @@ +import {useCallback, useContext, useEffect, useState} from "react"; +import {LocaleContext} from "shared/locale"; +import {Link, useNavigate} from "react-router-dom"; +import {Button, Checkbox, TextField, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow} from "@material-ui/core"; +import {Add, Refresh} from "@material-ui/icons"; + + +export default function AccessControlList(props) { + + // meta + const showDialog = props.showDialog; + const {translate: L, requestModules, currentLocale} = useContext(LocaleContext); + const navigate = useNavigate(); + + // data + const [acl, setACL] = useState([]); + const [groups, setGroups] = useState([]); + const [fetchACL, setFetchACL] = useState(true); + + // filters + const [query, setQuery] = useState(""); + + const onFetchACL = useCallback((force = false) => { + if (force || fetchACL) { + setFetchACL(false); + props.api.fetchGroups().then(res => { + if (!res.success) { + props.showDialog(res.msg, "Error fetching groups"); + navigate("/admin/dashboard"); + } else { + setGroups(res.groups); + props.api.fetchPermissions().then(res => { + if (!res.success) { + props.showDialog(res.msg, "Error fetching permissions"); + navigate("/admin/dashboard"); + } else { + setACL(res.permissions); + } + }); + } + }); + } + }, [fetchACL]); + + useEffect(() => { + onFetchACL(); + }, []); + + useEffect(() => { + requestModules(props.api, ["general"], currentLocale).then(data => { + if (!data.success) { + props.showDialog("Error fetching translations: " + data.msg); + } + }); + }, [currentLocale]); + + const PermissionList = () => { + let rows = []; + + for (let index = 0; index < acl.length; index++) { + const permission = acl[index]; + + if (query) { + if (!permission.method.toLowerCase().includes(query.toLowerCase()) || + !permission.description.toLowerCase().includes(query.toLowerCase())) { + continue; + } + } + + rows.push( + + + {permission.method}
+ {permission.description} +
+ {groups.map(group => + + )} +
+ ); + } + + return <>{rows} + } + + return <> +
+
+
+
+

Access Control List

+
+
+
    +
  1. Home
  2. +
  3. ACL
  4. +
+
+
+
+
+
+
+
+ + setQuery(e.target.value)} + variant={"outlined"} + size={"small"} /> +
+
+
+ +
+ + +
+
+
+
+ + + + + {L("permission")} + { groups.map(group => {group.name}) } + + + + + +
+
+
+ +} \ No newline at end of file diff --git a/react/admin-panel/src/views/log-view.js b/react/admin-panel/src/views/log-view.js index bb27e5d..2b328b5 100644 --- a/react/admin-panel/src/views/log-view.js +++ b/react/admin-panel/src/views/log-view.js @@ -12,13 +12,14 @@ import {format, toDate} from "date-fns"; export default function LogView(props) { - // - const LOG_LEVELS = ['debug', 'info', 'warning', 'error', 'severe']; - + // meta const api = props.api; const showDialog = props.showDialog; const {translate: L, requestModules, currentLocale} = useContext(LocaleContext); const pagination = usePagination(); + + // data + const LOG_LEVELS = ['debug', 'info', 'warning', 'error', 'severe']; const [logEntries, setLogEntries] = useState([]); // filters @@ -68,7 +69,7 @@ export default function LogView(props) { new NumericColumn(L("general.id"), "id"), new StringColumn(L("module"), "module"), new StringColumn(L("severity"), "severity"), - new DateTimeColumn(L("timestamp"), "timestamp"), + new DateTimeColumn(L("timestamp"), "timestamp", { precise: true }), messageColumn, ];