mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 17:45:39 +00:00
[WIP] Implemented basic permissions (#960)
* Add permissions to Database & add "User"-classes * Add UI & API for Permissions, protect "User"-(Api)-Controller with new permissions. * Add some permissions. * Add permission localization * Add error handling. * Error pages: only redirect on 404 * ExceptionController: return JSON-Response on api-routes * Rename PRODUCT_ADD to PRODUCT_PURCHASE * Move translation to new file * Fix checkboxes stay selected on reload. * Remove configurable User-implementation * Remove MASTER_DATA_READ * Disable buttons the user isn't allowed to use. * Add default permissions for new users * When migration to permissions, everyone starts as ADMIN * Permission-Localization: add to transifex & LocalizationService * Review Co-authored-by: Bernd Bestel <bernd@berrnd.de>
This commit is contained in:
parent
f28697e5b4
commit
b7d1b21f1d
@ -6,4 +6,5 @@ copy /Y localization\en\stock_transaction_types.po localization\en_GB\stock_tran
|
||||
copy /Y localization\en\component_translations.po localization\en_GB\component_translations.po
|
||||
copy /Y localization\en\chore_period_types.po localization\en_GB\chore_period_types.po
|
||||
copy /Y localization\en\chore_assignment_types.po localization\en_GB\chore_assignment_types.po
|
||||
copy /Y localization\en\permissions.po localization\en_GB\permissions.po
|
||||
popd
|
||||
|
@ -42,3 +42,9 @@ file_filter = localization/<lang>/userfield_types.po
|
||||
source_file = localization/userfield_types.pot
|
||||
source_lang = en
|
||||
type = PO
|
||||
|
||||
[grocy.permissions]
|
||||
file_filter = localization/<lang>/permissions.po
|
||||
source_file = localization/permissions.pot
|
||||
source_lang = en
|
||||
type = PO
|
||||
|
5
app.php
5
app.php
@ -65,6 +65,9 @@ if (!empty(GROCY_BASE_PATH))
|
||||
|
||||
// Add default middleware
|
||||
$app->addRoutingMiddleware();
|
||||
$app->addErrorMiddleware(true, false, false);
|
||||
$errorMiddleware = $app->addErrorMiddleware(true, false, false);
|
||||
$errorMiddleware->setDefaultErrorHandler(
|
||||
new \Grocy\Controllers\ExceptionController($app, $container)
|
||||
);
|
||||
|
||||
$app->run();
|
||||
|
@ -82,6 +82,11 @@ Setting('DISABLE_BROWSER_BARCODE_CAMERA_SCANNING', false);
|
||||
# Needs to be a number where Sunday = 0, Monday = 1 and so forth
|
||||
Setting('MEAL_PLAN_FIRST_DAY_OF_WEEK', '');
|
||||
|
||||
# Default permissions for new users
|
||||
# the array needs to contain the technical/constant names
|
||||
# see the file controllers/Users/User.php for possible values
|
||||
Setting('DEFAULT_PERMISSIONS', ['ADMIN']);
|
||||
|
||||
|
||||
# Default user settings
|
||||
# These settings can be changed per user, here the defaults
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use Grocy\Controllers\Users\User;
|
||||
use \Grocy\Services\DatabaseService;
|
||||
use \Grocy\Services\ApplicationService;
|
||||
use \Grocy\Services\LocalizationService;
|
||||
@ -66,6 +67,11 @@ class BaseController
|
||||
}
|
||||
$this->View->set('featureFlags', $constants);
|
||||
|
||||
if (GROCY_AUTHENTICATED)
|
||||
{
|
||||
$this->View->set('permissions', User::PermissionList());
|
||||
}
|
||||
|
||||
return $this->View->render($response, $page, $data);
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use Grocy\Controllers\Users\User;
|
||||
|
||||
class BatteriesApiController extends BaseApiController
|
||||
{
|
||||
public function __construct(\DI\Container $container)
|
||||
@ -11,7 +13,9 @@ class BatteriesApiController extends BaseApiController
|
||||
|
||||
public function TrackChargeCycle(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
User::checkPermission($request, User::PERMISSION_BATTERY_TRACK_CHARGE_CYCLE);
|
||||
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
try
|
||||
{
|
||||
@ -49,7 +53,9 @@ class BatteriesApiController extends BaseApiController
|
||||
|
||||
public function UndoChargeCycle(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
try
|
||||
User::checkPermission($request, User::PERMISSION_BATTERY_UNDO_TRACK_CHARGE_CYCLE);
|
||||
|
||||
try
|
||||
{
|
||||
$this->ApiResponse($response, $this->getBatteriesService()->UndoChargeCycle($args['chargeCycleId']));
|
||||
return $this->EmptyApiResponse($response);
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use Grocy\Controllers\Users\User;
|
||||
|
||||
class ChoresApiController extends BaseApiController
|
||||
{
|
||||
public function __construct(\DI\Container $container)
|
||||
@ -15,7 +17,9 @@ class ChoresApiController extends BaseApiController
|
||||
|
||||
try
|
||||
{
|
||||
$trackedTime = date('Y-m-d H:i:s');
|
||||
User::checkPermission($request, User::PERMISSION_CHORE_TRACK);
|
||||
|
||||
$trackedTime = date('Y-m-d H:i:s');
|
||||
if (array_key_exists('tracked_time', $requestBody) && (IsIsoDateTime($requestBody['tracked_time']) || IsIsoDate($requestBody['tracked_time'])))
|
||||
{
|
||||
$trackedTime = $requestBody['tracked_time'];
|
||||
@ -26,6 +30,8 @@ class ChoresApiController extends BaseApiController
|
||||
{
|
||||
$doneBy = $requestBody['done_by'];
|
||||
}
|
||||
if($doneBy != GROCY_USER_ID)
|
||||
User::checkPermission($request, User::PERMISSION_CHORE_TRACK_OTHERS);
|
||||
|
||||
$choreExecutionId = $this->getChoresService()->TrackChore($args['choreId'], $trackedTime, $doneBy);
|
||||
return $this->ApiResponse($response, $this->getDatabase()->chores_log($choreExecutionId));
|
||||
@ -57,7 +63,9 @@ class ChoresApiController extends BaseApiController
|
||||
{
|
||||
try
|
||||
{
|
||||
$this->ApiResponse($response, $this->getChoresService()->UndoChoreExecution($args['executionId']));
|
||||
User::checkPermission($request, User::PERMISSION_CHORE_UNDO);
|
||||
|
||||
$this->ApiResponse($response, $this->getChoresService()->UndoChoreExecution($args['executionId']));
|
||||
return $this->EmptyApiResponse($response);
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
@ -70,7 +78,9 @@ class ChoresApiController extends BaseApiController
|
||||
{
|
||||
try
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
User::checkPermission($request, User::PERMISSION_CHORE_EDIT);
|
||||
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
$choreId = null;
|
||||
if (array_key_exists('chore_id', $requestBody) && !empty($requestBody['chore_id']) && is_numeric($requestBody['chore_id']))
|
||||
|
69
controllers/ExceptionController.php
Normal file
69
controllers/ExceptionController.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Slim\Exception\HttpException;
|
||||
use Slim\Exception\HttpForbiddenException;
|
||||
use Slim\Exception\HttpNotFoundException;
|
||||
use Throwable;
|
||||
|
||||
class ExceptionController extends BaseApiController
|
||||
{
|
||||
/**
|
||||
* @var \Slim\App
|
||||
*/
|
||||
private $app;
|
||||
|
||||
public function __construct(\Slim\App $app, \DI\Container $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->app = $app;
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequestInterface $request,
|
||||
Throwable $exception,
|
||||
bool $displayErrorDetails,
|
||||
bool $logErrors,
|
||||
bool $logErrorDetails,
|
||||
?LoggerInterface $logger = null)
|
||||
{
|
||||
$response = $this->app->getResponseFactory()->createResponse();
|
||||
|
||||
$isApiRoute = string_starts_with($request->getUri()->getPath(), '/api/');
|
||||
if ($isApiRoute) {
|
||||
$status = 500;
|
||||
if ($exception instanceof HttpException) {
|
||||
$status = $exception->getCode();
|
||||
}
|
||||
$data = [
|
||||
'error_message' => $exception->getMessage(),
|
||||
];
|
||||
if ($displayErrorDetails) {
|
||||
$data['error_details'] = [
|
||||
'stack_trace' => $exception->getTraceAsString(),
|
||||
'file' => $exception->getFile(),
|
||||
'line' => $exception->getLine(),
|
||||
];
|
||||
}
|
||||
return $this->ApiResponse($response->withStatus($status), $data);
|
||||
}
|
||||
if ($exception instanceof HttpNotFoundException) {
|
||||
return $this->renderPage($response->withStatus(404), 'errors/404', [
|
||||
'exception' => $exception
|
||||
]);
|
||||
}
|
||||
if ($exception instanceof HttpForbiddenException) {
|
||||
return $this->renderPage($response->withStatus(403), 'errors/403', [
|
||||
'exception' => $exception
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->renderPage($response->withStatus(500), 'errors/500', [
|
||||
'exception' => $exception
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use Grocy\Controllers\Users\User;
|
||||
use \Grocy\Services\FilesService;
|
||||
|
||||
class FilesApiController extends BaseApiController
|
||||
@ -15,7 +16,9 @@ class FilesApiController extends BaseApiController
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsValidFileName(base64_decode($args['fileName'])))
|
||||
User::checkPermission($request, User::PERMISSION_UPLOAD_FILE);
|
||||
|
||||
if (IsValidFileName(base64_decode($args['fileName'])))
|
||||
{
|
||||
$fileName = base64_decode($args['fileName']);
|
||||
}
|
||||
@ -97,7 +100,9 @@ class FilesApiController extends BaseApiController
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsValidFileName(base64_decode($args['fileName'])))
|
||||
User::checkPermission($request, User::PERMISSION_DELETE_FILE);
|
||||
|
||||
if (IsValidFileName(base64_decode($args['fileName'])))
|
||||
{
|
||||
$fileName = base64_decode($args['fileName']);
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use Grocy\Controllers\Users\User;
|
||||
|
||||
class GenericEntityApiController extends BaseApiController
|
||||
{
|
||||
public function __construct(\DI\Container $container)
|
||||
@ -11,7 +13,7 @@ class GenericEntityApiController extends BaseApiController
|
||||
|
||||
public function GetObjects(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
$objects = $this->getDatabase()->{$args['entity']}();
|
||||
$objects = $this->getDatabase()->{$args['entity']}();
|
||||
$allUserfields = $this->getUserfieldsService()->GetAllValues($args['entity']);
|
||||
|
||||
foreach ($objects as $object)
|
||||
@ -41,7 +43,7 @@ class GenericEntityApiController extends BaseApiController
|
||||
|
||||
public function GetObject(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
if ($this->IsValidEntity($args['entity']) && !$this->IsEntityWithPreventedListing($args['entity']))
|
||||
if ($this->IsValidEntity($args['entity']) && !$this->IsEntityWithPreventedListing($args['entity']))
|
||||
{
|
||||
$userfields = $this->getUserfieldsService()->GetValues($args['entity'], $args['objectId']);
|
||||
if (count($userfields) === 0)
|
||||
@ -66,7 +68,9 @@ class GenericEntityApiController extends BaseApiController
|
||||
|
||||
public function AddObject(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
if ($this->IsValidEntity($args['entity']))
|
||||
User::checkPermission($request, User::PERMISSION_MASTER_DATA_EDIT);
|
||||
|
||||
if ($this->IsValidEntity($args['entity']))
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
@ -97,7 +101,9 @@ class GenericEntityApiController extends BaseApiController
|
||||
|
||||
public function EditObject(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
if ($this->IsValidEntity($args['entity']))
|
||||
User::checkPermission($request, User::PERMISSION_MASTER_DATA_EDIT);
|
||||
|
||||
if ($this->IsValidEntity($args['entity']))
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
@ -126,7 +132,9 @@ class GenericEntityApiController extends BaseApiController
|
||||
|
||||
public function DeleteObject(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
if ($this->IsValidEntity($args['entity']))
|
||||
User::checkPermission($request, User::PERMISSION_MASTER_DATA_EDIT);
|
||||
|
||||
if ($this->IsValidEntity($args['entity']))
|
||||
{
|
||||
$row = $this->getDatabase()->{$args['entity']}($args['objectId']);
|
||||
$row->delete();
|
||||
@ -141,7 +149,8 @@ class GenericEntityApiController extends BaseApiController
|
||||
|
||||
public function SearchObjects(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
if ($this->IsValidEntity($args['entity']) && !$this->IsEntityWithPreventedListing($args['entity']))
|
||||
|
||||
if ($this->IsValidEntity($args['entity']) && !$this->IsEntityWithPreventedListing($args['entity']))
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -160,7 +169,7 @@ class GenericEntityApiController extends BaseApiController
|
||||
|
||||
public function GetUserfields(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
try
|
||||
try
|
||||
{
|
||||
return $this->ApiResponse($response, $this->getUserfieldsService()->GetValues($args['entity'], $args['objectId']));
|
||||
}
|
||||
@ -172,7 +181,9 @@ class GenericEntityApiController extends BaseApiController
|
||||
|
||||
public function SetUserfields(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
User::checkPermission($request, User::PERMISSION_MASTER_DATA_EDIT);
|
||||
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use Grocy\Controllers\Users\User;
|
||||
|
||||
class RecipesApiController extends BaseApiController
|
||||
{
|
||||
public function __construct(\DI\Container $container)
|
||||
@ -11,7 +13,9 @@ class RecipesApiController extends BaseApiController
|
||||
|
||||
public function AddNotFulfilledProductsToShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
User::checkPermission($request, User::PERMISSION_SHOPPINGLIST_ITEMS_ADD);
|
||||
|
||||
$requestBody = $request->getParsedBody();
|
||||
$excludedProductIds = null;
|
||||
|
||||
if ($requestBody !== null && array_key_exists('excludedProductIds', $requestBody))
|
||||
@ -25,7 +29,9 @@ class RecipesApiController extends BaseApiController
|
||||
|
||||
public function ConsumeRecipe(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
try
|
||||
User::checkPermission($request, User::PERMISSION_PRODUCT_CONSUME);
|
||||
|
||||
try
|
||||
{
|
||||
$this->getRecipesService()->ConsumeRecipe($args['recipeId']);
|
||||
return $this->EmptyApiResponse($response);
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use Grocy\Controllers\Users\User;
|
||||
use \Grocy\Services\StockService;
|
||||
|
||||
class StockApiController extends BaseApiController
|
||||
@ -62,7 +63,9 @@ class StockApiController extends BaseApiController
|
||||
|
||||
public function AddProduct(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
User::checkPermission($request, User::PERMISSION_PRODUCT_PURCHASE);
|
||||
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
try
|
||||
{
|
||||
@ -136,7 +139,9 @@ class StockApiController extends BaseApiController
|
||||
|
||||
public function EditStockEntry(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
User::checkPermission($request, User::PERMISSION_STOCK_EDIT);
|
||||
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
try
|
||||
{
|
||||
@ -185,7 +190,9 @@ class StockApiController extends BaseApiController
|
||||
|
||||
public function TransferProduct(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
User::checkPermission($request, User::PERMISSION_STOCK_TRANSFER);
|
||||
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
try
|
||||
{
|
||||
@ -239,7 +246,9 @@ class StockApiController extends BaseApiController
|
||||
|
||||
public function ConsumeProduct(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
User::checkPermission($request, User::PERMISSION_PRODUCT_CONSUME);
|
||||
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
$result = null;
|
||||
|
||||
@ -310,7 +319,9 @@ class StockApiController extends BaseApiController
|
||||
|
||||
public function InventoryProduct(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
User::checkPermission($request, User::PERMISSION_STOCK_CORRECTION);
|
||||
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
try
|
||||
{
|
||||
@ -372,7 +383,9 @@ class StockApiController extends BaseApiController
|
||||
|
||||
public function OpenProduct(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
User::checkPermission($request, User::PERMISSION_PRODUCT_OPEN);
|
||||
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
try
|
||||
{
|
||||
@ -439,7 +452,9 @@ class StockApiController extends BaseApiController
|
||||
|
||||
public function AddMissingProductsToShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
try
|
||||
User::checkPermission($request, User::PERMISSION_SHOPPINGLIST_ITEMS_ADD);
|
||||
|
||||
try
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
@ -460,7 +475,9 @@ class StockApiController extends BaseApiController
|
||||
|
||||
public function ClearShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
try
|
||||
User::checkPermission($request, User::PERMISSION_SHOPPINGLIST_ITEMS_DELETE);
|
||||
|
||||
try
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
@ -482,7 +499,9 @@ class StockApiController extends BaseApiController
|
||||
|
||||
public function AddProductToShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
try
|
||||
User::checkPermission($request, User::PERMISSION_SHOPPINGLIST_ITEMS_ADD);
|
||||
|
||||
try
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
@ -523,7 +542,9 @@ class StockApiController extends BaseApiController
|
||||
|
||||
public function RemoveProductFromShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
try
|
||||
User::checkPermission($request, User::PERMISSION_SHOPPINGLIST_ITEMS_DELETE);
|
||||
|
||||
try
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
@ -559,7 +580,9 @@ class StockApiController extends BaseApiController
|
||||
|
||||
public function ExternalBarcodeLookup(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
try
|
||||
User::checkPermission($request, User::PERMISSION_MASTER_DATA_EDIT);
|
||||
|
||||
try
|
||||
{
|
||||
$addFoundProduct = false;
|
||||
if (isset($request->getQueryParams()['add']) && ($request->getQueryParams()['add'] === 'true' || $request->getQueryParams()['add'] === 1))
|
||||
@ -577,7 +600,9 @@ class StockApiController extends BaseApiController
|
||||
|
||||
public function UndoBooking(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
try
|
||||
User::checkPermission($request, User::PERMISSION_STOCK_CORRECTION);
|
||||
|
||||
try
|
||||
{
|
||||
$this->ApiResponse($response, $this->getStockService()->UndoBooking($args['bookingId']));
|
||||
return $this->EmptyApiResponse($response);
|
||||
@ -590,7 +615,9 @@ class StockApiController extends BaseApiController
|
||||
|
||||
public function UndoTransaction(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
try
|
||||
User::checkPermission($request, User::PERMISSION_STOCK_CORRECTION);
|
||||
|
||||
try
|
||||
{
|
||||
$this->ApiResponse($response, $this->getStockService()->UndoTransaction($args['transactionId']));
|
||||
return $this->EmptyApiResponse($response);
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use Grocy\Controllers\Users\User;
|
||||
|
||||
class TasksApiController extends BaseApiController
|
||||
{
|
||||
public function __construct(\DI\Container $container)
|
||||
@ -16,7 +18,9 @@ class TasksApiController extends BaseApiController
|
||||
|
||||
public function MarkTaskAsCompleted(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
User::checkPermission($request, User::PERMISSION_TASKS_MARK_COMPLETED);
|
||||
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
try
|
||||
{
|
||||
@ -37,7 +41,9 @@ class TasksApiController extends BaseApiController
|
||||
|
||||
public function UndoTask(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
try
|
||||
User::checkPermission($request, User::PERMISSION_TASKS_UNDO);
|
||||
|
||||
try
|
||||
{
|
||||
$this->getTasksService()->UndoTask($args['taskId']);
|
||||
return $this->EmptyApiResponse($response);
|
||||
|
15
controllers/Users/PermissionMissingException.php
Normal file
15
controllers/Users/PermissionMissingException.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Grocy\Controllers\Users;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Slim\Exception\HttpForbiddenException;
|
||||
use Throwable;
|
||||
|
||||
class PermissionMissingException extends HttpForbiddenException
|
||||
{
|
||||
public function __construct(ServerRequestInterface $request, string $permission, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($request, 'Permission missing: ' . $permission, $previous);
|
||||
}
|
||||
}
|
91
controllers/Users/User.php
Normal file
91
controllers/Users/User.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Grocy\Controllers\Users;
|
||||
|
||||
use Grocy\Services\DatabaseService;
|
||||
use LessQL\Result;
|
||||
|
||||
class User
|
||||
{
|
||||
const PERMISSION_ADMIN = 'ADMIN';
|
||||
const PERMISSION_CREATE_USER = 'CREATE_USER';
|
||||
const PERMISSION_EDIT_USER = 'EDIT_USER';
|
||||
const PERMISSION_READ_USER = 'READ_USER';
|
||||
const PERMISSION_EDIT_SELF = 'EDIT_SELF';
|
||||
const PERMISSION_BATTERY_UNDO_TRACK_CHARGE_CYCLE = 'BATTERY_UNDO_TRACK_CHARGE_CYCLE';
|
||||
const PERMISSION_BATTERY_TRACK_CHARGE_CYCLE = 'BATTERY_TRACK_CHARGE_CYCLE';
|
||||
const PERMISSION_CHORE_TRACK = 'CHORE_TRACK';
|
||||
const PERMISSION_CHORE_TRACK_OTHERS = 'CHORE_TRACK_OTHERS';
|
||||
const PERMISSION_CHORE_EDIT = 'CHORE_EDIT';
|
||||
const PERMISSION_CHORE_UNDO = 'CHORE_UNDO';
|
||||
const PERMISSION_UPLOAD_FILE = 'UPLOAD_FILE';
|
||||
const PERMISSION_DELETE_FILE = 'DELETE_FILE';
|
||||
const PERMISSION_MASTER_DATA_EDIT = 'MASTER_DATA_EDIT';
|
||||
const PERMISSION_TASKS_UNDO = 'TASKS_UNDO';
|
||||
const PERMISSION_TASKS_MARK_COMPLETED = 'TASKS_MARK_COMPLETED';
|
||||
const PERMISSION_STOCK_TRANSFER = 'STOCK_TRANSFER';
|
||||
const PERMISSION_STOCK_EDIT = 'STOCK_EDIT';
|
||||
const PERMISSION_PRODUCT_CONSUME = 'PRODUCT_CONSUME';
|
||||
const PERMISSION_STOCK_CORRECTION = 'STOCK_CORRECTION';
|
||||
const PERMISSION_PRODUCT_OPEN = 'PRODUCT_OPEN';
|
||||
const PERMISSION_SHOPPINGLIST_ITEMS_ADD = 'SHOPPINGLIST_ITEMS_ADD';
|
||||
const PERMISSION_SHOPPINGLIST_ITEMS_DELETE = 'SHOPPINGLIST_ITEMS_DELETE';
|
||||
const PERMISSION_PRODUCT_PURCHASE = 'PRODUCT_PURCHASE';
|
||||
|
||||
/**
|
||||
* @var \LessQL\Database|null
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = DatabaseService::getInstance()->GetDbConnection();
|
||||
|
||||
}
|
||||
|
||||
protected function getPermissions(): Result
|
||||
{
|
||||
return $this->db->user_permissions_resolved()->where('user_id', GROCY_USER_ID);
|
||||
}
|
||||
|
||||
public function hasPermission(string $permission): bool
|
||||
{
|
||||
// global $PERMISSION_CACHE;
|
||||
// if(isset($PERMISSION_CACHE[$permission]))
|
||||
// return $PERMISSION_CACHE[$permission];
|
||||
return $this->getPermissions()->where('permission_name', $permission)->fetch() !== null;
|
||||
}
|
||||
|
||||
public static function checkPermission($request, string ...$permissions): void
|
||||
{
|
||||
$user = new User();
|
||||
foreach ($permissions as $permission) {
|
||||
if (!$user->hasPermission($permission)) {
|
||||
throw new PermissionMissingException($request, $permission);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getPermissionList()
|
||||
{
|
||||
return $this->db->uihelper_user_permissions()->where('user_id', GROCY_USER_ID);
|
||||
}
|
||||
|
||||
public static function hasPermissions(string ...$permissions)
|
||||
{
|
||||
$user = new User();
|
||||
foreach ($permissions as $permission) {
|
||||
if (!$user->hasPermission($permission)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function PermissionList()
|
||||
{
|
||||
$user = new User();
|
||||
return $user->getPermissionList();
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use Grocy\Controllers\Users\User;
|
||||
|
||||
class UsersApiController extends BaseApiController
|
||||
{
|
||||
public function __construct(\DI\Container $container)
|
||||
@ -11,7 +13,8 @@ class UsersApiController extends BaseApiController
|
||||
|
||||
public function GetUsers(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
try
|
||||
User::checkPermission($request, User::PERMISSION_READ_USER);
|
||||
try
|
||||
{
|
||||
return $this->ApiResponse($response, $this->getUsersService()->GetUsersAsDto());
|
||||
}
|
||||
@ -23,6 +26,7 @@ class UsersApiController extends BaseApiController
|
||||
|
||||
public function CreateUser(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
User::checkPermission($request, User::PERMISSION_CREATE_USER);
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
try
|
||||
@ -43,7 +47,8 @@ class UsersApiController extends BaseApiController
|
||||
|
||||
public function DeleteUser(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
try
|
||||
User::checkPermission($request, User::PERMISSION_EDIT_USER);
|
||||
try
|
||||
{
|
||||
$this->getUsersService()->DeleteUser($args['userId']);
|
||||
return $this->EmptyApiResponse($response);
|
||||
@ -56,7 +61,12 @@ class UsersApiController extends BaseApiController
|
||||
|
||||
public function EditUser(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
if ($args['userId'] == GROCY_USER_ID) {
|
||||
User::checkPermission($request, User::PERMISSION_EDIT_SELF);
|
||||
} else {
|
||||
User::checkPermission($request, User::PERMISSION_EDIT_USER);
|
||||
}
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
try
|
||||
{
|
||||
@ -108,4 +118,66 @@ class UsersApiController extends BaseApiController
|
||||
return $this->GenericErrorResponse($response, $ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function AddPermission(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
try {
|
||||
User::checkPermission($request, User::PERMISSION_ADMIN);
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
$this->getDatabase()->user_permissions()->createRow(array(
|
||||
'user_id' => $args['userId'],
|
||||
'permission_id' => $requestBody['permission_id'],
|
||||
))->save();
|
||||
return $this->EmptyApiResponse($response);
|
||||
} catch (\Slim\Exception\HttpSpecializedException $ex) {
|
||||
return $this->GenericErrorResponse($response, $ex->getMessage(), $ex->getCode());
|
||||
} catch (\Exception $ex) {
|
||||
return $this->GenericErrorResponse($response, $ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function ListPermissions(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
try {
|
||||
User::checkPermission($request, User::PERMISSION_ADMIN);
|
||||
|
||||
return $this->ApiResponse($response,
|
||||
$this->getDatabase()->user_permissions()->where($args['userId'])
|
||||
);
|
||||
} catch (\Slim\Exception\HttpSpecializedException $ex) {
|
||||
return $this->GenericErrorResponse($response, $ex->getMessage(), $ex->getCode());
|
||||
} catch (\Exception $ex) {
|
||||
return $this->GenericErrorResponse($response, $ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function SetPermissions(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
try {
|
||||
User::checkPermission($request, User::PERMISSION_ADMIN);
|
||||
$requestBody = $request->getParsedBody();
|
||||
$db = $this->getDatabase();
|
||||
$db->user_permissions()
|
||||
->where('user_id', $args['userId'])
|
||||
->delete();
|
||||
|
||||
$perms = [];
|
||||
|
||||
foreach ($requestBody['permissions'] as $perm_id) {
|
||||
$perms[] = array(
|
||||
'user_id' => $args['userId'],
|
||||
'permission_id' => $perm_id
|
||||
);
|
||||
}
|
||||
|
||||
$db->insert('user_permissions', $perms, 'batch');
|
||||
|
||||
return $this->EmptyApiResponse($response);
|
||||
} catch (\Slim\Exception\HttpSpecializedException $ex) {
|
||||
return $this->GenericErrorResponse($response, $ex->getMessage(), $ex->getCode());
|
||||
} catch (\Exception $ex) {
|
||||
return $this->GenericErrorResponse($response, $ex->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,14 @@
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use Grocy\Controllers\Users\User;
|
||||
|
||||
class UsersController extends BaseController
|
||||
{
|
||||
public function UsersList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
return $this->renderPage($response, 'users', [
|
||||
User::checkPermission($request, User::PERMISSION_READ_USER);
|
||||
return $this->renderPage($response, 'users', [
|
||||
'users' => $this->getDatabase()->users()->orderBy('username')
|
||||
]);
|
||||
}
|
||||
@ -15,16 +18,30 @@ class UsersController extends BaseController
|
||||
{
|
||||
if ($args['userId'] == 'new')
|
||||
{
|
||||
return $this->renderPage($response, 'userform', [
|
||||
User::checkPermission($request, User::PERMISSION_CREATE_USER);
|
||||
return $this->renderPage($response, 'userform', [
|
||||
'mode' => 'create'
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->renderPage($response, 'userform', [
|
||||
if($args['userId'] == GROCY_USER_ID)
|
||||
User::checkPermission($request, User::PERMISSION_EDIT_SELF);
|
||||
else User::checkPermission($request, User::PERMISSION_EDIT_USER);
|
||||
return $this->renderPage($response, 'userform', [
|
||||
'user' => $this->getDatabase()->users($args['userId']),
|
||||
'mode' => 'edit'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function PermissionList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
User::checkPermission($request, User::PERMISSION_READ_USER);
|
||||
return $this->renderPage($response, 'userpermissions', [
|
||||
'user' => $this->getDatabase()->users($args['userId']),
|
||||
'permissions' => $this->getDatabase()->uihelper_user_permissions()
|
||||
->where('parent IS NULL')->where('user_id', $args['userId']),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
85
localization/en/permissions.po
Normal file
85
localization/en/permissions.po
Normal file
@ -0,0 +1,85 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
|
||||
"PO-Revision-Date: 2019-05-01T17:59:17+00:00\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: http://www.transifex.com/grocy/grocy/language/en\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: en\n"
|
||||
"X-Domain: grocy/permissions\n"
|
||||
|
||||
msgid "ADMIN"
|
||||
msgstr ""
|
||||
|
||||
msgid "CREATE_USER"
|
||||
msgstr "Create new users"
|
||||
|
||||
msgid "EDIT_USER"
|
||||
msgstr "Edit existing users (including passwords)"
|
||||
|
||||
msgid "READ_USER"
|
||||
msgstr "View user data"
|
||||
|
||||
msgid "EDIT_SELF"
|
||||
msgstr "Edit own user data, e.g. password and name"
|
||||
|
||||
msgid "BATTERY_UNDO_TRACK_CHARGE_CYCLE"
|
||||
msgstr "Batteries: undo tracking of charge cycles"
|
||||
|
||||
msgid "BATTERY_TRACK_CHARGE_CYCLE"
|
||||
msgstr "Batteries: track charge cycle"
|
||||
|
||||
msgid "CHORE_TRACK"
|
||||
msgstr "Chores: track execution"
|
||||
|
||||
msgid "CHORE_TRACK_OTHERS"
|
||||
msgstr "Chores: Track execution for others"
|
||||
|
||||
msgid "CHORE_EDIT"
|
||||
msgstr "Chores: Edit chore data"
|
||||
|
||||
msgid "CHORE_UNDO"
|
||||
msgstr "Chores: undo tracked execution"
|
||||
|
||||
msgid "UPLOAD_FILE"
|
||||
msgstr "Upload files, e.g. product images"
|
||||
|
||||
msgid "DELETE_FILE"
|
||||
msgstr "Delete (uploaded) files"
|
||||
|
||||
msgid "MASTER_DATA_EDIT"
|
||||
msgstr "Edit Master data (e.g. products)"
|
||||
|
||||
msgid "TASKS_UNDO"
|
||||
msgstr "Tasks: undo tracked execution"
|
||||
|
||||
msgid "TASKS_MARK_COMPLETED"
|
||||
msgstr "Tasks: mark as completed"
|
||||
|
||||
msgid "STOCK_EDIT"
|
||||
msgstr "Stock: edit entries"
|
||||
|
||||
msgid "STOCK_TRANSFER"
|
||||
msgstr "Stock: transfer products between locations"
|
||||
|
||||
msgid "STOCK_CORRECTION"
|
||||
msgstr "Stock: correct wrong entries"
|
||||
|
||||
msgid "PRODUCT_CONSUME"
|
||||
msgstr "Consume Products"
|
||||
|
||||
msgid "PRODUCT_OPEN"
|
||||
msgstr "Open products"
|
||||
|
||||
msgid "PRODUCT_PURCHASE"
|
||||
msgstr "Purchase new products and add them to stock"
|
||||
|
||||
msgid "SHOPPINGLIST_ITEMS_ADD"
|
||||
msgstr "Add items to shopping list"
|
||||
|
||||
msgid "SHOPPINGLIST_ITEMS_DELETE"
|
||||
msgstr "Remove items from shopping list"
|
85
localization/permissions.pot
Normal file
85
localization/permissions.pot
Normal file
@ -0,0 +1,85 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: http://www.transifex.com/grocy/grocy/language/en\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"POT-Creation-Date: 2019-05-01T17:59:17+00:00\n"
|
||||
"PO-Revision-Date: 2019-05-01T17:59:17+00:00\n"
|
||||
"Language: en\n"
|
||||
"X-Domain: grocy/permissions\n"
|
||||
|
||||
msgid "ADMIN"
|
||||
msgstr ""
|
||||
|
||||
msgid "CREATE_USER"
|
||||
msgstr ""
|
||||
|
||||
msgid "EDIT_USER"
|
||||
msgstr ""
|
||||
|
||||
msgid "READ_USER"
|
||||
msgstr ""
|
||||
|
||||
msgid "EDIT_SELF"
|
||||
msgstr ""
|
||||
|
||||
msgid "BATTERY_UNDO_TRACK_CHARGE_CYCLE"
|
||||
msgstr ""
|
||||
|
||||
msgid "BATTERY_TRACK_CHARGE_CYCLE"
|
||||
msgstr ""
|
||||
|
||||
msgid "CHORE_TRACK"
|
||||
msgstr ""
|
||||
|
||||
msgid "CHORE_TRACK_OTHERS"
|
||||
msgstr ""
|
||||
|
||||
msgid "CHORE_EDIT"
|
||||
msgstr ""
|
||||
|
||||
msgid "CHORE_UNDO"
|
||||
msgstr ""
|
||||
|
||||
msgid "UPLOAD_FILE"
|
||||
msgstr ""
|
||||
|
||||
msgid "DELETE_FILE"
|
||||
msgstr ""
|
||||
|
||||
msgid "MASTER_DATA_EDIT"
|
||||
msgstr ""
|
||||
|
||||
msgid "TASKS_UNDO"
|
||||
msgstr ""
|
||||
|
||||
msgid "TASKS_MARK_COMPLETED"
|
||||
msgstr ""
|
||||
|
||||
msgid "STOCK_EDIT"
|
||||
msgstr ""
|
||||
|
||||
msgid "STOCK_TRANSFER"
|
||||
msgstr ""
|
||||
|
||||
msgid "STOCK_CORRECTION"
|
||||
msgstr ""
|
||||
|
||||
msgid "PRODUCT_CONSUME"
|
||||
msgstr ""
|
||||
|
||||
msgid "PRODUCT_OPEN"
|
||||
msgstr ""
|
||||
|
||||
msgid "PRODUCT_PURCHASE"
|
||||
msgstr ""
|
||||
|
||||
msgid "SHOPPINGLIST_ITEMS_ADD"
|
||||
msgstr ""
|
||||
|
||||
msgid "SHOPPINGLIST_ITEMS_DELETE"
|
||||
msgstr ""
|
@ -1849,3 +1849,45 @@ msgstr ""
|
||||
|
||||
msgid "Clear filter"
|
||||
msgstr ""
|
||||
|
||||
msgid "Permissions for user %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Are you sure you want to stop being an ADMIN?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Permissions saved"
|
||||
msgstr ""
|
||||
|
||||
msgid "You are not allowed to view this page"
|
||||
msgstr ""
|
||||
|
||||
msgid "Page not found"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unauthorized"
|
||||
msgstr ""
|
||||
|
||||
msgid "Error source"
|
||||
msgstr ""
|
||||
|
||||
msgid "Error message"
|
||||
msgstr ""
|
||||
|
||||
msgid "Stack trace"
|
||||
msgstr ""
|
||||
|
||||
msgid "This page does not exists"
|
||||
msgstr ""
|
||||
|
||||
msgid "You will be redirected to the default page in %s seconds"
|
||||
msgstr ""
|
||||
|
||||
msgid "Server error"
|
||||
msgstr ""
|
||||
|
||||
msgid "A server error occured while processing your request"
|
||||
msgstr ""
|
||||
|
||||
msgid "If you think this is a bug, please report it"
|
||||
msgstr ""
|
||||
|
109
migrations/0111.sql
Normal file
109
migrations/0111.sql
Normal file
@ -0,0 +1,109 @@
|
||||
CREATE TABLE user_permissions
|
||||
(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
permission_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
|
||||
UNIQUE (user_id, permission_id)
|
||||
);
|
||||
|
||||
CREATE TABLE permission_hierarchy
|
||||
(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
parent INTEGER NULL -- If the user has the parent permission, the user also has the child permission
|
||||
);
|
||||
|
||||
INSERT INTO permission_hierarchy
|
||||
(name, parent)
|
||||
VALUES
|
||||
('ADMIN', NULL);
|
||||
|
||||
INSERT INTO user_permissions
|
||||
(permission_id, user_id)
|
||||
SELECT (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN'), id
|
||||
FROM users;
|
||||
|
||||
CREATE VIEW permission_tree
|
||||
AS
|
||||
WITH RECURSIVE perm AS (
|
||||
SELECT id AS root, id AS child, name, parent
|
||||
FROM permission_hierarchy
|
||||
UNION
|
||||
SELECT perm.root, ph.id, ph.name, ph.id
|
||||
FROM permission_hierarchy ph, perm
|
||||
WHERE ph.parent = perm.child
|
||||
)
|
||||
SELECT root AS id, name AS name
|
||||
FROM perm;
|
||||
|
||||
CREATE VIEW user_permissions_resolved
|
||||
AS
|
||||
SELECT
|
||||
u.id AS id, -- Dummy for LessQL
|
||||
u.id AS user_id,
|
||||
pt.name AS permission_name
|
||||
FROM permission_tree pt, users u
|
||||
WHERE pt.id IN (SELECT permission_id FROM user_permissions sub_up WHERE sub_up.user_id = u.id);
|
||||
|
||||
CREATE VIEW uihelper_user_permissions
|
||||
AS
|
||||
SELECT
|
||||
ph.id AS id,
|
||||
u.id AS user_id,
|
||||
ph.name AS permission_name,
|
||||
ph.id AS permission_id,
|
||||
(ph.name IN (
|
||||
SELECT pc.permission_name
|
||||
FROM user_permissions_resolved pc
|
||||
WHERE pc.user_id = u.id
|
||||
)
|
||||
) AS has_permission,
|
||||
ph.parent AS parent
|
||||
FROM users u, permission_hierarchy ph;
|
||||
|
||||
INSERT INTO permission_hierarchy
|
||||
(name, parent)
|
||||
VALUES
|
||||
('CREATE_USER', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN'));
|
||||
|
||||
INSERT INTO permission_hierarchy
|
||||
(name, parent)
|
||||
VALUES
|
||||
('EDIT_USER', last_insert_rowid());
|
||||
|
||||
INSERT INTO permission_hierarchy
|
||||
(name, parent)
|
||||
VALUES
|
||||
('READ_USER', last_insert_rowid()),
|
||||
('EDIT_SELF', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN'));
|
||||
|
||||
INSERT INTO permission_hierarchy
|
||||
(name, parent)
|
||||
VALUES
|
||||
-- Batteries
|
||||
('BATTERY_UNDO_TRACK_CHARGE_CYCLE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
|
||||
('BATTERY_TRACK_CHARGE_CYCLE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
|
||||
-- Chores
|
||||
('CHORE_TRACK', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
|
||||
('CHORE_TRACK_OTHERS', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
|
||||
('CHORE_EDIT', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
|
||||
('CHORE_UNDO', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
|
||||
-- Files
|
||||
('UPLOAD_FILE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
|
||||
('DELETE_FILE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
|
||||
-- master data
|
||||
('MASTER_DATA_EDIT', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
|
||||
-- Tasks
|
||||
('TASKS_UNDO', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
|
||||
('TASKS_MARK_COMPLETED', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
|
||||
-- Stock / Products
|
||||
('STOCK_EDIT', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
|
||||
('STOCK_TRANSFER', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
|
||||
('STOCK_CORRECTION', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
|
||||
('PRODUCT_PURCHASE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
|
||||
('PRODUCT_CONSUME', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
|
||||
('PRODUCT_OPEN', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
|
||||
-- shopping list
|
||||
('SHOPPINGLIST_ITEMS_ADD', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
|
||||
('SHOPPINGLIST_ITEMS_DELETE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN'));
|
@ -581,3 +581,7 @@ canvas.drawingBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
.not-allowed {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
@ -662,3 +662,11 @@ $.extend(true, $.fn.dataTable.defaults, {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$(Grocy.UserPermissions).each(function (index, item)
|
||||
{
|
||||
if(item.has_permission == 0)
|
||||
{
|
||||
$('.permission-'+item.permission_name).addClass('disabled').addClass('not-allowed');
|
||||
}
|
||||
});
|
||||
|
42
public/viewjs/userpermissions.js
Normal file
42
public/viewjs/userpermissions.js
Normal file
@ -0,0 +1,42 @@
|
||||
$('input.permission-cb').click(
|
||||
function () {
|
||||
check_hierachy(this.checked, this.name);
|
||||
}
|
||||
);
|
||||
|
||||
function check_hierachy(checked, name) {
|
||||
var disabled = checked;
|
||||
$('#permission-sub-' + name).find('input.permission-cb')
|
||||
.prop('checked', disabled)
|
||||
.attr('disabled', disabled);
|
||||
}
|
||||
|
||||
$('#permission-save').click(
|
||||
function () {
|
||||
var permission_list = $('input.permission-cb')
|
||||
.filter(function () {
|
||||
return $(this).prop('checked') && !$(this).attr('disabled');
|
||||
}).map(function () {
|
||||
return $(this).data('perm-id');
|
||||
}).toArray();
|
||||
Grocy.Api.Put('users/' + Grocy.EditObjectId + '/permissions', {
|
||||
'permissions': permission_list,
|
||||
}, function (result) {
|
||||
toastr.success(__t("Permissions saved"));
|
||||
}, function (xhr) {
|
||||
toastr.error(__t(JSON.parse(xhr.response).error_message));
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
if (Grocy.EditObjectId == Grocy.UserId) {
|
||||
$('input.permission-cb[name=ADMIN]').click(function () {
|
||||
if (!this.checked) {
|
||||
if (!confirm(__t('Are you sure you want to stop being an ADMIN?'))) {
|
||||
this.checked = true;
|
||||
check_hierachy(this.checked, this.name);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@ -33,8 +33,9 @@ $app->group('', function(RouteCollectorProxy $group)
|
||||
// User routes
|
||||
$group->get('/users', '\Grocy\Controllers\UsersController:UsersList');
|
||||
$group->get('/user/{userId}', '\Grocy\Controllers\UsersController:UserEditForm');
|
||||
$group->get('/user/{userId}/permissions', '\Grocy\Controllers\UsersController:PermissionList');
|
||||
|
||||
// Stock routes
|
||||
// Stock routes
|
||||
if (GROCY_FEATURE_FLAG_STOCK)
|
||||
{
|
||||
$group->get('/stockoverview', '\Grocy\Controllers\StockController:Overview');
|
||||
@ -168,8 +169,11 @@ $app->group('/api', function(RouteCollectorProxy $group)
|
||||
$group->post('/users', '\Grocy\Controllers\UsersApiController:CreateUser');
|
||||
$group->put('/users/{userId}', '\Grocy\Controllers\UsersApiController:EditUser');
|
||||
$group->delete('/users/{userId}', '\Grocy\Controllers\UsersApiController:DeleteUser');
|
||||
$group->get('/users/{userId}/permissions', '\Grocy\Controllers\UsersApiController:ListPermissions');
|
||||
$group->post('/users/{userId}/permissions', '\Grocy\Controllers\UsersApiController:AddPermission');
|
||||
$group->put('/users/{userId}/permissions', '\Grocy\Controllers\UsersApiController:SetPermissions');
|
||||
|
||||
// User
|
||||
// User
|
||||
$group->get('/user/settings', '\Grocy\Controllers\UsersApiController:GetUserSettings');
|
||||
$group->get('/user/settings/{settingKey}', '\Grocy\Controllers\UsersApiController:GetUserSetting');
|
||||
$group->put('/user/settings/{settingKey}', '\Grocy\Controllers\UsersApiController:SetUserSetting');
|
||||
|
@ -60,6 +60,7 @@ class LocalizationService
|
||||
$this->Pot = $this->Pot->mergeWith(Translations::fromPoFile(__DIR__ . '/../localization/stock_transaction_types.pot'));
|
||||
$this->Pot = $this->Pot->mergeWith(Translations::fromPoFile(__DIR__ . '/../localization/strings.pot'));
|
||||
$this->Pot = $this->Pot->mergeWith(Translations::fromPoFile(__DIR__ . '/../localization/userfield_types.pot'));
|
||||
$this->Pot = $this->Pot->mergeWith(Translations::fromPoFile(__DIR__ . '/../localization/permissions.pot'));
|
||||
|
||||
if (GROCY_MODE !== 'production')
|
||||
{
|
||||
@ -91,6 +92,10 @@ class LocalizationService
|
||||
{
|
||||
$this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/userfield_types.po"));
|
||||
}
|
||||
if (file_exists(__DIR__ . "/../localization/$culture/permissions.po"))
|
||||
{
|
||||
$this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/permissions.po"));
|
||||
}
|
||||
if (GROCY_MODE !== 'production' && file_exists(__DIR__ . "/../localization/$culture/demo_data.po"))
|
||||
{
|
||||
$this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/demo_data.po"));
|
||||
|
@ -12,7 +12,17 @@ class UsersService extends BaseService
|
||||
'last_name' => $lastName,
|
||||
'password' => password_hash($password, PASSWORD_DEFAULT)
|
||||
));
|
||||
return $newUserRow->save();
|
||||
$newUserRow = $newUserRow->save();
|
||||
$permList = array();
|
||||
foreach ($this->getDatabase()->permission_hierarchy()->where('name', GROCY_DEFAULT_PERMISSIONS)->fetchAll() as $perm) {
|
||||
$permList[] = array(
|
||||
'user_id' => $newUserRow->id,
|
||||
'permission_id' => $perm->id
|
||||
);
|
||||
}
|
||||
$this->getDatabase()->user_permissions()->insert($permList);
|
||||
|
||||
return $newUserRow;
|
||||
}
|
||||
|
||||
public function EditUser(int $userId, string $username, string $firstName, string $lastName, string $password)
|
||||
|
@ -20,7 +20,7 @@
|
||||
<hr>
|
||||
<div class="row mt-3">
|
||||
<div class="col-xs-12 col-md-2 col-xl-1">
|
||||
<a class="btn btn-primary btn-sm responsive-button w-100 mb-3" href="{{ $U('/battery/new') }}">
|
||||
<a class="btn btn-primary btn-sm responsive-button w-100 mb-3 permission-MASTER_DATA_EDIT" href="{{ $U('/battery/new') }}">
|
||||
{{ $__t('Add') }}
|
||||
</a>
|
||||
</div>
|
||||
@ -57,10 +57,10 @@
|
||||
@foreach($batteries as $battery)
|
||||
<tr>
|
||||
<td class="fit-content border-right">
|
||||
<a class="btn btn-info btn-sm" href="{{ $U('/battery/') }}{{ $battery->id }}">
|
||||
<a class="btn btn-info btn-sm permission-MASTER_DATA_EDIT" href="{{ $U('/battery/') }}{{ $battery->id }}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<a class="btn btn-danger btn-sm battery-delete-button" href="#" data-battery-id="{{ $battery->id }}" data-battery-name="{{ $battery->name }}">
|
||||
<a class="btn btn-danger btn-sm battery-delete-button permission-MASTER_DATA_EDIT" href="#" data-battery-id="{{ $battery->id }}" data-battery-name="{{ $battery->name }}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
|
@ -50,7 +50,7 @@
|
||||
@foreach($chargeCycles as $chargeCycleEntry)
|
||||
<tr id="charge-cycle-{{ $chargeCycleEntry->id }}-row" class="@if($chargeCycleEntry->undone == 1) text-muted @endif">
|
||||
<td class="fit-content border-right">
|
||||
<a class="btn btn-secondary btn-sm undo-battery-execution-button @if($chargeCycleEntry->undone == 1) disabled @endif" href="#" data-charge-cycle-id="{{ $chargeCycleEntry->id }}" data-toggle="tooltip" data-placement="left" title="{{ $__t('Undo charge cycle') }}">
|
||||
<a class="btn btn-secondary btn-sm undo-battery-execution-button @if($chargeCycleEntry->undone == 1) disabled @endif permission-BATTERY_UNDO_TRACK_CHARGE_CYCLE" href="#" data-charge-cycle-id="{{ $chargeCycleEntry->id }}" data-toggle="tooltip" data-placement="left" title="{{ $__t('Undo charge cycle') }}">
|
||||
<i class="fas fa-undo"></i>
|
||||
</a>
|
||||
</td>
|
||||
|
@ -69,7 +69,7 @@
|
||||
@foreach($current as $currentBatteryEntry)
|
||||
<tr id="battery-{{ $currentBatteryEntry->battery_id }}-row" class="@if(FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->charge_interval_days > 0 && $currentBatteryEntry->next_estimated_charge_time < date('Y-m-d H:i:s')) table-danger @elseif(FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->charge_interval_days > 0 && $currentBatteryEntry->next_estimated_charge_time < date('Y-m-d H:i:s', strtotime("+$nextXDays days"))) table-warning @endif">
|
||||
<td class="fit-content border-right">
|
||||
<a class="btn btn-success btn-sm track-charge-cycle-button" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Track charge cycle of battery %s', FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->name) }}"
|
||||
<a class="btn btn-success btn-sm track-charge-cycle-button permission-BATTERY_TRACK_CHARGE_CYCLE" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Track charge cycle of battery %s', FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->name) }}"
|
||||
data-battery-id="{{ $currentBatteryEntry->battery_id }}"
|
||||
data-battery-name="{{ FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->name }}">
|
||||
<i class="fas fa-fire"></i>
|
||||
@ -85,7 +85,7 @@
|
||||
<a class="dropdown-item" type="button" href="{{ $U('/batteriesjournal?battery=') }}{{ $currentBatteryEntry->battery_id }}">
|
||||
<span class="dropdown-item-icon"><i class="fas fa-file-alt"></i></span> <span class="dropdown-item-text">{{ $__t('Journal for this battery') }}</span>
|
||||
</a>
|
||||
<a class="dropdown-item" type="button" href="{{ $U('/battery/') }}{{ $currentBatteryEntry->battery_id }}">
|
||||
<a class="dropdown-item permission-MASTER_DATA_EDIT" type="button" href="{{ $U('/battery/') }}{{ $currentBatteryEntry->battery_id }}">
|
||||
<span class="dropdown-item-icon"><i class="fas fa-edit"></i></span> <span class="dropdown-item-text">{{ $__t('Edit battery') }}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -53,7 +53,7 @@
|
||||
@foreach($choresLog as $choreLogEntry)
|
||||
<tr id="chore-execution-{{ $choreLogEntry->id }}-row" class="@if($choreLogEntry->undone == 1) text-muted @endif">
|
||||
<td class="fit-content border-right">
|
||||
<a class="btn btn-secondary btn-sm undo-chore-execution-button @if($choreLogEntry->undone == 1) disabled @endif" href="#" data-execution-id="{{ $choreLogEntry->id }}" data-toggle="tooltip" data-placement="left" title="{{ $__t('Undo chore execution') }}">
|
||||
<a class="btn btn-secondary btn-sm undo-chore-execution-button permission-CHORE_UNDO @if($choreLogEntry->undone == 1) disabled @endif" href="#" data-execution-id="{{ $choreLogEntry->id }}" data-toggle="tooltip" data-placement="left" title="{{ $__t('Undo chore execution') }}">
|
||||
<i class="fas fa-undo"></i>
|
||||
</a>
|
||||
</td>
|
||||
|
@ -95,7 +95,7 @@
|
||||
@foreach($currentChores as $curentChoreEntry)
|
||||
<tr id="chore-{{ $curentChoreEntry->chore_id }}-row" class="@if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_PERIOD_TYPE_MANUALLY && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s')) table-danger @elseif(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_PERIOD_TYPE_MANUALLY && $curentChoreEntry->next_estimated_execution_time < date('Y-m-d H:i:s', strtotime("+$nextXDays days"))) table-warning @endif">
|
||||
<td class="fit-content border-right">
|
||||
<a class="btn btn-success btn-sm track-chore-button" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Track execution of chore %s', FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name) }}"
|
||||
<a class="btn btn-success btn-sm track-chore-button permission-CHORE_TRACK" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Track execution of chore %s', FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name) }}"
|
||||
data-chore-id="{{ $curentChoreEntry->chore_id }}"
|
||||
data-chore-name="{{ FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name }}">
|
||||
<i class="fas fa-play"></i>
|
||||
@ -111,7 +111,7 @@
|
||||
<a class="dropdown-item" type="button" href="{{ $U('/choresjournal?chore=') }}{{ $curentChoreEntry->chore_id }}">
|
||||
<span class="dropdown-item-icon"><i class="fas fa-file-alt"></i></span> <span class="dropdown-item-text">{{ $__t('Journal for this chore') }}</span>
|
||||
</a>
|
||||
<a class="dropdown-item" type="button" href="{{ $U('/chore/') }}{{ $curentChoreEntry->chore_id }}">
|
||||
<a class="dropdown-item permission-MASTER_DATA_EDIT" type="button" href="{{ $U('/chore/') }}{{ $curentChoreEntry->chore_id }}">
|
||||
<span class="dropdown-item-icon"><i class="fas fa-edit"></i></span> <span class="dropdown-item-text">{{ $__t('Edit chore') }}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
15
views/components/userpermission_select.blade.php
Normal file
15
views/components/userpermission_select.blade.php
Normal file
@ -0,0 +1,15 @@
|
||||
<label>
|
||||
<input type="checkbox" name="{{ $perm->permission_name }}" class="permission-cb" data-perm-id="{{ $perm->permission_id }}" @if($perm->has_permission) checked @endif autocomplete="off">
|
||||
{{ $__t($perm->permission_name) }}
|
||||
</label>
|
||||
<div id="permission-sub-{{ $perm->permission_name }}">
|
||||
<ul>
|
||||
@foreach($perm->uihelper_user_permissionsList(array('user_id' => $user->id))->via('parent') as $p)
|
||||
<li>
|
||||
@include('components.userpermission_select', array(
|
||||
'perm' => $p
|
||||
))
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
11
views/errors/403.blade.php
Normal file
11
views/errors/403.blade.php
Normal file
@ -0,0 +1,11 @@
|
||||
@extends('errors.base')
|
||||
|
||||
@section('title', $__t('Unauthorized'))
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="alert alert-danger">{{ $__t('You are not allowed to view this page') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@stop
|
13
views/errors/404.blade.php
Normal file
13
views/errors/404.blade.php
Normal file
@ -0,0 +1,13 @@
|
||||
@extends('errors.base')
|
||||
|
||||
@section('title', $__t('Page not found'))
|
||||
|
||||
@section('content')
|
||||
<meta http-equiv="refresh" content="5;url={{$U('/')}}">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="alert alert-danger">{{ $__t('This page does not exists') }}</div>
|
||||
<div>{{ $__t('You will be redirected to the default page in %s seconds', '5') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@stop
|
16
views/errors/500.blade.php
Normal file
16
views/errors/500.blade.php
Normal file
@ -0,0 +1,16 @@
|
||||
@extends('errors.base')
|
||||
|
||||
@section('title', $__t('Server error'))
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="alert alert-danger">{{ $__t('A server error occured while processing your request') }}</div>
|
||||
<div class="alert alert-warning">
|
||||
{{ $__t('If you think this is a bug, please report it') }}<br>
|
||||
→ <a target="_blank" href="https://github.com/grocy/grocy/issues">https://github.com/grocy/grocy/issues</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@parent
|
||||
@stop
|
20
views/errors/base.blade.php
Normal file
20
views/errors/base.blade.php
Normal file
@ -0,0 +1,20 @@
|
||||
@extends('layout.default')
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div>
|
||||
<h6>{{ $__t('Error source') }}</h6>
|
||||
<pre><code>{!! $exception->getFile() !!}:{!! $exception->getLine() !!}</code></pre>
|
||||
</div>
|
||||
<div>
|
||||
<h6>{{ $__t('Error message') }}</h6>
|
||||
<pre><code>{!! $exception->getMessage() !!}</code></pre>
|
||||
</div>
|
||||
<div>
|
||||
<h6>{{ $__t('Stack trace') }}</h6>
|
||||
<pre><code>{!! $exception->getTraceAsString() !!}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@stop
|
@ -62,6 +62,7 @@
|
||||
@if (GROCY_AUTHENTICATED)
|
||||
Grocy.UserSettings = {!! json_encode($userSettings) !!};
|
||||
Grocy.UserId = {{ GROCY_USER_ID }};
|
||||
Grocy.UserPermissions = {!! json_encode($permissions) !!};
|
||||
@else
|
||||
Grocy.UserSettings = { };
|
||||
Grocy.UserId = -1;
|
||||
@ -163,27 +164,27 @@
|
||||
|
||||
@if(GROCY_FEATURE_FLAG_STOCK)
|
||||
<div class="nav-item-divider"></div>
|
||||
<li class="nav-item nav-item-sidebar" data-toggle="tooltip" data-placement="right" title="{{ $__t('Purchase') }}" data-nav-for-page="purchase">
|
||||
<li class="nav-item nav-item-sidebar permission-PRODUCT_PURCHASE" data-toggle="tooltip" data-placement="right" title="{{ $__t('Purchase') }}" data-nav-for-page="purchase">
|
||||
<a class="nav-link discrete-link" href="{{ $U('/purchase') }}">
|
||||
<i class="fas fa-shopping-cart"></i>
|
||||
<span class="nav-link-text">{{ $__t('Purchase') }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item nav-item-sidebar" data-toggle="tooltip" data-placement="right" title="{{ $__t('Consume') }}" data-nav-for-page="consume">
|
||||
<li class="nav-item nav-item-sidebar permission-PRODUCT_CONSUME" data-toggle="tooltip" data-placement="right" title="{{ $__t('Consume') }}" data-nav-for-page="consume">
|
||||
<a class="nav-link discrete-link" href="{{ $U('/consume') }}">
|
||||
<i class="fas fa-utensils"></i>
|
||||
<span class="nav-link-text">{{ $__t('Consume') }}</span>
|
||||
</a>
|
||||
</li>
|
||||
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
|
||||
<li class="nav-item nav-item-sidebar" data-toggle="tooltip" data-placement="right" title="{{ $__t('Transfer') }}" data-nav-for-page="transfer">
|
||||
<li class="nav-item nav-item-sidebar permission-STOCK_TRANSFER" data-toggle="tooltip" data-placement="right" title="{{ $__t('Transfer') }}" data-nav-for-page="transfer">
|
||||
<a class="nav-link discrete-link" href="{{ $U('/transfer') }}">
|
||||
<i class="fas fa-exchange-alt"></i>
|
||||
<span class="nav-link-text">{{ $__t('Transfer') }}</span>
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
<li class="nav-item nav-item-sidebar" data-toggle="tooltip" data-placement="right" title="{{ $__t('Inventory') }}" data-nav-for-page="inventory">
|
||||
<li class="nav-item nav-item-sidebar permission-STOCK_CORRECTION" data-toggle="tooltip" data-placement="right" title="{{ $__t('Inventory') }}" data-nav-for-page="inventory">
|
||||
<a class="nav-link discrete-link" href="{{ $U('/inventory') }}">
|
||||
<i class="fas fa-list"></i>
|
||||
<span class="nav-link-text">{{ $__t('Inventory') }}</span>
|
||||
@ -191,7 +192,7 @@
|
||||
</li>
|
||||
@endif
|
||||
@if(GROCY_FEATURE_FLAG_CHORES)
|
||||
<li class="nav-item nav-item-sidebar" data-toggle="tooltip" data-placement="right" title="{{ $__t('Chore tracking') }}" data-nav-for-page="choretracking">
|
||||
<li class="nav-item nav-item-sidebar permission-CHORE_TRACK_OTHERS" data-toggle="tooltip" data-placement="right" title="{{ $__t('Chore tracking') }}" data-nav-for-page="choretracking">
|
||||
<a class="nav-link discrete-link" href="{{ $U('/choretracking') }}">
|
||||
<i class="fas fa-play"></i>
|
||||
<span class="nav-link-text">{{ $__t('Chore tracking') }}</span>
|
||||
@ -199,7 +200,7 @@
|
||||
</li>
|
||||
@endif
|
||||
@if(GROCY_FEATURE_FLAG_BATTERIES)
|
||||
<li class="nav-item nav-item-sidebar" data-toggle="tooltip" data-placement="right" title="{{ $__t('Battery tracking') }}" data-nav-for-page="batterytracking">
|
||||
<li class="nav-item nav-item-sidebar permission-BATTERY_TRACK_CHARGE_CYCLE" data-toggle="tooltip" data-placement="right" title="{{ $__t('Battery tracking') }}" data-nav-for-page="batterytracking">
|
||||
<a class="nav-link discrete-link" href="{{ $U('/batterytracking') }}">
|
||||
<i class="fas fa-fire"></i>
|
||||
<span class="nav-link-text">{{ $__t('Battery tracking') }}</span>
|
||||
@ -417,7 +418,7 @@
|
||||
@endif
|
||||
<div class="dropdown-divider"></div>
|
||||
@if(GROCY_SHOW_AUTH_VIEWS)
|
||||
<a class="dropdown-item discrete-link" href="{{ $U('/users') }}"><i class="fas fa-users"></i> {{ $__t('Manage users') }}</a>
|
||||
<a class="dropdown-item discrete-link permission-READ_USER" href="{{ $U('/users') }}"><i class="fas fa-users"></i> {{ $__t('Manage users') }}</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item discrete-link" href="{{ $U('/manageapikeys') }}"><i class="fas fa-handshake"></i> {{ $__t('Manage API keys') }}</a>
|
||||
@endif
|
||||
|
@ -121,14 +121,14 @@
|
||||
@foreach($currentStock as $currentStockEntry)
|
||||
<tr id="product-{{ $currentStockEntry->product_id }}-row" class="@if(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('-1 days')) && $currentStockEntry->amount > 0) table-danger @elseif(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime("+$nextXDays days")) && $currentStockEntry->amount > 0) table-warning @elseif ($currentStockEntry->product_missing) table-info @endif">
|
||||
<td class="fit-content border-right">
|
||||
<a class="btn btn-success btn-sm product-consume-button @if($currentStockEntry->amount < 1 || $currentStockEntry->enable_tare_weight_handling == 1) disabled @endif" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Consume %1$s of %2$s', '1 ' . $currentStockEntry->qu_unit_name, $currentStockEntry->product_name) }}"
|
||||
<a class="permission-PRODUCT_CONSUME btn btn-success btn-sm product-consume-button @if($currentStockEntry->amount < 1 || $currentStockEntry->enable_tare_weight_handling == 1) disabled @endif" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Consume %1$s of %2$s', '1 ' . $currentStockEntry->qu_unit_name, $currentStockEntry->product_name) }}"
|
||||
data-product-id="{{ $currentStockEntry->product_id }}"
|
||||
data-product-name="{{ $currentStockEntry->product_name }}"
|
||||
data-product-qu-name="{{ $currentStockEntry->qu_unit_name }}"
|
||||
data-consume-amount="1">
|
||||
<i class="fas fa-utensils"></i> 1
|
||||
</a>
|
||||
<a id="product-{{ $currentStockEntry->product_id }}-consume-all-button" class="d-none d-sm-inline-block btn btn-danger btn-sm product-consume-button @if($currentStockEntry->amount == 0) disabled @endif" href="#" data-toggle="tooltip" data-placement="right" title="{{ $__t('Consume all %s which are currently in stock', $currentStockEntry->product_name) }}"
|
||||
<a id="product-{{ $currentStockEntry->product_id }}-consume-all-button" class="permission-PRODUCT_CONSUME d-none d-sm-inline-block btn btn-danger btn-sm product-consume-button @if($currentStockEntry->amount == 0) disabled @endif" href="#" data-toggle="tooltip" data-placement="right" title="{{ $__t('Consume all %s which are currently in stock', $currentStockEntry->product_name) }}"
|
||||
data-product-id="{{ $currentStockEntry->product_id }}"
|
||||
data-product-name="{{ $currentStockEntry->product_name }}"
|
||||
data-product-qu-name="{{ $currentStockEntry->qu_unit_name }}"
|
||||
@ -156,22 +156,22 @@
|
||||
data-consume-amount="{{ $currentStockEntry->amount }}">
|
||||
<span class="dropdown-item-icon"><i class="fas fa-utensils"></i></span> <span class="dropdown-item-text">{{ $__t('Consume all %s which are currently in stock', $currentStockEntry->product_name) }}</span>
|
||||
</a>
|
||||
<a class="dropdown-item show-as-dialog-link" type="button" href="{{ $U('/shoppinglistitem/new?embedded&updateexistingproduct&product=' . $currentStockEntry->product_id ) }}">
|
||||
<a class="dropdown-item show-as-dialog-link permission-SHOPPINGLIST_ITEMS_ADD" type="button" href="{{ $U('/shoppinglistitem/new?embedded&updateexistingproduct&product=' . $currentStockEntry->product_id ) }}">
|
||||
<span class="dropdown-item-icon"><i class="fas fa-shopping-cart"></i></span> <span class="dropdown-item-text">{{ $__t('Add to shopping list') }}</span>
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item show-as-dialog-link" type="button" href="{{ $U('/purchase?embedded&product=' . $currentStockEntry->product_id ) }}">
|
||||
<a class="dropdown-item show-as-dialog-link permission-PRODUCT_PURCHASE" type="button" href="{{ $U('/purchase?embedded&product=' . $currentStockEntry->product_id ) }}">
|
||||
<span class="dropdown-item-icon"><i class="fas fa-shopping-cart"></i></span> <span class="dropdown-item-text">{{ $__t('Purchase') }}</span>
|
||||
</a>
|
||||
<a class="dropdown-item show-as-dialog-link" type="button" href="{{ $U('/consume?embedded&product=' . $currentStockEntry->product_id ) }}">
|
||||
<a class="dropdown-item show-as-dialog-link permission-PRODUCT_CONSUME" type="button" href="{{ $U('/consume?embedded&product=' . $currentStockEntry->product_id ) }}">
|
||||
<span class="dropdown-item-icon"><i class="fas fa-utensils"></i></span> <span class="dropdown-item-text">{{ $__t('Consume') }}</span>
|
||||
</a>
|
||||
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
|
||||
<a class="dropdown-item show-as-dialog-link @if($currentStockEntry->amount < 1) disabled @endif" type="button" href="{{ $U('/transfer?embedded&product=' . $currentStockEntry->product_id) }}">
|
||||
<a class="dropdown-item show-as-dialog-link permission-STOCK_TRANSFER @if($currentStockEntry->amount < 1) disabled @endif" type="button" href="{{ $U('/transfer?embedded&product=' . $currentStockEntry->product_id) }}">
|
||||
<span class="dropdown-item-icon"><i class="fas fa-exchange-alt"></i></span> <span class="dropdown-item-text">{{ $__t('Transfer') }}</span>
|
||||
</a>
|
||||
@endif
|
||||
<a class="dropdown-item show-as-dialog-link" type="button" href="{{ $U('/inventory?embedded&product=' . $currentStockEntry->product_id ) }}">
|
||||
<a class="dropdown-item show-as-dialog-link permission-STOCK_CORRECTION" type="button" href="{{ $U('/inventory?embedded&product=' . $currentStockEntry->product_id ) }}">
|
||||
<span class="dropdown-item-icon"><i class="fas fa-list"></i></span> <span class="dropdown-item-text">{{ $__t('Inventory') }}</span>
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
@ -185,11 +185,11 @@
|
||||
<a class="dropdown-item" type="button" href="{{ $U('/stockjournal?product=') }}{{ $currentStockEntry->product_id }}">
|
||||
<span class="dropdown-item-icon"><i class="fas fa-file-alt"></i></span> <span class="dropdown-item-text">{{ $__t('Stock journal for this product') }}</span>
|
||||
</a>
|
||||
<a class="dropdown-item" type="button" href="{{ $U('/product/') }}{{ $currentStockEntry->product_id . '?returnto=%2Fstockoverview' }}">
|
||||
<a class="dropdown-item permission-MASTER_DATA_EDIT" type="button" href="{{ $U('/product/') }}{{ $currentStockEntry->product_id . '?returnto=%2Fstockoverview' }}">
|
||||
<span class="dropdown-item-icon"><i class="fas fa-edit"></i></span> <span class="dropdown-item-text">{{ $__t('Edit product') }}</span>
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item product-consume-button product-consume-button-spoiled @if($currentStockEntry->amount < 1) disabled @endif" type="button" href="#"
|
||||
<a class="dropdown-item product-consume-button product-consume-button-spoiled permission-PRODUCT_CONSUME @if($currentStockEntry->amount < 1) disabled @endif" type="button" href="#"
|
||||
data-product-id="{{ $currentStockEntry->product_id }}"
|
||||
data-product-name="{{ $currentStockEntry->product_name }}"
|
||||
data-product-qu-name="{{ $currentStockEntry->qu_unit_name }}"
|
||||
|
34
views/userpermissions.blade.php
Normal file
34
views/userpermissions.blade.php
Normal file
@ -0,0 +1,34 @@
|
||||
@extends('layout.default')
|
||||
|
||||
@section('title', $__t('Permissions for user %s', GetUserDisplayName($user)))
|
||||
@section('activeNav', '')
|
||||
@section('viewJsName', 'userpermissions')
|
||||
|
||||
@push('pageScripts')
|
||||
<script>
|
||||
Grocy.EditObjectId = {{ $user->id }};
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h2 class="title">@yield('title')</h2>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row mt-3">
|
||||
<div class="col">
|
||||
<ul>
|
||||
@foreach($permissions as $perm)
|
||||
<li>
|
||||
@include('components.userpermission_select', array(
|
||||
'permission' => $perm
|
||||
))
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
<button id="permission-save" class="btn btn-success" type="submit">{{ $__t('Save') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
@ -47,6 +47,9 @@
|
||||
<a class="btn btn-info btn-sm" href="{{ $U('/user/') }}{{ $user->id }}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<a class="btn btn-info btn-sm" href="{{ $U('/user/' . $user->id . '/permissions') }}">
|
||||
<i class="fas fa-lock"></i>
|
||||
</a>
|
||||
<a class="btn btn-danger btn-sm user-delete-button @if($user->id == GROCY_USER_ID) disabled @endif" href="#" data-user-id="{{ $user->id }}" data-user-username="{{ $user->username }}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
|
Loading…
x
Reference in New Issue
Block a user