From aa513800550d36bf79fe89790dab7c6747e5bfd8 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 27 Mar 2024 15:15:46 +0100 Subject: [PATCH] bugfix, permission api rewrite --- Core/API/PermissionAPI.class.php | 143 +++++++----- Core/API/Request.class.php | 12 +- Core/API/Swagger.class.php | 5 - Core/Driver/SQL/MySQL.class.php | 2 +- cli.php | 15 +- .../src/views/access-control-list.js | 41 +++- react/admin-panel/src/views/user-list.js | 101 --------- react/admin-panel/src/views/user/user-list.js | 2 + react/shared/api.js | 8 + react/shared/elements/data-table.js | 5 +- yarn.lock | 213 ++++++++++++++++++ 11 files changed, 365 insertions(+), 182 deletions(-) mode change 100644 => 100755 cli.php delete mode 100644 react/admin-panel/src/views/user-list.js create mode 100644 yarn.lock diff --git a/Core/API/PermissionAPI.class.php b/Core/API/PermissionAPI.class.php index c51dc96..21d2921 100644 --- a/Core/API/PermissionAPI.class.php +++ b/Core/API/PermissionAPI.class.php @@ -12,6 +12,7 @@ namespace Core\API { } protected function checkStaticPermission(): bool { + // hardcoded permission checking $user = $this->context->getUser(); if (!$user || !$user->hasGroup(Group::ADMIN)) { return $this->createError("Permission denied."); @@ -19,18 +20,21 @@ namespace Core\API { return true; } + + protected function isRestricted(string $method): bool { + return in_array(strtolower($method), ["permission/update", "permission/delete"]); + } } } namespace Core\API\Permission { + use Core\API\Parameter\ArrayType; use Core\API\Parameter\Parameter; use Core\API\Parameter\StringType; use Core\API\PermissionAPI; use Core\Driver\SQL\Column\Column; - use Core\Driver\SQL\Condition\CondIn; use Core\Driver\SQL\Condition\CondLike; - use Core\Driver\SQL\Condition\CondNot; use Core\Driver\SQL\Query\Insert; use Core\Driver\SQL\Strategy\UpdateStrategy; use Core\Objects\Context; @@ -142,75 +146,51 @@ namespace Core\API\Permission { } } - class Save extends PermissionAPI { - + class Update extends PermissionAPI { public function __construct(Context $context, bool $externalCall = false) { - parent::__construct($context, $externalCall, array( - 'permissions' => new Parameter('permissions', Parameter::TYPE_ARRAY) - )); + parent::__construct($context, $externalCall, [ + "method" => new StringType("method", 32, false), + "groups" => new ArrayType("groups", Parameter::TYPE_INT, true, false), + "description" => new StringType("description", 128, true, null), + ]); } - public function _execute(): bool { + protected function _execute(): bool { if (!$this->checkStaticPermission()) { return false; } - $permissions = $this->getParam("permissions"); $sql = $this->context->getSQL(); - $methodParam = new StringType('method', 32); - $groupsParam = new Parameter('groups', Parameter::TYPE_ARRAY); - $descriptionParam = new StringType('method', 128); + $method = $this->getParam("method"); + $description = $this->getParam("description"); + if ($this->isRestricted($method)) { + return $this->createError("This method cannot be updated."); + } - $updateQuery = $sql->insert("ApiPermission", ["method", "groups", "description"]) - ->onDuplicateKeyStrategy(new UpdateStrategy(["method"], [ - "groups" => new Column("groups"), - "description" => new Column("description") - ])); - - $insertedMethods = array(); - - foreach ($permissions as $permission) { - if (!is_array($permission)) { - return $this->createError("Invalid data type found in parameter: permissions, expected: object"); - } else if (!isset($permission["method"]) || !isset($permission["description"]) || !array_key_exists("groups", $permission)) { - return $this->createError("Invalid object found in parameter: permissions, expected keys: 'method', 'groups', 'description'"); - } else if (!$methodParam->parseParam($permission["method"])) { - $expectedType = $methodParam->getTypeName(); - return $this->createError("Invalid data type found for attribute 'method', expected: $expectedType"); - } else if (!$groupsParam->parseParam($permission["groups"])) { - $expectedType = $groupsParam->getTypeName(); - return $this->createError("Invalid data type found for attribute 'groups', expected: $expectedType"); - } else if (!$descriptionParam->parseParam($permission["description"])) { - $expectedType = $descriptionParam->getTypeName(); - return $this->createError("Invalid data type found for attribute 'description', expected: $expectedType"); - } else if (empty(trim($methodParam->value))) { - return $this->createError("Method cannot be empty."); - } else { - $method = $methodParam->value; - $groups = $groupsParam->value; - $description = $descriptionParam->value; - $updateQuery->addRow($method, $groups, $description); - $insertedMethods[] = $method; + $groups = $this->getParam("groups"); + if (!empty($groups)) { + sort($groups); + $availableGroups = Group::findAll($sql); + foreach ($groups as $groupId) { + if (!isset($availableGroups[$groupId])) { + return $this->createError("Unknown group id: $groupId"); + } } } - if (!empty($permissions)) { - $res = $updateQuery->execute(); - $this->success = ($res !== FALSE); - $this->lastError = $sql->getLastError(); - } - - if ($this->success) { - $res = $sql->delete("ApiPermission") - ->whereEq("description", "") // only delete non default permissions - ->where(new CondNot(new CondIn(new Column("method"), $insertedMethods))) - ->execute(); - - $this->success = ($res !== FALSE); - $this->lastError = $sql->getLastError(); + if ($description === null) { + $updateQuery = $sql->insert("ApiPermission", ["method", "groups", "isCore"]) + ->onDuplicateKeyStrategy(new UpdateStrategy(["method"], ["groups" => $groups])) + ->addRow($method, $groups, false); + } else { + $updateQuery = $sql->insert("ApiPermission", ["method", "groups", "isCore", "description"]) + ->onDuplicateKeyStrategy(new UpdateStrategy(["method"], ["groups" => $groups, "description" => $description])) + ->addRow($method, $groups, false, $description); } + $this->success = $updateQuery->execute() !== false; + $this->lastError = $sql->getLastError(); return $this->success; } @@ -222,4 +202,55 @@ namespace Core\API\Permission { ); } } + + class Delete extends PermissionAPI { + public function __construct(Context $context, bool $externalCall = false) { + parent::__construct($context, $externalCall, [ + "method" => new StringType("method", 32, false), + ]); + } + + protected function _execute(): bool { + + if (!$this->checkStaticPermission()) { + return false; + } + + $sql = $this->context->getSQL(); + $method = $this->getParam("method"); + if ($this->isRestricted($method)) { + return $this->createError("This method cannot be deleted."); + } + + $res = $sql->select("method") + ->from("ApiPermission") + ->whereEq("method", $method) + ->execute(); + + $this->success = $res !== false; + $this->lastError = $sql->getLastError(); + + if ($this->success) { + if (!$res) { + return $this->createError("This method was not configured yet"); + } else { + $res = $sql->delete("ApiPermission") + ->whereEq("method", $method) + ->execute(); + $this->success = $res !== false; + $this->lastError = $sql->getLastError(); + } + } + + return $this->success; + } + + public static function getDefaultACL(Insert $insert): void { + $insert->addRow( + self::getEndpoint(), [Group::ADMIN], + "Allows users to delete API permissions. This is restricted to the administrator and cannot be changed", + true + ); + } + } } \ No newline at end of file diff --git a/Core/API/Request.class.php b/Core/API/Request.class.php index ceafd2a..a4ba499 100644 --- a/Core/API/Request.class.php +++ b/Core/API/Request.class.php @@ -244,13 +244,11 @@ abstract class Request { } // Check for permission - if (!($this instanceof \Core\API\Permission\Save)) { - $req = new \Core\API\Permission\Check($this->context); - $this->success = $req->execute(array("method" => self::getEndpoint())); - $this->lastError = $req->getLastError(); - if (!$this->success) { - return false; - } + $req = new \Core\API\Permission\Check($this->context); + $this->success = $req->execute(array("method" => self::getEndpoint())); + $this->lastError = $req->getLastError(); + if (!$this->success) { + return false; } } diff --git a/Core/API/Swagger.class.php b/Core/API/Swagger.class.php index 2cf6e5e..549185f 100644 --- a/Core/API/Swagger.class.php +++ b/Core/API/Swagger.class.php @@ -41,11 +41,6 @@ class Swagger extends Request { return false; } - // special case: hardcoded permission - if ($request instanceof \Core\API\Permission\Save && (!$isLoggedIn || !$currentUser->hasGroup(Group::ADMIN))) { - return false; - } - if (!empty($requiredGroups)) { $userGroups = array_keys($currentUser?->getGroups() ?? []); return !empty(array_intersect($requiredGroups, $userGroups)); diff --git a/Core/Driver/SQL/MySQL.class.php b/Core/Driver/SQL/MySQL.class.php index ba9fd71..30e9a79 100644 --- a/Core/Driver/SQL/MySQL.class.php +++ b/Core/Driver/SQL/MySQL.class.php @@ -52,7 +52,7 @@ class MySQL extends SQL { return true; } - @$this->connection = mysqli_connect( + $this->connection = @mysqli_connect( $this->connectionData->getHost(), $this->connectionData->getLogin(), $this->connectionData->getPassword(), diff --git a/cli.php b/cli.php old mode 100644 new mode 100755 index 4866be6..519f461 --- a/cli.php +++ b/cli.php @@ -1,3 +1,4 @@ +#!/usr/bin/php setData(["2faAuthenticated" => true]); $session->update(); - echo "session=" . $session->getUUID() . PHP_EOL; + echo "Cookie: session=" . $session->getUUID() . PHP_EOL . + "CSRF-Token: " . $session->getCsrfToken() . PHP_EOL; } function onFrontend(array $argv): void { @@ -809,7 +809,7 @@ $registeredCommands = [ "test" => ["handler" => "onTest"], "mail" => ["handler" => "onMail"], "settings" => ["handler" => "onSettings"], - "impersonate" => ["handler" => "onImpersonate"], + "impersonate" => ["handler" => "onImpersonate", "requiresDocker" => true], "frontend" => ["handler" => "onFrontend"], ]; @@ -820,13 +820,12 @@ if (count($argv) < 2) { $command = $argv[1]; if (array_key_exists($command, $registeredCommands)) { - // TODO: do we need this? if ($database !== null && $database->getProperty("isDocker", false) && !is_file("/.dockerenv")) { - $requiresDocker = in_array($argv[2] ?? null, $registeredCommands[$command]["requiresDocker"] ?? []); + $requiresDockerArgs = $registeredCommands[$command]["requiresDocker"] ?? []; + $requiresDocker = $requiresDockerArgs === true || in_array($argv[2] ?? null, $requiresDockerArgs); if ($requiresDocker) { $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/views/access-control-list.js b/react/admin-panel/src/views/access-control-list.js index bad7690..4205004 100644 --- a/react/admin-panel/src/views/access-control-list.js +++ b/react/admin-panel/src/views/access-control-list.js @@ -54,6 +54,40 @@ export default function AccessControlList(props) { }); }, [currentLocale]); + const onChangePermission = useCallback((methodIndex, groupId, selected) => { + let newGroups = null; + let currentGroups = acl[methodIndex].groups; + let groupIndex = currentGroups.indexOf(groupId); + if (!selected) { + if (currentGroups.length === 0) { + // it was an "everyone permission" before + newGroups = groups.filter(group => group.id !== groupId).map(group => group.id); + } else if (groupIndex !== -1 && currentGroups.length > 1) { + newGroups = [...currentGroups]; + newGroups.splice(groupIndex, 1); + } + } else if (groupIndex === -1) { + newGroups = [...currentGroups]; + newGroups.push(groupId); + } + + if (newGroups !== null) { + props.api.updatePermission(acl[methodIndex].method, newGroups).then((data) => { + if (data.success) { + let newACL = [...acl]; + newACL[methodIndex].groups = newGroups; + setACL(newACL); + } else { + props.showDialog("Error updating permission: " + data.msg); + } + }); + } + }, [acl]); + + const isRestricted = (method) => { + return ["permissions/update", "permissions/delete"].includes(method.toLowerCase()); + } + const PermissionList = () => { let rows = []; @@ -75,7 +109,8 @@ export default function AccessControlList(props) { {groups.map(group => + onChange={(e) => onChangePermission(index, group.id, e.target.checked)} + disabled={isRestricted(permission.method) || !props.api.hasGroup(USER_GROUP_ADMIN)} /> )} ); @@ -132,7 +167,9 @@ export default function AccessControlList(props) { {L("permission")} - { groups.map(group => {group.name}) } + { groups.map(group => + {group.name} + ) } diff --git a/react/admin-panel/src/views/user-list.js b/react/admin-panel/src/views/user-list.js deleted file mode 100644 index 49ce52a..0000000 --- a/react/admin-panel/src/views/user-list.js +++ /dev/null @@ -1,101 +0,0 @@ -import {Link, Navigate, useNavigate} from "react-router-dom"; -import {useCallback, useContext, useEffect, useState} from "react"; -import {LocaleContext} from "shared/locale"; -import {DataColumn, DataTable, NumericColumn, StringColumn} from "shared/elements/data-table"; -import {Button, IconButton} from "@material-ui/core"; -import EditIcon from '@mui/icons-material/Edit'; -import {Chip} from "@mui/material"; -import AddIcon from "@mui/icons-material/Add"; -import usePagination from "shared/hooks/pagination"; - - -export default function UserListView(props) { - - const api = props.api; - const showDialog = props.showDialog; - const {translate: L, requestModules, currentLocale} = useContext(LocaleContext); - const navigate = useNavigate(); - const pagination = usePagination(); - const [users, setUsers] = useState([]); - - useEffect(() => { - requestModules(props.api, ["general", "account"], currentLocale).then(data => { - if (!data.success) { - props.showDialog("Error fetching translations: " + data.msg); - } - }); - }, [currentLocale]); - - const onFetchUsers = useCallback((page, count, orderBy, sortOrder) => { - api.fetchUsers(page, count, orderBy, sortOrder).then((res) => { - if (res.success) { - setUsers(res.users); - pagination.update(res.pagination); - } else { - showDialog(res.msg, "Error fetching users"); - return null; - } - }); - }, [api, showDialog]); - - const groupColumn = (() => { - let column = new DataColumn(L("account.groups"), "groups"); - column.renderData = (L, entry) => { - return Object.values(entry.groups).map(group => ) - } - return column; - })(); - - const actionColumn = (() => { - let column = new DataColumn(L("general.actions"), null, false); - column.renderData = (L, entry) => <> - navigate("/admin/user/" + entry.id)}> - - - - return column; - })(); - - const columnDefinitions = [ - new NumericColumn(L("general.id"), "id"), - new StringColumn(L("account.username"), "name"), - new StringColumn(L("account.email"), "email"), - groupColumn, - new StringColumn(L("account.full_name"), "full_name"), - actionColumn, - ]; - - return <> -
-
-
-
-

Users

-
-
-
    -
  1. Home
  2. -
  3. Users
  4. -
-
-
-
-
-
- - - - -
-
-
- -} \ No newline at end of file diff --git a/react/admin-panel/src/views/user/user-list.js b/react/admin-panel/src/views/user/user-list.js index a3402be..8fa2510 100644 --- a/react/admin-panel/src/views/user/user-list.js +++ b/react/admin-panel/src/views/user/user-list.js @@ -77,6 +77,8 @@ export default function UserListView(props) { {}); diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..6bf3c1a --- /dev/null +++ b/yarn.lock @@ -0,0 +1,213 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.23.5": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" + integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== + dependencies: + "@babel/highlight" "^7.24.2" + picocolors "^1.0.0" + +"@babel/helper-annotate-as-pure@^7.18.6", "@babel/helper-annotate-as-pure@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" + integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-create-class-features-plugin@^7.21.0": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.1.tgz#db58bf57137b623b916e24874ab7188d93d7f68f" + integrity sha512-1yJa9dX9g//V6fDebXoEfEsxkZHk3Hcbm+zLhyu6qVgYFLvmTALTeV+jNU9e5RnYtioBrGEOdoI2joMSNQ/+aA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-member-expression-to-functions" "^7.23.0" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers" "^7.24.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + semver "^6.3.1" + +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + +"@babel/helper-member-expression-to-functions@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz#9263e88cc5e41d39ec18c9a3e0eced59a3e7d366" + integrity sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA== + dependencies: + "@babel/types" "^7.23.0" + +"@babel/helper-optimise-call-expression@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" + integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.20.2": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz#945681931a52f15ce879fd5b86ce2dae6d3d7f2a" + integrity sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w== + +"@babel/helper-replace-supers@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz#7085bd19d4a0b7ed8f405c1ed73ccb70f323abc1" + integrity sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-member-expression-to-functions" "^7.23.0" + "@babel/helper-optimise-call-expression" "^7.22.5" + +"@babel/helper-skip-transparent-expression-wrappers@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" + integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-string-parser@^7.23.4": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" + integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/highlight@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" + integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.24.0": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.1.tgz#1e416d3627393fab1cb5b0f2f1796a100ae9133a" + integrity sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg== + +"@babel/plugin-proposal-private-property-in-object@^7.21.11": + version "7.21.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz#69d597086b6760c4126525cfa154f34631ff272c" + integrity sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.21.0" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/template@^7.22.15": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" + integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" + +"@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" + integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==