improve permission checks for timesheets with activated lockdown (#2271)
This commit is contained in:
@@ -90,6 +90,17 @@ final class RolePermissionManager
|
||||
return \in_array($permission, $this->permissions[$role]);
|
||||
}
|
||||
|
||||
public function hasRolePermission(User $user, string $permission)
|
||||
{
|
||||
foreach ($user->getRoles() as $role) {
|
||||
if ($this->hasPermission($role, $permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only permissions which were registered through the Symfony configuration stack will be returned here.
|
||||
*
|
||||
|
||||
96
src/Timesheet/LockdownService.php
Normal file
96
src/Timesheet/LockdownService.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?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\Timesheet;
|
||||
|
||||
use App\Configuration\SystemConfiguration;
|
||||
use App\Entity\Timesheet;
|
||||
|
||||
final class LockdownService
|
||||
{
|
||||
private $configuration;
|
||||
private $isActive;
|
||||
|
||||
public function __construct(SystemConfiguration $configuration)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
public function isLockdownActive(): bool
|
||||
{
|
||||
if ($this->isActive === null) {
|
||||
$this->isActive = $this->configuration->isTimesheetLockdownActive();
|
||||
}
|
||||
|
||||
return $this->isActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not check if the current user is allowed to edit timesheets in lockdown situations.
|
||||
* This needs to be performed earlier by yourself (see TimesheetVoter or LockdownValidator).
|
||||
*
|
||||
* @param Timesheet $timesheet
|
||||
* @param \DateTime $now
|
||||
* @param bool $allowEditInGracePeriod
|
||||
* @return bool
|
||||
*/
|
||||
public function isEditable(Timesheet $timesheet, \DateTime $now, bool $allowEditInGracePeriod = false)
|
||||
{
|
||||
if (!$this->isLockdownActive()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$timesheetStart = $timesheet->getBegin();
|
||||
|
||||
if (null === $timesheetStart) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$lockedStart = $this->configuration->getTimesheetLockdownPeriodStart();
|
||||
$lockedEnd = $this->configuration->getTimesheetLockdownPeriodEnd();
|
||||
|
||||
$gracePeriod = $this->configuration->getTimesheetLockdownGracePeriod();
|
||||
if (!empty($gracePeriod)) {
|
||||
$gracePeriod = $gracePeriod . ' ';
|
||||
}
|
||||
|
||||
try {
|
||||
$lockdownStart = new \DateTime($lockedStart, $timesheetStart->getTimezone());
|
||||
$lockdownEnd = new \DateTime($lockedEnd, $timesheetStart->getTimezone());
|
||||
$lockdownGrace = new \DateTime($gracePeriod . $lockdownEnd->format('Y-m-d'), $timesheetStart->getTimezone());
|
||||
} catch (\Exception $ex) {
|
||||
// should not happen, but ... if parsing of datetimes fails: skip validation
|
||||
return true;
|
||||
}
|
||||
|
||||
// misconfiguration detected, skip validation
|
||||
if ($lockdownEnd < $lockdownStart) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// validate only entries added before the end of lockdown period
|
||||
if ($timesheetStart > $lockdownEnd) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// further validate entries inside of the most recent lockdown
|
||||
if ($timesheetStart > $lockdownStart && $timesheetStart < $lockdownEnd) {
|
||||
// if grace period is still in effect, validation succeeds
|
||||
if ($now < $lockdownGrace) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($allowEditInGracePeriod) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ use App\Security\CurrentUser;
|
||||
use DateTimeZone;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
* @deprecated will be removed with 2.0
|
||||
*/
|
||||
class UserDateTimeFactory extends DateTimeFactory
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
|
||||
namespace App\Validator\Constraints;
|
||||
|
||||
use App\Configuration\SystemConfiguration;
|
||||
use App\Entity\Timesheet as TimesheetEntity;
|
||||
use App\Timesheet\LockdownService;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
@@ -18,19 +18,13 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
final class TimesheetLockdownValidator extends ConstraintValidator
|
||||
{
|
||||
/**
|
||||
* @var AuthorizationCheckerInterface
|
||||
*/
|
||||
private $auth;
|
||||
/**
|
||||
* @var SystemConfiguration
|
||||
*/
|
||||
private $configuration;
|
||||
private $lockdownService;
|
||||
|
||||
public function __construct(AuthorizationCheckerInterface $auth, SystemConfiguration $configuration)
|
||||
public function __construct(AuthorizationCheckerInterface $auth, LockdownService $lockdownService)
|
||||
{
|
||||
$this->auth = $auth;
|
||||
$this->configuration = $configuration;
|
||||
$this->lockdownService = $lockdownService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,40 +41,11 @@ final class TimesheetLockdownValidator extends ConstraintValidator
|
||||
throw new UnexpectedTypeException($timesheet, TimesheetEntity::class);
|
||||
}
|
||||
|
||||
$timesheetStart = $timesheet->getBegin();
|
||||
|
||||
if (null === $timesheetStart) {
|
||||
if (!$this->lockdownService->isLockdownActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->configuration->isTimesheetLockdownActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$lockedStart = $this->configuration->getTimesheetLockdownPeriodStart();
|
||||
$lockedEnd = $this->configuration->getTimesheetLockdownPeriodEnd();
|
||||
|
||||
$gracePeriod = $this->configuration->getTimesheetLockdownGracePeriod();
|
||||
if (!empty($gracePeriod)) {
|
||||
$gracePeriod = $gracePeriod . ' ';
|
||||
}
|
||||
|
||||
try {
|
||||
$lockdownStart = new \DateTime($lockedStart, $timesheetStart->getTimezone());
|
||||
$lockdownEnd = new \DateTime($lockedEnd, $timesheetStart->getTimezone());
|
||||
$lockdownGrace = new \DateTime($gracePeriod . $lockdownEnd->format('Y-m-d'), $timesheetStart->getTimezone());
|
||||
} catch (\Exception $ex) {
|
||||
// should not happen, but ... if parsing of datetimes fails: skip validation
|
||||
return;
|
||||
}
|
||||
|
||||
// misconfiguration detected, skip validation
|
||||
if ($lockdownEnd < $lockdownStart) {
|
||||
return;
|
||||
}
|
||||
|
||||
// validate only entries added before the end of lockdown period
|
||||
if ($timesheetStart > $lockdownEnd) {
|
||||
if (null === ($timesheetStart = $timesheet->getBegin())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -89,6 +54,8 @@ final class TimesheetLockdownValidator extends ConstraintValidator
|
||||
return;
|
||||
}
|
||||
|
||||
$now = new \DateTime('now', $timesheetStart->getTimezone());
|
||||
|
||||
if (!empty($constraint->now)) {
|
||||
if ($constraint->now instanceof \DateTime) {
|
||||
$now = $constraint->now;
|
||||
@@ -100,21 +67,10 @@ final class TimesheetLockdownValidator extends ConstraintValidator
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($now)) {
|
||||
$now = new \DateTime('now', $timesheetStart->getTimezone());
|
||||
}
|
||||
$allowEditInGracePeriod = $this->auth->isGranted('lockdown_grace_timesheet');
|
||||
|
||||
// further validate entries inside of the most recent lockdown
|
||||
if ($timesheetStart > $lockdownStart && $timesheetStart < $lockdownEnd) {
|
||||
// if grace period is still in effect, validation succeeds
|
||||
if ($now < $lockdownGrace) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if user has special role, validation succeeds
|
||||
if ($this->auth->isGranted('lockdown_grace_timesheet')) {
|
||||
return;
|
||||
}
|
||||
if ($this->lockdownService->isEditable($timesheet, $now, $allowEditInGracePeriod)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// raise a violation for all entries before the start of lockdown period
|
||||
|
||||
@@ -17,6 +17,8 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
|
||||
/**
|
||||
* Abstract voter to help with checking user permissions.
|
||||
* @codeCoverageIgnore
|
||||
* @deprecated since 1.13
|
||||
*/
|
||||
abstract class AbstractVoter extends Voter
|
||||
{
|
||||
|
||||
@@ -12,17 +12,19 @@ namespace App\Voter;
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\Team;
|
||||
use App\Entity\User;
|
||||
use App\Security\RolePermissionManager;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
|
||||
/**
|
||||
* A voter to check permissions on Activities.
|
||||
*/
|
||||
class ActivityVoter extends AbstractVoter
|
||||
class ActivityVoter extends Voter
|
||||
{
|
||||
/**
|
||||
* support rules based on the given activity
|
||||
*/
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
private const ALLOWED_ATTRIBUTES = [
|
||||
'view',
|
||||
'edit',
|
||||
'budget',
|
||||
@@ -30,6 +32,13 @@ class ActivityVoter extends AbstractVoter
|
||||
'permissions',
|
||||
];
|
||||
|
||||
private $permissionManager;
|
||||
|
||||
public function __construct(RolePermissionManager $permissionManager)
|
||||
{
|
||||
$this->permissionManager = $permissionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $attribute
|
||||
* @param Activity $subject
|
||||
@@ -62,7 +71,7 @@ class ActivityVoter extends AbstractVoter
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasRolePermission($user, $attribute . '_activity')) {
|
||||
if ($this->permissionManager->hasRolePermission($user, $attribute . '_activity')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -71,8 +80,8 @@ class ActivityVoter extends AbstractVoter
|
||||
return false;
|
||||
}
|
||||
|
||||
$hasTeamleadPermission = $this->hasRolePermission($user, $attribute . '_teamlead_activity');
|
||||
$hasTeamPermission = $this->hasRolePermission($user, $attribute . '_team_activity');
|
||||
$hasTeamleadPermission = $this->permissionManager->hasRolePermission($user, $attribute . '_teamlead_activity');
|
||||
$hasTeamPermission = $this->permissionManager->hasRolePermission($user, $attribute . '_team_activity');
|
||||
|
||||
if (!$hasTeamleadPermission && !$hasTeamPermission) {
|
||||
return false;
|
||||
|
||||
@@ -12,17 +12,19 @@ namespace App\Voter;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\Team;
|
||||
use App\Entity\User;
|
||||
use App\Security\RolePermissionManager;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
|
||||
/**
|
||||
* A voter to check authorization on Customers.
|
||||
*/
|
||||
class CustomerVoter extends AbstractVoter
|
||||
class CustomerVoter extends Voter
|
||||
{
|
||||
/**
|
||||
* supported attributes/rules based on the given customer
|
||||
*/
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
private const ALLOWED_ATTRIBUTES = [
|
||||
'view',
|
||||
'create',
|
||||
'edit',
|
||||
@@ -34,6 +36,13 @@ class CustomerVoter extends AbstractVoter
|
||||
'details',
|
||||
];
|
||||
|
||||
private $permissionManager;
|
||||
|
||||
public function __construct(RolePermissionManager $permissionManager)
|
||||
{
|
||||
$this->permissionManager = $permissionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $attribute
|
||||
* @param Customer $subject
|
||||
@@ -66,7 +75,7 @@ class CustomerVoter extends AbstractVoter
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasRolePermission($user, $attribute . '_customer')) {
|
||||
if ($this->permissionManager->hasRolePermission($user, $attribute . '_customer')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -75,8 +84,8 @@ class CustomerVoter extends AbstractVoter
|
||||
return false;
|
||||
}
|
||||
|
||||
$hasTeamleadPermission = $this->hasRolePermission($user, $attribute . '_teamlead_customer');
|
||||
$hasTeamPermission = $this->hasRolePermission($user, $attribute . '_team_customer');
|
||||
$hasTeamleadPermission = $this->permissionManager->hasRolePermission($user, $attribute . '_teamlead_customer');
|
||||
$hasTeamPermission = $this->permissionManager->hasRolePermission($user, $attribute . '_team_customer');
|
||||
|
||||
if (!$hasTeamleadPermission && !$hasTeamPermission) {
|
||||
return false;
|
||||
|
||||
@@ -12,17 +12,19 @@ namespace App\Voter;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\Team;
|
||||
use App\Entity\User;
|
||||
use App\Security\RolePermissionManager;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
|
||||
/**
|
||||
* A voter to check permissions on Projects.
|
||||
*/
|
||||
class ProjectVoter extends AbstractVoter
|
||||
class ProjectVoter extends Voter
|
||||
{
|
||||
/**
|
||||
* support rules based on the given project
|
||||
*/
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
private const ALLOWED_ATTRIBUTES = [
|
||||
'view',
|
||||
'edit',
|
||||
'budget',
|
||||
@@ -33,6 +35,13 @@ class ProjectVoter extends AbstractVoter
|
||||
'details',
|
||||
];
|
||||
|
||||
private $permissionManager;
|
||||
|
||||
public function __construct(RolePermissionManager $permissionManager)
|
||||
{
|
||||
$this->permissionManager = $permissionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $attribute
|
||||
* @param Project $subject
|
||||
@@ -65,7 +74,7 @@ class ProjectVoter extends AbstractVoter
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasRolePermission($user, $attribute . '_project')) {
|
||||
if ($this->permissionManager->hasRolePermission($user, $attribute . '_project')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -74,8 +83,8 @@ class ProjectVoter extends AbstractVoter
|
||||
return false;
|
||||
}
|
||||
|
||||
$hasTeamleadPermission = $this->hasRolePermission($user, $attribute . '_teamlead_project');
|
||||
$hasTeamPermission = $this->hasRolePermission($user, $attribute . '_team_project');
|
||||
$hasTeamleadPermission = $this->permissionManager->hasRolePermission($user, $attribute . '_teamlead_project');
|
||||
$hasTeamPermission = $this->permissionManager->hasRolePermission($user, $attribute . '_team_project');
|
||||
|
||||
if (!$hasTeamleadPermission && !$hasTeamPermission) {
|
||||
return false;
|
||||
|
||||
@@ -11,13 +11,22 @@ namespace App\Voter;
|
||||
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\User;
|
||||
use App\Security\RolePermissionManager;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
|
||||
/**
|
||||
* A voter to check the free-configurable permission from "kimai.permissions".
|
||||
*/
|
||||
class RolePermissionVoter extends AbstractVoter
|
||||
class RolePermissionVoter extends Voter
|
||||
{
|
||||
private $permissionManager;
|
||||
|
||||
public function __construct(RolePermissionManager $permissionManager)
|
||||
{
|
||||
$this->permissionManager = $permissionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $attribute
|
||||
* @param mixed $subject
|
||||
@@ -30,7 +39,7 @@ class RolePermissionVoter extends AbstractVoter
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->isRegisteredPermission($attribute);
|
||||
return $this->permissionManager->isRegisteredPermission($attribute);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,6 +56,6 @@ class RolePermissionVoter extends AbstractVoter
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->hasRolePermission($user, $attribute);
|
||||
return $this->permissionManager->hasRolePermission($user, $attribute);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,19 +11,28 @@ namespace App\Voter;
|
||||
|
||||
use App\Entity\Team;
|
||||
use App\Entity\User;
|
||||
use App\Security\RolePermissionManager;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
|
||||
class TeamVoter extends AbstractVoter
|
||||
class TeamVoter extends Voter
|
||||
{
|
||||
/**
|
||||
* support rules based on the given $subject (here: Team)
|
||||
*/
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
private const ALLOWED_ATTRIBUTES = [
|
||||
'view',
|
||||
'edit',
|
||||
'delete',
|
||||
];
|
||||
|
||||
private $permissionManager;
|
||||
|
||||
public function __construct(RolePermissionManager $permissionManager)
|
||||
{
|
||||
$this->permissionManager = $permissionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $attribute
|
||||
* @param Team $subject
|
||||
@@ -56,6 +65,6 @@ class TeamVoter extends AbstractVoter
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->hasRolePermission($user, $attribute . '_team');
|
||||
return $this->permissionManager->hasRolePermission($user, $attribute . '_team');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,15 @@ namespace App\Voter;
|
||||
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\User;
|
||||
use App\Security\RolePermissionManager;
|
||||
use App\Timesheet\LockdownService;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
|
||||
/**
|
||||
* A voter to check permissions on Timesheets.
|
||||
*/
|
||||
class TimesheetVoter extends AbstractVoter
|
||||
class TimesheetVoter extends Voter
|
||||
{
|
||||
public const VIEW = 'view';
|
||||
public const START = 'start';
|
||||
@@ -31,7 +34,7 @@ class TimesheetVoter extends AbstractVoter
|
||||
/**
|
||||
* support rules based on the given $subject (here: Timesheet)
|
||||
*/
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
private const ALLOWED_ATTRIBUTES = [
|
||||
self::VIEW,
|
||||
self::START,
|
||||
self::STOP,
|
||||
@@ -44,6 +47,20 @@ class TimesheetVoter extends AbstractVoter
|
||||
'duplicate'
|
||||
];
|
||||
|
||||
private $permissionManager;
|
||||
private $lockdownService;
|
||||
|
||||
private $lockdownGrace;
|
||||
private $lockdownOverride;
|
||||
private $editExported;
|
||||
private $now;
|
||||
|
||||
public function __construct(RolePermissionManager $permissionManager, LockdownService $lockdownService)
|
||||
{
|
||||
$this->permissionManager = $permissionManager;
|
||||
$this->lockdownService = $lockdownService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $attribute
|
||||
* @param mixed $subject
|
||||
@@ -101,6 +118,9 @@ class TimesheetVoter extends AbstractVoter
|
||||
break;
|
||||
|
||||
case 'duplicate':
|
||||
if (!$this->canDuplicate($user, $subject)) {
|
||||
return false;
|
||||
}
|
||||
$permission = self::EDIT;
|
||||
break;
|
||||
|
||||
@@ -128,7 +148,7 @@ class TimesheetVoter extends AbstractVoter
|
||||
|
||||
$permission .= '_timesheet';
|
||||
|
||||
return $this->hasRolePermission($user, $permission);
|
||||
return $this->permissionManager->hasRolePermission($user, $permission);
|
||||
}
|
||||
|
||||
protected function canStart(Timesheet $timesheet): bool
|
||||
@@ -158,7 +178,11 @@ class TimesheetVoter extends AbstractVoter
|
||||
|
||||
protected function canEdit(User $user, Timesheet $timesheet): bool
|
||||
{
|
||||
if ($timesheet->isExported() && !$this->hasRolePermission($user, 'edit_exported_timesheet')) {
|
||||
if (!$this->isAllowedExported($user, $timesheet)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->isAllowedInLockdown($user, $timesheet)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -167,10 +191,61 @@ class TimesheetVoter extends AbstractVoter
|
||||
|
||||
protected function canDelete(User $user, Timesheet $timesheet): bool
|
||||
{
|
||||
if ($timesheet->isExported() && !$this->hasRolePermission($user, 'edit_exported_timesheet')) {
|
||||
if (!$this->isAllowedExported($user, $timesheet)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->isAllowedInLockdown($user, $timesheet)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function canDuplicate(User $user, Timesheet $timesheet): bool
|
||||
{
|
||||
if (!$this->isAllowedInLockdown($user, $timesheet)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function isAllowedExported(User $user, Timesheet $timesheet): bool
|
||||
{
|
||||
if (!$timesheet->isExported()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->editExported === null) {
|
||||
$this->editExported = $this->permissionManager->hasRolePermission($user, 'edit_exported_timesheet');
|
||||
}
|
||||
|
||||
return $this->editExported;
|
||||
}
|
||||
|
||||
private function isAllowedInLockdown(User $user, Timesheet $timesheet): bool
|
||||
{
|
||||
if (!$this->lockdownService->isLockdownActive()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->lockdownOverride === null) {
|
||||
$this->lockdownOverride = $this->permissionManager->hasRolePermission($user, 'lockdown_override_timesheet');
|
||||
}
|
||||
|
||||
if ($this->lockdownOverride) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->lockdownGrace === null) {
|
||||
$this->lockdownGrace = $this->permissionManager->hasRolePermission($user, 'lockdown_grace_timesheet');
|
||||
}
|
||||
|
||||
if ($this->now === null) {
|
||||
$this->now = new \DateTime('now', new \DateTimeZone($user->getTimezone()));
|
||||
}
|
||||
|
||||
return $this->lockdownService->isEditable($timesheet, $this->now, $this->lockdownGrace);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,14 +10,16 @@
|
||||
namespace App\Voter;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Security\RolePermissionManager;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
|
||||
/**
|
||||
* A voter to check permissions on user profiles.
|
||||
*/
|
||||
class UserVoter extends AbstractVoter
|
||||
class UserVoter extends Voter
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
private const ALLOWED_ATTRIBUTES = [
|
||||
'view',
|
||||
'edit',
|
||||
'roles',
|
||||
@@ -29,6 +31,13 @@ class UserVoter extends AbstractVoter
|
||||
'hourly-rate',
|
||||
];
|
||||
|
||||
private $permissionManager;
|
||||
|
||||
public function __construct(RolePermissionManager $permissionManager)
|
||||
{
|
||||
$this->permissionManager = $permissionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $attribute
|
||||
* @param mixed $subject
|
||||
@@ -66,8 +75,10 @@ class UserVoter extends AbstractVoter
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->hasRolePermission($user, 'delete_user');
|
||||
} elseif ($attribute === 'password') {
|
||||
return $this->permissionManager->hasRolePermission($user, 'delete_user');
|
||||
}
|
||||
|
||||
if ($attribute === 'password') {
|
||||
if (!$subject->isInternalUser()) {
|
||||
return false;
|
||||
}
|
||||
@@ -84,6 +95,6 @@ class UserVoter extends AbstractVoter
|
||||
|
||||
$permission .= '_profile';
|
||||
|
||||
return $this->hasRolePermission($user, $permission);
|
||||
return $this->permissionManager->hasRolePermission($user, $permission);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user