Add UI & API for Permissions, protect "User"-(Api)-Controller with new permissions.

This commit is contained in:
fipwmaqzufheoxq92ebc 2020-08-25 15:46:08 +02:00
parent f6c76b6e20
commit 359baa794a
9 changed files with 212 additions and 9 deletions

View File

@ -5,6 +5,10 @@ namespace Grocy\Controllers\Users;
abstract class User abstract class User
{ {
const PERMISSION_ADMIN = 'ADMIN'; 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';
public abstract function hasPermission(string $permission): bool; public abstract function hasPermission(string $permission): bool;

View File

@ -2,6 +2,8 @@
namespace Grocy\Controllers; namespace Grocy\Controllers;
use Grocy\Controllers\Users\User;
class UsersApiController extends BaseApiController class UsersApiController extends BaseApiController
{ {
public function __construct(\DI\Container $container) 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) 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()); 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) 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(); $requestBody = $request->getParsedBody();
try try
@ -43,7 +47,8 @@ class UsersApiController extends BaseApiController
public function DeleteUser(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) 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']); $this->getUsersService()->DeleteUser($args['userId']);
return $this->EmptyApiResponse($response); 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) 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 try
{ {
@ -108,4 +118,66 @@ class UsersApiController extends BaseApiController
return $this->GenericErrorResponse($response, $ex->getMessage()); 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());
}
}
} }

View File

@ -2,11 +2,14 @@
namespace Grocy\Controllers; namespace Grocy\Controllers;
use Grocy\Controllers\Users\User;
class UsersController extends BaseController class UsersController extends BaseController
{ {
public function UsersList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) 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') 'users' => $this->getDatabase()->users()->orderBy('username')
]); ]);
} }
@ -15,16 +18,30 @@ class UsersController extends BaseController
{ {
if ($args['userId'] == 'new') if ($args['userId'] == 'new')
{ {
return $this->renderPage($response, 'userform', [ User::checkPermission($request, User::PERMISSION_CREATE_USER);
return $this->renderPage($response, 'userform', [
'mode' => 'create' 'mode' => 'create'
]); ]);
} }
else 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']), 'user' => $this->getDatabase()->users($args['userId']),
'mode' => 'edit' '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, 'permissions', [
'user' => $this->getDatabase()->users($args['userId']),
'permissions' => $this->getDatabase()->uihelper_permission()
->where('parent IS NULL')->where('user_id', $args['userId']),
]);
}
} }

View File

@ -58,4 +58,15 @@ SELECT ph.id AS id,
) AS has_permission, ) AS has_permission,
ph.parent AS parent ph.parent AS parent
FROM users u, FROM users u,
permission_hierarchy ph; 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'));

View File

@ -0,0 +1,41 @@
$('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/' + edited_user_id + '/permissions', {
'permissions': permission_list,
}, function (result) {
toastr.success(__t("Permissions saved!"));
}, function (xhr) {
toastr.error(__t(JSON.parse(xhr.response).error_message));
}
);
}
);
if (edited_user_id == 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);
}
}
})
}

View File

@ -34,7 +34,10 @@ $app->group('', function(RouteCollectorProxy $group)
$group->get('/users', '\Grocy\Controllers\UsersController:UsersList'); $group->get('/users', '\Grocy\Controllers\UsersController:UsersList');
$group->get('/user/{userId}', '\Grocy\Controllers\UsersController:UserEditForm'); $group->get('/user/{userId}', '\Grocy\Controllers\UsersController:UserEditForm');
// Stock routes $group->get('/user/permissions/{userId}', '\Grocy\Controllers\UsersController:PermissionList');
// Stock routes
if (GROCY_FEATURE_FLAG_STOCK) if (GROCY_FEATURE_FLAG_STOCK)
{ {
$group->get('/stockoverview', '\Grocy\Controllers\StockController:Overview'); $group->get('/stockoverview', '\Grocy\Controllers\StockController:Overview');
@ -169,7 +172,11 @@ $app->group('/api', function(RouteCollectorProxy $group)
$group->put('/users/{userId}', '\Grocy\Controllers\UsersApiController:EditUser'); $group->put('/users/{userId}', '\Grocy\Controllers\UsersApiController:EditUser');
$group->delete('/users/{userId}', '\Grocy\Controllers\UsersApiController:DeleteUser'); $group->delete('/users/{userId}', '\Grocy\Controllers\UsersApiController:DeleteUser');
// User $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
$group->get('/user/settings', '\Grocy\Controllers\UsersApiController:GetUserSettings'); $group->get('/user/settings', '\Grocy\Controllers\UsersApiController:GetUserSettings');
$group->get('/user/settings/{settingKey}', '\Grocy\Controllers\UsersApiController:GetUserSetting'); $group->get('/user/settings/{settingKey}', '\Grocy\Controllers\UsersApiController:GetUserSetting');
$group->put('/user/settings/{settingKey}', '\Grocy\Controllers\UsersApiController:SetUserSetting'); $group->put('/user/settings/{settingKey}', '\Grocy\Controllers\UsersApiController:SetUserSetting');

View 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>
{{ $__t($perm->permission_name) }}
</label>
<div id="permission-sub-{{ $perm->permission_name }}">
<ul>
@foreach($perm->uihelper_permissionList(array('user_id' => $user->id))->via('parent') as $p)
<li>
@include('components.permission_select', array(
'perm' => $p
))
</li>
@endforeach
</ul>
</div>

View File

@ -0,0 +1,33 @@
@extends('layout.default')
@section('title', $__t('Permissions of %s', GetUserDisplayName($user)))
@section('activeNav', '')
@section('viewJsName', 'permissions')
@push('pageScripts')
<script>
var edited_user_id = {{ $user->id }};
</script>
@endpush
@section('content')
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
</div>
</div>
<hr>
<div class="row">
<div>
<ul>
@foreach($permissions as $perm)
<li>
@include('components.permission_select', array(
'permission' => $perm
))
</li>
@endforeach
</ul>
<button id="permission-save" class="btn btn-success" type="submit">{{ $__t('Save') }}</button>
</div>
</div>
@endsection

View File

@ -47,6 +47,9 @@
<a class="btn btn-info btn-sm" href="{{ $U('/user/') }}{{ $user->id }}"> <a class="btn btn-info btn-sm" href="{{ $U('/user/') }}{{ $user->id }}">
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</a> </a>
<a class="btn btn-info btn-sm" href="{{ $U('/user/permissions/') }}{{ $user->id }}">
<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 }}"> <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> <i class="fas fa-trash"></i>
</a> </a>