User roles and permission management via Admin UI (#1231)
This commit is contained in:
@@ -27,7 +27,10 @@ abstract class AbstractController extends BaseAbstractController implements Serv
|
||||
public const DOMAIN_FLASH = 'flashmessages';
|
||||
public const DOMAIN_ERROR = 'exceptions';
|
||||
|
||||
public const ROLE_ADMIN = 'ROLE_ADMIN';
|
||||
/**
|
||||
* @deprecated since 1.6, will be removed with 2.0
|
||||
*/
|
||||
public const ROLE_ADMIN = User::ROLE_ADMIN;
|
||||
|
||||
/**
|
||||
* @return DataCollectorTranslator
|
||||
|
||||
@@ -21,7 +21,7 @@ use Symfony\Component\Routing\Annotation\Route;
|
||||
* Controller used to display calendars.
|
||||
*
|
||||
* @Route(path="/calendar")
|
||||
* @Security("is_granted('ROLE_USER')")
|
||||
* @Security("is_granted('IS_AUTHENTICATED_REMEMBERED')")
|
||||
*/
|
||||
class CalendarController extends AbstractController
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ use Symfony\Component\Routing\Annotation\Route;
|
||||
* Dashboard controller for the admin area.
|
||||
*
|
||||
* @Route(path="/dashboard")
|
||||
* @Security("is_granted('ROLE_USER')")
|
||||
* @Security("is_granted('IS_AUTHENTICATED_REMEMBERED')")
|
||||
*/
|
||||
class DashboardController extends AbstractController
|
||||
{
|
||||
@@ -121,9 +121,16 @@ class DashboardController extends AbstractController
|
||||
$this->eventDispatcher->dispatch($event);
|
||||
|
||||
$sections = $event->getSections();
|
||||
$clearedSections = [];
|
||||
/** @var WidgetContainerInterface $section */
|
||||
foreach ($sections as $key => $section) {
|
||||
if (!empty($section->getWidgets())) {
|
||||
$clearedSections[] = $section;
|
||||
}
|
||||
}
|
||||
|
||||
uasort(
|
||||
$sections,
|
||||
$clearedSections,
|
||||
function (WidgetContainerInterface $a, WidgetContainerInterface $b) {
|
||||
if ($a->getOrder() == $b->getOrder()) {
|
||||
return 0;
|
||||
@@ -134,7 +141,7 @@ class DashboardController extends AbstractController
|
||||
);
|
||||
|
||||
return $this->render('dashboard/index.html.twig', [
|
||||
'widgets' => $sections
|
||||
'widgets' => $clearedSections
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ use Symfony\Component\Routing\Annotation\Route;
|
||||
* Homepage controller is a redirect controller with user specific logic.
|
||||
*
|
||||
* @Route(path="/homepage")
|
||||
* @Security("is_granted('ROLE_USER')")
|
||||
* @Security("is_granted('IS_AUTHENTICATED_REMEMBERED')")
|
||||
*/
|
||||
class HomepageController extends AbstractController
|
||||
{
|
||||
|
||||
34
src/Controller/LayoutController.php
Normal file
34
src/Controller/LayoutController.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Configuration\TimesheetConfiguration;
|
||||
use App\Repository\TimesheetRepository;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Used for the (initial) page rendering.
|
||||
*/
|
||||
class LayoutController extends AbstractController
|
||||
{
|
||||
public function activeEntries(TimesheetRepository $repository, TimesheetConfiguration $configuration): Response
|
||||
{
|
||||
$user = $this->getUser();
|
||||
$activeEntries = $repository->getActiveEntries($user);
|
||||
|
||||
return $this->render(
|
||||
'navbar/active-entries.html.twig',
|
||||
[
|
||||
'entries' => $activeEntries,
|
||||
'soft_limit' => $configuration->getActiveEntriesSoftLimit(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
161
src/Controller/PermissionController.php
Normal file
161
src/Controller/PermissionController.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Role;
|
||||
use App\Entity\RolePermission;
|
||||
use App\Form\RoleType;
|
||||
use App\Repository\RolePermissionRepository;
|
||||
use App\Repository\RoleRepository;
|
||||
use App\Security\RolePermissionManager;
|
||||
use App\Security\RoleService;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* Controller used to manage user roles and role permissions.
|
||||
*
|
||||
* @Route(path="/admin/permissions")
|
||||
* @Security("is_granted('role_permissions')")
|
||||
*/
|
||||
final class PermissionController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @var RoleService
|
||||
*/
|
||||
private $roleService;
|
||||
/**
|
||||
* @var RolePermissionManager
|
||||
*/
|
||||
private $manager;
|
||||
/**
|
||||
* @var RoleRepository
|
||||
*/
|
||||
private $roleRepository;
|
||||
|
||||
public function __construct(RoleService $roleService, RolePermissionManager $manager, RoleRepository $roleRepository)
|
||||
{
|
||||
$this->roleService = $roleService;
|
||||
$this->manager = $manager;
|
||||
$this->roleRepository = $roleRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="", name="admin_user_permissions", methods={"GET", "POST"})
|
||||
* @Security("is_granted('role_permissions')")
|
||||
*/
|
||||
public function permissions()
|
||||
{
|
||||
$all = $this->roleRepository->findAll();
|
||||
$existing = [];
|
||||
|
||||
foreach ($all as $role) {
|
||||
$existing[] = $role->getName();
|
||||
}
|
||||
|
||||
$existing = array_map('strtoupper', $existing);
|
||||
|
||||
// automatically import all hard coded (default) roles into the database table
|
||||
foreach ($this->roleService->getAvailableNames() as $roleName) {
|
||||
$roleName = strtoupper($roleName);
|
||||
if (!in_array($roleName, $existing)) {
|
||||
$role = new Role();
|
||||
$role->setName($roleName);
|
||||
$this->roleRepository->saveRole($role);
|
||||
$existing[] = $roleName;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('user/permissions.html.twig', [
|
||||
'roles' => $this->roleRepository->findAll(),
|
||||
'permissions' => $this->manager->getPermissions(),
|
||||
'manager' => $this->manager,
|
||||
'system_roles' => $this->roleService->getSystemRoles(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/roles/create", name="admin_user_roles", methods={"GET", "POST"})
|
||||
* @Security("is_granted('role_permissions')")
|
||||
*/
|
||||
public function createRole(Request $request): Response
|
||||
{
|
||||
$role = new Role();
|
||||
|
||||
$form = $this->createForm(RoleType::class, $role, [
|
||||
'action' => $this->generateUrl('admin_user_roles', []),
|
||||
'method' => 'POST',
|
||||
]);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
try {
|
||||
$this->roleRepository->saveRole($role);
|
||||
$this->flashSuccess('action.update.success');
|
||||
} catch (\Exception $ex) {
|
||||
$this->flashSuccess('action.update.error');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_user_permissions');
|
||||
}
|
||||
|
||||
return $this->render('user/edit_role.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'role' => $role,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/roles/{id}/delete", name="admin_user_role_delete", methods={"GET", "POST"})
|
||||
* @Security("is_granted('role_permissions')")
|
||||
*/
|
||||
public function deleteRole(Role $role): Response
|
||||
{
|
||||
try {
|
||||
$this->roleRepository->deleteRole($role);
|
||||
$this->flashSuccess('action.delete.success');
|
||||
} catch (\Exception $ex) {
|
||||
$this->flashError('action.delete.error');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_user_permissions');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/roles/{id}/{name}/{value}", name="admin_user_permission_save", methods={"GET"})
|
||||
* @Security("is_granted('role_permissions')")
|
||||
*/
|
||||
public function savePermission(Role $role, string $name, string $value, RolePermissionRepository $rolePermissionRepository): Response
|
||||
{
|
||||
if (!$this->manager->isRegisteredPermission($name)) {
|
||||
throw $this->createNotFoundException('Unknown permission: ' . $name);
|
||||
}
|
||||
|
||||
try {
|
||||
$permission = $rolePermissionRepository->findRolePermission($role, $name);
|
||||
if (null === $permission) {
|
||||
$permission = new RolePermission();
|
||||
$permission->setRole($role);
|
||||
$permission->setPermission($name);
|
||||
}
|
||||
$permission->setAllowed((bool) $value);
|
||||
|
||||
$rolePermissionRepository->saveRolePermission($permission);
|
||||
$this->flashSuccess('action.update.success');
|
||||
} catch (\Exception $ex) {
|
||||
$this->flashError('action.update.error');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_user_permissions');
|
||||
}
|
||||
}
|
||||
@@ -78,23 +78,4 @@ class TimesheetController extends TimesheetAbstractController
|
||||
{
|
||||
return $this->create($request, 'timesheet/edit.html.twig', $projectRepository, $activityRepository, $tagRepository);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for the initial page rendering.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function activeEntriesAction()
|
||||
{
|
||||
$user = $this->getUser();
|
||||
$activeEntries = $this->getRepository()->getActiveEntries($user);
|
||||
|
||||
return $this->render(
|
||||
'navbar/active-entries.html.twig',
|
||||
[
|
||||
'entries' => $activeEntries,
|
||||
'soft_limit' => $this->getSoftLimit(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,12 @@ use App\Form\UserCreateType;
|
||||
use App\Repository\Query\UserQuery;
|
||||
use App\Repository\TimesheetRepository;
|
||||
use App\Repository\UserRepository;
|
||||
use App\Security\RolePermissionManager;
|
||||
use Pagerfanta\Pagerfanta;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
|
||||
|
||||
@@ -64,12 +65,8 @@ class UserController extends AbstractController
|
||||
* @Route(path="/", defaults={"page": 1}, name="admin_user", methods={"GET"})
|
||||
* @Route(path="/page/{page}", requirements={"page": "[1-9]\d*"}, name="admin_user_paginated", methods={"GET"})
|
||||
* @Security("is_granted('view_user')")
|
||||
*
|
||||
* @param int $page
|
||||
* @param Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function indexAction($page, Request $request)
|
||||
public function indexAction($page, Request $request): Response
|
||||
{
|
||||
$query = new UserQuery();
|
||||
$query->setPage($page);
|
||||
@@ -99,11 +96,8 @@ class UserController extends AbstractController
|
||||
/**
|
||||
* @Route(path="/create", name="admin_user_create", methods={"GET", "POST"})
|
||||
* @Security("is_granted('create_user')")
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function createAction(Request $request)
|
||||
public function createAction(Request $request): Response
|
||||
{
|
||||
$user = new User();
|
||||
$user->setEnabled(true);
|
||||
@@ -144,14 +138,8 @@ class UserController extends AbstractController
|
||||
/**
|
||||
* @Route(path="/{id}/delete", name="admin_user_delete", methods={"GET", "POST"})
|
||||
* @Security("is_granted('delete', userToDelete)")
|
||||
*
|
||||
* @param User $userToDelete
|
||||
* @param Request $request
|
||||
* @param TimesheetRepository $repository
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
||||
* @throws \Doctrine\ORM\NonUniqueResultException
|
||||
*/
|
||||
public function deleteAction(User $userToDelete, Request $request, TimesheetRepository $repository)
|
||||
public function deleteAction(User $userToDelete, Request $request, TimesheetRepository $repository): Response
|
||||
{
|
||||
// $userToDelete MUST not be called $user, as $user is always the current user!
|
||||
$stats = $repository->getUserStatistics($userToDelete);
|
||||
@@ -189,27 +177,7 @@ class UserController extends AbstractController
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/permissions", name="admin_user_permissions", methods={"GET", "POST"})
|
||||
* @Security("is_granted('role_permissions')")
|
||||
*
|
||||
* @param RolePermissionManager $manager
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function permissions(RolePermissionManager $manager)
|
||||
{
|
||||
return $this->render('user/permissions.html.twig', [
|
||||
'roles' => $manager->getRoles(),
|
||||
'permissions' => $manager->getPermissions(),
|
||||
'manager' => $manager,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserQuery $query
|
||||
* @return \Symfony\Component\Form\FormInterface
|
||||
*/
|
||||
protected function getToolbarForm(UserQuery $query)
|
||||
protected function getToolbarForm(UserQuery $query): FormInterface
|
||||
{
|
||||
return $this->createForm(UserToolbarForm::class, $query, [
|
||||
'action' => $this->generateUrl('admin_user', [
|
||||
@@ -219,11 +187,7 @@ class UserController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @return \Symfony\Component\Form\FormInterface
|
||||
*/
|
||||
private function createEditForm(User $user)
|
||||
private function createEditForm(User $user): FormInterface
|
||||
{
|
||||
return $this->createForm(UserCreateType::class, $user, [
|
||||
'action' => $this->generateUrl('admin_user_create'),
|
||||
|
||||
Reference in New Issue
Block a user