diff --git a/composer.lock b/composer.lock index 3744f3d6..77ee5af5 100644 --- a/composer.lock +++ b/composer.lock @@ -52,12 +52,12 @@ "source": { "type": "git", "url": "https://github.com/kevinpapst/AdminThemeBundle.git", - "reference": "1aa9b1637b2f827fe79ca1edff26092840e30716" + "reference": "d31084eff7888d76f7976700f9ac878f40c3bacd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kevinpapst/AdminThemeBundle/zipball/1aa9b1637b2f827fe79ca1edff26092840e30716", - "reference": "1aa9b1637b2f827fe79ca1edff26092840e30716", + "url": "https://api.github.com/repos/kevinpapst/AdminThemeBundle/zipball/d31084eff7888d76f7976700f9ac878f40c3bacd", + "reference": "d31084eff7888d76f7976700f9ac878f40c3bacd", "shasum": "" }, "require": { @@ -111,7 +111,7 @@ "support": { "source": "https://github.com/kevinpapst/AdminThemeBundle/tree/kevinpapst" }, - "time": "2018-01-15T19:14:46+00:00" + "time": "2018-01-17T12:31:37+00:00" }, { "name": "beberlei/DoctrineExtensions", diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 04680e58..48f0a023 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -24,6 +24,11 @@ security: # denying access is done in access_control or in your controllers anonymous: ~ + remember_me: + secret: '%kernel.secret%' + lifetime: 604800 + path: / + # This allows the user to login by submitting a username and password # Reference: http://symfony.com/doc/current/cookbook/security/form_login_setup.html form_login: diff --git a/config/services.yaml b/config/services.yaml index 59548e78..bafd62a7 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -1,7 +1,7 @@ # Put parameters here that don't need to change on each machine where the app is deployed # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration parameters: - locale: 'en' + locale: en app_locales: en|de services: @@ -33,19 +33,12 @@ services: # ================================================================================ # a route listener, that injects the locale through a URL directory - app.redirect_to_preferred_locale_listener: - class: App\EventListener\RedirectToPreferredLocaleListener + App\EventSubscriber\RedirectToLocaleSubscriber: + class: App\EventSubscriber\RedirectToLocaleSubscriber arguments: ['@router', '%app_locales%', '%locale%'] tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest } - # additional menu entries for the timesheet bundle - timesheet.configure_menu_listener: - class: App\EventListener\Menu - tags: - - { name: kernel.event_listener, event: app.main_menu_configure, method: onMainMenuConfigure } - - { name: kernel.event_listener, event: app.admin_menu_configure, method: onAdminMenuConfigure } - # ================================================================================ # DATABASE # ================================================================================ @@ -80,17 +73,6 @@ services: # // same code using repository services # $posts = $this->get('app.user_repository')->findAll(); - # ================================================================================ - # EVENTS and LISTENER / SUBSCRIBER - # ================================================================================ - - # event-listener to populate the navigation - app.menu_builder: - class: App\EventListener\MenuBuilder - arguments: ["@event_dispatcher", "@security.authorization_checker"] - tags: - - { name: kernel.event_listener, event: theme.sidebar_setup_menu, method: onSetupNavbar } - # ================================================================================ # FORMS # ================================================================================ @@ -132,13 +114,6 @@ services: App\Twig\Extensions: arguments: ['%app_locales%'] - avanzu_admin_theme.navbar_user_listener: - class: App\EventListener\NavbarShowUserListener - arguments: ["@security.token_storage"] - tags: - - { name: kernel.event_listener, event: theme.navbar_user, method: onShowUser } - - { name: kernel.event_listener, event: theme.sidebar_user, method: onShowUser } - # avanzu_admin_theme.navbar_task_listener: # class: "%avanzu_admin_theme.navbar_task_listener.class%" # tags: diff --git a/public/css/kimai.css b/public/css/kimai.css index 3a929c72..a4e26400 100644 --- a/public/css/kimai.css +++ b/public/css/kimai.css @@ -79,6 +79,11 @@ footer.main-footer { font-size: 80%; } +/* ================================ LOGIN-PAGE ================================ */ + +.login-page form label { + padding-left: 5px; +} /* ================================ DATA-TABLES ================================ */ diff --git a/src/Controller/Admin/ActivityController.php b/src/Controller/Admin/ActivityController.php index 36996095..3b436315 100644 --- a/src/Controller/Admin/ActivityController.php +++ b/src/Controller/Admin/ActivityController.php @@ -30,6 +30,7 @@ use App\Repository\Query\ActivityQuery; * * @Route("/admin/activity") * @Security("has_role('ROLE_ADMIN')") + * @Security("is_granted('IS_AUTHENTICATED_FULLY')") * * @author Kevin Papst */ diff --git a/src/Controller/Admin/CustomerController.php b/src/Controller/Admin/CustomerController.php index e8715f3d..7cb78f5f 100644 --- a/src/Controller/Admin/CustomerController.php +++ b/src/Controller/Admin/CustomerController.php @@ -28,6 +28,7 @@ use App\Repository\Query\CustomerQuery; * * @Route("/admin/customer") * @Security("has_role('ROLE_ADMIN')") + * @Security("is_granted('IS_AUTHENTICATED_FULLY')") * * @author Kevin Papst */ diff --git a/src/Controller/Admin/ProjectController.php b/src/Controller/Admin/ProjectController.php index 1d718b65..0830246b 100644 --- a/src/Controller/Admin/ProjectController.php +++ b/src/Controller/Admin/ProjectController.php @@ -29,6 +29,7 @@ use App\Repository\Query\ProjectQuery; * * @Route("/admin/project") * @Security("has_role('ROLE_ADMIN')") + * @Security("is_granted('IS_AUTHENTICATED_FULLY')") * * @author Kevin Papst */ diff --git a/src/Controller/Admin/TimesheetController.php b/src/Controller/Admin/TimesheetController.php index c519999c..38ce0f0f 100644 --- a/src/Controller/Admin/TimesheetController.php +++ b/src/Controller/Admin/TimesheetController.php @@ -20,7 +20,6 @@ use App\Entity\Timesheet; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache; use App\Form\TimesheetAdminForm; /** @@ -28,6 +27,7 @@ use App\Form\TimesheetAdminForm; * * @Route("/team/timesheet") * @Security("has_role('ROLE_TEAMLEAD')") + * @Security("is_granted('IS_AUTHENTICATED_FULLY')") * * @author Kevin Papst */ @@ -41,7 +41,6 @@ class TimesheetController extends AbstractController * @Route("/", defaults={"page": 1}, name="admin_timesheet") * @Route("/page/{page}", requirements={"page": "[1-9]\d*"}, name="admin_timesheet_paginated") * @Method("GET") - * @Cache(smaxage="10") * * @param $page * @param Request $request diff --git a/src/Controller/Admin/UserController.php b/src/Controller/Admin/UserController.php index 2256bd27..52e1bb1a 100644 --- a/src/Controller/Admin/UserController.php +++ b/src/Controller/Admin/UserController.php @@ -28,6 +28,7 @@ use Symfony\Component\HttpFoundation\Request; * * @Route("/admin/user") * @Security("has_role('ROLE_SUPER_ADMIN')") + * @Security("is_granted('IS_AUTHENTICATED_FULLY')") * * @author Kevin Papst */ @@ -61,7 +62,6 @@ class UserController extends AbstractController * @Route("/", defaults={"page": 1}, name="admin_user") * @Route("/page/{page}", requirements={"page": "[1-9]\d*"}, name="admin_user_paginated") * @Method("GET") - * @Security("is_granted('view_all', user)") */ public function indexAction($page, Request $request) { diff --git a/src/Event/ConfigureMenuEvent.php b/src/Event/ConfigureMenuEvent.php index 1e47ff6c..b36ca528 100644 --- a/src/Event/ConfigureMenuEvent.php +++ b/src/Event/ConfigureMenuEvent.php @@ -31,23 +31,16 @@ abstract class ConfigureMenuEvent extends Event * @var SidebarMenuEvent */ private $event; - /** - * @var AuthorizationChecker - */ - private $auth; /** * ConfigureMenuEvent constructor. - * @param AuthorizationChecker $auth * @param Request $request * @param SidebarMenuEvent $event */ public function __construct( - AuthorizationChecker $auth, Request $request, SidebarMenuEvent $event ) { - $this->auth = $auth; $this->request = $request; $this->event = $event; } @@ -67,12 +60,4 @@ abstract class ConfigureMenuEvent extends Event { return $this->event; } - - /** - * @return AuthorizationChecker - */ - public function getAuth() - { - return $this->auth; - } } diff --git a/src/EventListener/MenuBuilder.php b/src/EventSubscriber/MenuBuilderSubscriber.php similarity index 82% rename from src/EventListener/MenuBuilder.php rename to src/EventSubscriber/MenuBuilderSubscriber.php index 17b7abb5..c63b2174 100644 --- a/src/EventListener/MenuBuilder.php +++ b/src/EventSubscriber/MenuBuilderSubscriber.php @@ -9,42 +9,53 @@ * file that was distributed with this source code. */ -namespace App\EventListener; +namespace App\EventSubscriber; use App\Event\ConfigureMainMenuEvent; use App\Event\ConfigureAdminMenuEvent; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; use Avanzu\AdminThemeBundle\Model\MenuItemModel; use Avanzu\AdminThemeBundle\Event\SidebarMenuEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; /** * Class MenuBuilder configures the main navigation. * * @author Kevin Papst */ -class MenuBuilder +class MenuBuilderSubscriber implements EventSubscriberInterface { /** * @var EventDispatcherInterface */ private $eventDispatcher; /** - * @var AuthorizationChecker + * @var AuthorizationCheckerInterface */ private $security; /** - * MenuBuilder constructor. + * MenuBuilderSubscriber constructor. * @param EventDispatcherInterface $dispatcher - * @param AuthorizationChecker $security + * @param AuthorizationCheckerInterface $security */ - public function __construct(EventDispatcherInterface $dispatcher, AuthorizationChecker $security) + public function __construct(EventDispatcherInterface $dispatcher, AuthorizationCheckerInterface $security) { $this->eventDispatcher = $dispatcher; $this->security = $security; } + /** + * @return array + */ + public static function getSubscribedEvents(): array + { + return [ + 'theme.sidebar_setup_menu' => ['onSetupNavbar', 100], + ]; + } + /** * Generate the main menu. * @@ -53,9 +64,8 @@ class MenuBuilder public function onSetupNavbar(SidebarMenuEvent $event) { $request = $event->getRequest(); - $isLoggedIn = $this->security->isGranted('IS_AUTHENTICATED_FULLY'); + $isLoggedIn = $this->security->isGranted('IS_AUTHENTICATED_REMEMBERED'); $isTeamlead = $isLoggedIn && $this->security->isGranted('ROLE_TEAMLEAD'); - $isSuperAdmin = $isLoggedIn && $this->security->isGranted('ROLE_SUPER_ADMIN'); $event->addItem( new MenuItemModel('dashboard', 'menu.homepage', 'dashboard', [], 'fa fa-dashboard') @@ -64,7 +74,6 @@ class MenuBuilder $this->eventDispatcher->dispatch( ConfigureMainMenuEvent::CONFIGURE, new ConfigureMainMenuEvent( - $this->security, $request, $event ) @@ -74,16 +83,9 @@ class MenuBuilder $admin = new MenuItemModel('admin', 'menu.admin', '', [], 'fa fa-wrench'); $event->addItem($admin); - if ($isSuperAdmin) { - $admin->addChild( - new MenuItemModel('user_admin', 'menu.admin_user', 'admin_user', [], 'fa fa-user') - ); - } - $this->eventDispatcher->dispatch( ConfigureAdminMenuEvent::CONFIGURE, new ConfigureAdminMenuEvent( - $this->security, $request, $event ) diff --git a/src/EventListener/Menu.php b/src/EventSubscriber/MenuSubscriber.php similarity index 56% rename from src/EventListener/Menu.php rename to src/EventSubscriber/MenuSubscriber.php index b63c9789..d402d06a 100644 --- a/src/EventListener/Menu.php +++ b/src/EventSubscriber/MenuSubscriber.php @@ -9,29 +9,55 @@ * file that was distributed with this source code. */ -namespace App\EventListener; +namespace App\EventSubscriber; use App\Event\ConfigureMainMenuEvent; use App\Event\ConfigureAdminMenuEvent; -use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Avanzu\AdminThemeBundle\Model\MenuItemModel; -use Avanzu\AdminThemeBundle\Event\SidebarMenuEvent; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; /** - * Menus for timesheet + * Menu event subscriber for timesheet, customer, projects, activities. + * This is a sample implementation for developer who want to add new navigation entries in their bundles. * * @author Kevin Papst */ -class Menu +class MenuSubscriber implements EventSubscriberInterface { + /** + * @var AuthorizationCheckerInterface + */ + private $security; + + /** + * MenuSubscriber constructor. + * @param AuthorizationCheckerInterface $security + */ + public function __construct(AuthorizationCheckerInterface $security) + { + $this->security = $security; + } + + /** + * @return array + */ + public static function getSubscribedEvents(): array + { + return [ + ConfigureMainMenuEvent::CONFIGURE => ['onMainMenuConfigure', 100], + ConfigureAdminMenuEvent::CONFIGURE => ['onAdminMenuConfigure', 100], + ]; + } + /** * @param \App\Event\ConfigureMainMenuEvent $event */ public function onMainMenuConfigure(ConfigureMainMenuEvent $event) { - $auth = $event->getAuth(); + $auth = $this->security; - $isLoggedIn = $auth->isGranted('IS_AUTHENTICATED_FULLY'); + $isLoggedIn = $auth->isGranted('IS_AUTHENTICATED_REMEMBERED'); $isUser = $isLoggedIn && $auth->isGranted('ROLE_USER'); if (!$isLoggedIn || !$isUser) { @@ -50,13 +76,9 @@ class Menu public function onAdminMenuConfigure(ConfigureAdminMenuEvent $event) { $menu = $event->getAdminMenu(); - $auth = $event->getAuth(); + $auth = $this->security; - if (!$auth->isGranted('IS_AUTHENTICATED_FULLY')) { - return; - } - - if (!$auth->isGranted('ROLE_TEAMLEAD')) { + if (!$auth->isGranted('IS_AUTHENTICATED_REMEMBERED') || !$auth->isGranted('ROLE_TEAMLEAD')) { return; } @@ -68,13 +90,18 @@ class Menu return; } + if ($auth->isGranted('ROLE_SUPER_ADMIN')) { + $menu->addChild( + new MenuItemModel('user_admin', 'menu.admin_user', 'admin_user', [], 'fa fa-user') + ); + } + $menu->addChild( new MenuItemModel('customer_admin', 'menu.admin_customer', 'admin_customer', [], 'fa fa-users') )->addChild( new MenuItemModel('project_admin', 'menu.admin_project', 'admin_project', [], 'fa fa-book') )->addChild( new MenuItemModel('activity_admin', 'menu.admin_activity', 'admin_activity', [], 'fa fa-tasks') - ) - ; + ); } } diff --git a/src/EventListener/NavbarShowUserListener.php b/src/EventSubscriber/NavbarShowUserSubscriber.php similarity index 75% rename from src/EventListener/NavbarShowUserListener.php rename to src/EventSubscriber/NavbarShowUserSubscriber.php index 79bec296..29d7ab1f 100644 --- a/src/EventListener/NavbarShowUserListener.php +++ b/src/EventSubscriber/NavbarShowUserSubscriber.php @@ -9,19 +9,20 @@ * file that was distributed with this source code. */ -namespace App\EventListener; +namespace App\EventSubscriber; use App\Entity\User; use Avanzu\AdminThemeBundle\Event\ShowUserEvent; use Avanzu\AdminThemeBundle\Model\UserModel; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; /** - * Class NavbarShowUserListener + * Class NavbarShowUserSubscriber * * @author Kevin Papst */ -class NavbarShowUserListener +class NavbarShowUserSubscriber implements EventSubscriberInterface { /** * @var TokenStorageInterface @@ -37,6 +38,17 @@ class NavbarShowUserListener $this->storage = $tokenStorage; } + /** + * @return array + */ + public static function getSubscribedEvents(): array + { + return [ + 'theme.navbar_user' => ['onShowUser', 100], + 'theme.sidebar_user' => ['onShowUser', 100], + ]; + } + /** * @param ShowUserEvent $event */ diff --git a/src/EventListener/RedirectToPreferredLocaleListener.php b/src/EventSubscriber/RedirectToLocaleSubscriber.php similarity index 97% rename from src/EventListener/RedirectToPreferredLocaleListener.php rename to src/EventSubscriber/RedirectToLocaleSubscriber.php index 1c6890a2..8e91f27b 100644 --- a/src/EventListener/RedirectToPreferredLocaleListener.php +++ b/src/EventSubscriber/RedirectToLocaleSubscriber.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace App\EventListener; +namespace App\EventSubscriber; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -23,7 +23,7 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; * * @author Oleg Voronkovich */ -class RedirectToPreferredLocaleListener +class RedirectToLocaleSubscriber { /** * @var UrlGeneratorInterface diff --git a/src/Voter/AbstractVoter.php b/src/Voter/AbstractVoter.php index 160b5a40..9c90e99d 100644 --- a/src/Voter/AbstractVoter.php +++ b/src/Voter/AbstractVoter.php @@ -36,6 +36,19 @@ abstract class AbstractVoter extends Voter $this->decisionManager = $decisionManager; } + /** + * @param TokenInterface $token + * @return bool + */ + protected function isFullyAuthenticated(TokenInterface $token) + { + if ($this->decisionManager->decide($token, ['IS_AUTHENTICATED_FULLY'])) { + return true; + } + + return false; + } + /** * @param string $role * @param TokenInterface $token @@ -43,7 +56,7 @@ abstract class AbstractVoter extends Voter */ protected function hasRole($role, TokenInterface $token) { - if ($this->decisionManager->decide($token, array($role))) { + if ($this->decisionManager->decide($token, [$role])) { return true; } diff --git a/src/Voter/ActivityVoter.php b/src/Voter/ActivityVoter.php index 66bc1412..ec4354c4 100644 --- a/src/Voter/ActivityVoter.php +++ b/src/Voter/ActivityVoter.php @@ -27,6 +27,12 @@ class ActivityVoter extends AbstractVoter const EDIT = 'edit'; const DELETE = 'delete'; + const ALLOWED_ATTRIBUTES = [ + self::VIEW, + self::EDIT, + self::DELETE + ]; + /** * @param string $attribute * @param mixed $subject @@ -34,7 +40,7 @@ class ActivityVoter extends AbstractVoter */ protected function supports($attribute, $subject) { - if (!in_array($attribute, array(self::VIEW, self::EDIT, self::DELETE))) { + if (!in_array($attribute, self::ALLOWED_ATTRIBUTES)) { return false; } @@ -107,6 +113,6 @@ class ActivityVoter extends AbstractVoter */ protected function canDelete(TokenInterface $token) { - return $this->hasRole('ROLE_ADMIN', $token); + return $this->isFullyAuthenticated($token) && $this->hasRole('ROLE_ADMIN', $token); } } diff --git a/src/Voter/CustomerVoter.php b/src/Voter/CustomerVoter.php index 2bcc9b16..4290d29d 100644 --- a/src/Voter/CustomerVoter.php +++ b/src/Voter/CustomerVoter.php @@ -27,6 +27,12 @@ class CustomerVoter extends AbstractVoter const EDIT = 'edit'; const DELETE = 'delete'; + const ALLOWED_ATTRIBUTES = [ + self::VIEW, + self::EDIT, + self::DELETE + ]; + /** * @param string $attribute * @param mixed $subject @@ -34,7 +40,7 @@ class CustomerVoter extends AbstractVoter */ protected function supports($attribute, $subject) { - if (!in_array($attribute, array(self::VIEW, self::EDIT, self::DELETE))) { + if (!in_array($attribute, self::ALLOWED_ATTRIBUTES)) { return false; } @@ -107,6 +113,6 @@ class CustomerVoter extends AbstractVoter */ protected function canDelete(TokenInterface $token) { - return $this->hasRole('ROLE_ADMIN', $token); + return $this->isFullyAuthenticated($token) && $this->hasRole('ROLE_ADMIN', $token); } } diff --git a/src/Voter/ProjectVoter.php b/src/Voter/ProjectVoter.php index 40a781b0..83d4fb73 100644 --- a/src/Voter/ProjectVoter.php +++ b/src/Voter/ProjectVoter.php @@ -27,6 +27,12 @@ class ProjectVoter extends AbstractVoter const EDIT = 'edit'; const DELETE = 'delete'; + const ALLOWED_ATTRIBUTES = [ + self::VIEW, + self::EDIT, + self::DELETE + ]; + /** * @param string $attribute * @param mixed $subject @@ -34,7 +40,7 @@ class ProjectVoter extends AbstractVoter */ protected function supports($attribute, $subject) { - if (!in_array($attribute, array(self::VIEW, self::EDIT, self::DELETE))) { + if (!in_array($attribute, self::ALLOWED_ATTRIBUTES)) { return false; } @@ -107,6 +113,6 @@ class ProjectVoter extends AbstractVoter */ protected function canDelete(TokenInterface $token) { - return $this->hasRole('ROLE_ADMIN', $token); + return $this->isFullyAuthenticated($token) && $this->hasRole('ROLE_ADMIN', $token); } } diff --git a/src/Voter/TimesheetVoter.php b/src/Voter/TimesheetVoter.php index b43f3c6c..ffbbb3f4 100644 --- a/src/Voter/TimesheetVoter.php +++ b/src/Voter/TimesheetVoter.php @@ -30,6 +30,14 @@ class TimesheetVoter extends AbstractVoter const EDIT = 'edit'; const DELETE = 'delete'; + const ALLOWED_ATTRIBUTES = [ + self::START, + self::STOP, + self::VIEW, + self::EDIT, + self::DELETE + ]; + /** * @param string $attribute * @param mixed $subject @@ -37,7 +45,7 @@ class TimesheetVoter extends AbstractVoter */ protected function supports($attribute, $subject) { - if (!in_array($attribute, array(self::START, self::STOP, self::VIEW, self::EDIT, self::DELETE))) { + if (!in_array($attribute, self::ALLOWED_ATTRIBUTES)) { return false; } @@ -146,6 +154,10 @@ class TimesheetVoter extends AbstractVoter */ protected function canDelete(Timesheet $timesheet, User $user, TokenInterface $token) { + if (!$this->isFullyAuthenticated($token)) { + return false; + } + return $this->isOwnOrAdmin($timesheet, $user, $token); } diff --git a/src/Voter/UserVoter.php b/src/Voter/UserVoter.php index cc1a21af..8edfa9f0 100644 --- a/src/Voter/UserVoter.php +++ b/src/Voter/UserVoter.php @@ -28,11 +28,9 @@ class UserVoter extends AbstractVoter const PASSWORD = 'password'; const ROLES = 'roles'; const PREFERENCES = 'preferences'; - const VIEW_ALL = 'view_all'; const ALLOWED_ATTRIBUTES = [ self::VIEW, - self::VIEW_ALL, self::EDIT, self::CREATE, self::ROLES, @@ -82,7 +80,6 @@ class UserVoter extends AbstractVoter return $this->canEdit($subject, $user, $token); case self::DELETE: return $this->canDelete($subject, $user, $token); - case self::VIEW_ALL: case self::CREATE: // create actually passes in the current user as $subject, not the new one case self::ROLES: return $this->canAdminUsers($token); @@ -135,6 +132,6 @@ class UserVoter extends AbstractVoter */ protected function canAdminUsers(TokenInterface $token) { - return $this->hasRole('ROLE_SUPER_ADMIN', $token); + return $this->isFullyAuthenticated($token) && $this->hasRole('ROLE_SUPER_ADMIN', $token); } } diff --git a/templates/base.html.twig b/templates/base.html.twig index db511c65..6dfaf53c 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -48,13 +48,13 @@ {% endblock %} {% block avanzu_navbar_messages %} - {% if app.user is not null and is_granted('IS_AUTHENTICATED_FULLY') %} + {% if app.user is not null and is_granted('IS_AUTHENTICATED_REMEMBERED') %} {{ render(controller('App\\Controller\\TimesheetController::activeEntriesAction')) }} {% endif %} {% endblock %} {% block avanzu_navbar_notifications %} - {% if app.user is not null and is_granted('IS_AUTHENTICATED_FULLY') %} + {% if app.user is not null and is_granted('IS_AUTHENTICATED_REMEMBERED') %} {{ render(controller('App\\Controller\\ActivityController::recentActivitiesAction')) }} {% endif %} {% endblock %} diff --git a/templates/security/login.html.twig b/templates/security/login.html.twig index a305358e..df9ad412 100644 --- a/templates/security/login.html.twig +++ b/templates/security/login.html.twig @@ -2,6 +2,10 @@ {% block avanzu_login_title %}{{ 'browser.title'|trans }}{% endblock %} {% block avanzu_logo_login %}Kimai
TimeTracking{% endblock %} +{% block avanzu_head %} + +{% endblock %} + {% block avanzu_login_box_msg %} {{ 'security.title.login'|trans }} @@ -21,13 +25,12 @@
- {# -
- -
- #} +
+ +
diff --git a/templates/timesheet/index.html.twig b/templates/timesheet/index.html.twig index 199fd5fe..8e90bc22 100644 --- a/templates/timesheet/index.html.twig +++ b/templates/timesheet/index.html.twig @@ -38,10 +38,7 @@ {{ widgets.label_activity(entry.activity) }} {{ entry.description }} - {% set actionButtons = {} %} - {% if is_granted('edit', entry) %} - {% set actionButtons = {'edit': path('timesheet_edit', {'id' : entry.id, 'page': page})}|merge(actionButtons) %} - {% endif %} + {% set actionButtons = {'edit': path('timesheet_edit', {'id' : entry.id, 'page': page})} %} {% if entry.end %} {% if is_granted('start', entry.activity) %} {% set actionButtons = {'repeat': path('timesheet_start', {'id' : entry.activity.id})}|merge(actionButtons) %} @@ -51,9 +48,7 @@ {% set actionButtons = {'stop': path('timesheet_stop', {'id' : entry.id})}|merge(actionButtons) %} {% endif %} {% endif %} - {% if is_granted('delete', entry) %} - {% set actionButtons = actionButtons|merge({'trash': path('timesheet_delete', {'id' : entry.id, 'page': page})}) %} - {% endif %} + {% set actionButtons = actionButtons|merge({'trash': path('timesheet_delete', {'id' : entry.id, 'page': page})}) %} {{ widgets.button_group(actionButtons) }} diff --git a/translations/AvanzuAdminTheme.de.xliff b/translations/AvanzuAdminTheme.de.xliff index abe12d53..0214159d 100644 --- a/translations/AvanzuAdminTheme.de.xliff +++ b/translations/AvanzuAdminTheme.de.xliff @@ -6,6 +6,10 @@ Toggle navigation Ansicht der Seitenleiste umschalten + + Remember Me + Dauerhaft anmelden + diff --git a/translations/AvanzuAdminTheme.en.xliff b/translations/AvanzuAdminTheme.en.xliff index 9ad6fbd0..1cfee403 100644 --- a/translations/AvanzuAdminTheme.en.xliff +++ b/translations/AvanzuAdminTheme.en.xliff @@ -6,6 +6,10 @@ Toggle navigation Toggle navigation + + Remember Me + Remember Me + diff --git a/var/docs/users.md b/var/docs/users.md index bc3261a0..7bacdfa3 100644 --- a/var/docs/users.md +++ b/var/docs/users.md @@ -2,7 +2,7 @@ There are multiple pre-defined roles in Kimai, which define the ACLs. A user can only inherit one role, where the roles extend each user. -## Roles +## Roles & Permissions | Role name | extends | Gives permission for | |---|---|---| @@ -12,3 +12,21 @@ There are multiple pre-defined roles in Kimai, which define the ACLs. A user can | ROLE_ADMIN | ROLE_TEAMLEAD | All of the above, plus: editing customers, editing projects, editing activities | | ROLE_SUPER_ADMIN | ROLE_ADMIN | All of the above, plus: editing users | +# Login & Authentication + +- User can login with their username or email +- If you activate the `Remember me` option, you can use use the most common functions within the next days without a new login + +## Remember me login + +If you have chosen to login with the `Remember me` option, your login will be extended to one week (default value). +After coming back and being remembered you have access to all the following features: +- view your own timesheet +- start and stop new records +- edit existing records + +If you are an administrator, you will see all your allowed options in the menu, but will be redirected to the login +form when you try to access them. This is a security feature to prevent abuse in case you forgot to logout in public +environments. + +The default period for the `Remember me` option can be changed in the config file [security.yaml](config/packages/security.yaml).