Next major version 2 with PHP 8.1, Symfony 6, Tabler UI, 2FA ... (#2902)
This commit is contained in:
@@ -1,83 +0,0 @@
|
||||
<?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\Voter;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Security\AclDecisionManager;
|
||||
use App\Security\RolePermissionManager;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
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
|
||||
{
|
||||
/**
|
||||
* @var AclDecisionManager
|
||||
*/
|
||||
protected $decisionManager;
|
||||
/**
|
||||
* @var RolePermissionManager
|
||||
*/
|
||||
protected $roleManager;
|
||||
|
||||
public function __construct(AclDecisionManager $decisionManager, RolePermissionManager $roleManager)
|
||||
{
|
||||
$this->decisionManager = $decisionManager;
|
||||
$this->roleManager = $roleManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TokenInterface $token
|
||||
* @return bool
|
||||
*/
|
||||
protected function isFullyAuthenticated(TokenInterface $token)
|
||||
{
|
||||
return $this->decisionManager->isFullyAuthenticated($token);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $role
|
||||
* @param string $permission
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasPermission($role, $permission)
|
||||
{
|
||||
return $this->roleManager->hasPermission($role, $permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @param string $permission
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasRolePermission(User $user, $permission)
|
||||
{
|
||||
foreach ($user->getRoles() as $role) {
|
||||
if ($this->hasPermission($role, $permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $permission
|
||||
* @return bool
|
||||
*/
|
||||
public function isRegisteredPermission($permission)
|
||||
{
|
||||
return $this->roleManager->isRegisteredPermission($permission);
|
||||
}
|
||||
}
|
||||
@@ -33,11 +33,8 @@ final class ActivityVoter extends Voter
|
||||
'permissions',
|
||||
];
|
||||
|
||||
private $permissionManager;
|
||||
|
||||
public function __construct(RolePermissionManager $permissionManager)
|
||||
public function __construct(private RolePermissionManager $permissionManager)
|
||||
{
|
||||
$this->permissionManager = $permissionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,7 +42,7 @@ final class ActivityVoter extends Voter
|
||||
* @param Activity $subject
|
||||
* @return bool
|
||||
*/
|
||||
protected function supports($attribute, $subject)
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
if (!($subject instanceof Activity)) {
|
||||
return false;
|
||||
@@ -64,7 +61,7 @@ final class ActivityVoter extends Voter
|
||||
* @param TokenInterface $token
|
||||
* @return bool
|
||||
*/
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||
{
|
||||
$user = $token->getUser();
|
||||
|
||||
|
||||
@@ -33,16 +33,12 @@ final class CustomerVoter extends Voter
|
||||
'delete',
|
||||
'permissions',
|
||||
'comments',
|
||||
'comments_create',
|
||||
'details',
|
||||
'access',
|
||||
];
|
||||
|
||||
private $permissionManager;
|
||||
|
||||
public function __construct(RolePermissionManager $permissionManager)
|
||||
public function __construct(private RolePermissionManager $permissionManager)
|
||||
{
|
||||
$this->permissionManager = $permissionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +46,7 @@ final class CustomerVoter extends Voter
|
||||
* @param Customer $subject
|
||||
* @return bool
|
||||
*/
|
||||
protected function supports($attribute, $subject)
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
if (!($subject instanceof Customer)) {
|
||||
return false;
|
||||
@@ -69,7 +65,7 @@ final class CustomerVoter extends Voter
|
||||
* @param TokenInterface $token
|
||||
* @return bool
|
||||
*/
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||
{
|
||||
$user = $token->getUser();
|
||||
|
||||
|
||||
@@ -34,11 +34,8 @@ final class EntityMultiRoleVoter extends Voter
|
||||
'activity',
|
||||
];
|
||||
|
||||
private $permissionManager;
|
||||
|
||||
public function __construct(RolePermissionManager $permissionManager)
|
||||
public function __construct(private RolePermissionManager $permissionManager)
|
||||
{
|
||||
$this->permissionManager = $permissionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,7 +43,7 @@ final class EntityMultiRoleVoter extends Voter
|
||||
* @param Activity|Project|Customer|string $subject
|
||||
* @return bool
|
||||
*/
|
||||
protected function supports($attribute, $subject)
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
if (!\in_array($attribute, self::ALLOWED_ATTRIBUTES)) {
|
||||
return false;
|
||||
@@ -69,7 +66,7 @@ final class EntityMultiRoleVoter extends Voter
|
||||
* @param TokenInterface $token
|
||||
* @return bool
|
||||
*/
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||
{
|
||||
$user = $token->getUser();
|
||||
|
||||
|
||||
@@ -32,15 +32,11 @@ final class ProjectVoter extends Voter
|
||||
'delete',
|
||||
'permissions',
|
||||
'comments',
|
||||
'comments_create',
|
||||
'details',
|
||||
];
|
||||
|
||||
private $permissionManager;
|
||||
|
||||
public function __construct(RolePermissionManager $permissionManager)
|
||||
public function __construct(private RolePermissionManager $permissionManager)
|
||||
{
|
||||
$this->permissionManager = $permissionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,7 +44,7 @@ final class ProjectVoter extends Voter
|
||||
* @param Project $subject
|
||||
* @return bool
|
||||
*/
|
||||
protected function supports($attribute, $subject)
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
if (!($subject instanceof Project)) {
|
||||
return false;
|
||||
@@ -67,7 +63,7 @@ final class ProjectVoter extends Voter
|
||||
* @param TokenInterface $token
|
||||
* @return bool
|
||||
*/
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||
{
|
||||
$user = $token->getUser();
|
||||
|
||||
|
||||
@@ -17,13 +17,8 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
|
||||
final class QuickEntryVoter extends Voter
|
||||
{
|
||||
private $permissionManager;
|
||||
private $trackingModeService;
|
||||
|
||||
public function __construct(RolePermissionManager $permissionManager, TrackingModeService $trackingModeService)
|
||||
public function __construct(private RolePermissionManager $permissionManager, private TrackingModeService $trackingModeService)
|
||||
{
|
||||
$this->permissionManager = $permissionManager;
|
||||
$this->trackingModeService = $trackingModeService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,7 +26,7 @@ final class QuickEntryVoter extends Voter
|
||||
* @param mixed $subject
|
||||
* @return bool
|
||||
*/
|
||||
protected function supports($attribute, $subject)
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
return 'quick-entry' === $attribute;
|
||||
}
|
||||
@@ -42,7 +37,7 @@ final class QuickEntryVoter extends Voter
|
||||
* @param TokenInterface $token
|
||||
* @return bool
|
||||
*/
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||
{
|
||||
$user = $token->getUser();
|
||||
|
||||
|
||||
83
src/Voter/ReportingVoter.php
Normal file
83
src/Voter/ReportingVoter.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?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\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;
|
||||
|
||||
final class ReportingVoter extends Voter
|
||||
{
|
||||
private const ALLOWED_ATTRIBUTES = [
|
||||
'report:customer',
|
||||
'report:other',
|
||||
'report:project',
|
||||
'report:user',
|
||||
];
|
||||
|
||||
public function __construct(private RolePermissionManager $permissionManager)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $attribute
|
||||
* @param null $subject
|
||||
* @return bool
|
||||
*/
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
return $subject === null && \in_array($attribute, self::ALLOWED_ATTRIBUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $attribute
|
||||
* @param null $subject
|
||||
* @param TokenInterface $token
|
||||
* @return bool
|
||||
*/
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||
{
|
||||
$user = $token->getUser();
|
||||
|
||||
if (!$user instanceof User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$permissions = ['view_reporting'];
|
||||
|
||||
switch ($attribute) {
|
||||
case 'report:customer':
|
||||
$permissions[] = 'customer_reporting';
|
||||
break;
|
||||
|
||||
case 'report:other':
|
||||
$permissions[] = 'view_other_reporting';
|
||||
$permissions[] = 'view_other_timesheet';
|
||||
break;
|
||||
|
||||
case 'report:project':
|
||||
$permissions[] = 'project_reporting';
|
||||
break;
|
||||
|
||||
case 'report:user':
|
||||
// own reports are always allowed if reporting can be accessed
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
if (!$this->permissionManager->hasRolePermission($user, $permission)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -20,11 +20,8 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
*/
|
||||
final class RolePermissionVoter extends Voter
|
||||
{
|
||||
private $permissionManager;
|
||||
|
||||
public function __construct(RolePermissionManager $permissionManager)
|
||||
public function __construct(private RolePermissionManager $permissionManager)
|
||||
{
|
||||
$this->permissionManager = $permissionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,7 +29,7 @@ final class RolePermissionVoter extends Voter
|
||||
* @param mixed $subject
|
||||
* @return bool
|
||||
*/
|
||||
protected function supports($attribute, $subject)
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
// we only work on single strings that have no subject
|
||||
if (null !== $subject) {
|
||||
@@ -48,7 +45,7 @@ final class RolePermissionVoter extends Voter
|
||||
* @param TokenInterface $token
|
||||
* @return bool
|
||||
*/
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||
{
|
||||
$user = $token->getUser();
|
||||
|
||||
|
||||
@@ -26,11 +26,8 @@ final class TeamVoter extends Voter
|
||||
'delete',
|
||||
];
|
||||
|
||||
private $permissionManager;
|
||||
|
||||
public function __construct(RolePermissionManager $permissionManager)
|
||||
public function __construct(private RolePermissionManager $permissionManager)
|
||||
{
|
||||
$this->permissionManager = $permissionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,7 +35,7 @@ final class TeamVoter extends Voter
|
||||
* @param Team $subject
|
||||
* @return bool
|
||||
*/
|
||||
protected function supports($attribute, $subject)
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
if (!($subject instanceof Team)) {
|
||||
return false;
|
||||
@@ -57,7 +54,7 @@ final class TeamVoter extends Voter
|
||||
* @param TokenInterface $token
|
||||
* @return bool
|
||||
*/
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||
{
|
||||
$user = $token->getUser();
|
||||
|
||||
|
||||
@@ -48,18 +48,13 @@ final class TimesheetVoter extends Voter
|
||||
'duplicate'
|
||||
];
|
||||
|
||||
private $permissionManager;
|
||||
private $lockdownService;
|
||||
private ?bool $lockdownGrace = null;
|
||||
private ?bool $lockdownOverride = null;
|
||||
private ?bool $editExported = null;
|
||||
private ?\DateTime $now = null;
|
||||
|
||||
private $lockdownGrace;
|
||||
private $lockdownOverride;
|
||||
private $editExported;
|
||||
private $now;
|
||||
|
||||
public function __construct(RolePermissionManager $permissionManager, LockdownService $lockdownService)
|
||||
public function __construct(private RolePermissionManager $permissionManager, private LockdownService $lockdownService)
|
||||
{
|
||||
$this->permissionManager = $permissionManager;
|
||||
$this->lockdownService = $lockdownService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,7 +62,7 @@ final class TimesheetVoter extends Voter
|
||||
* @param mixed $subject
|
||||
* @return bool
|
||||
*/
|
||||
protected function supports($attribute, $subject)
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
if (!($subject instanceof Timesheet)) {
|
||||
return false;
|
||||
@@ -86,7 +81,7 @@ final class TimesheetVoter extends Voter
|
||||
* @param TokenInterface $token
|
||||
* @return bool
|
||||
*/
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||
{
|
||||
$user = $token->getUser();
|
||||
|
||||
@@ -142,7 +137,7 @@ final class TimesheetVoter extends Voter
|
||||
$permission .= '_';
|
||||
|
||||
// extend me for "team" support later on
|
||||
if ($subject->getUser()->getId() == $user->getId()) {
|
||||
if ($subject->getUser()->getId() === $user->getId()) {
|
||||
$permission .= 'own';
|
||||
} else {
|
||||
$permission .= 'other';
|
||||
@@ -153,7 +148,7 @@ final class TimesheetVoter extends Voter
|
||||
return $this->permissionManager->hasRolePermission($user, $permission);
|
||||
}
|
||||
|
||||
protected function canStart(Timesheet $timesheet): bool
|
||||
private function canStart(Timesheet $timesheet): bool
|
||||
{
|
||||
// possible improvements for the future:
|
||||
// we could check the amount of active entries (maybe slow)
|
||||
@@ -167,7 +162,7 @@ final class TimesheetVoter extends Voter
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$timesheet->getActivity()->isVisible() || !$timesheet->getProject()->isVisible()) {
|
||||
if (!$timesheet->getProject()->isVisible()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -175,10 +170,14 @@ final class TimesheetVoter extends Voter
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$timesheet->getActivity()->isVisible()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function canEdit(User $user, Timesheet $timesheet): bool
|
||||
private function canEdit(User $user, Timesheet $timesheet): bool
|
||||
{
|
||||
if (!$this->isAllowedExported($user, $timesheet)) {
|
||||
return false;
|
||||
@@ -191,7 +190,7 @@ final class TimesheetVoter extends Voter
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function canDelete(User $user, Timesheet $timesheet): bool
|
||||
private function canDelete(User $user, Timesheet $timesheet): bool
|
||||
{
|
||||
if (!$this->isAllowedExported($user, $timesheet)) {
|
||||
return false;
|
||||
@@ -204,7 +203,7 @@ final class TimesheetVoter extends Voter
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function canDuplicate(User $user, Timesheet $timesheet): bool
|
||||
private function canDuplicate(User $user, Timesheet $timesheet): bool
|
||||
{
|
||||
if (!$this->isAllowedInLockdown($user, $timesheet)) {
|
||||
return false;
|
||||
|
||||
@@ -25,6 +25,7 @@ final class UserVoter extends Voter
|
||||
'roles',
|
||||
'teams',
|
||||
'password',
|
||||
'2fa',
|
||||
'delete',
|
||||
'preferences',
|
||||
'api-token',
|
||||
@@ -32,11 +33,8 @@ final class UserVoter extends Voter
|
||||
'view_team_member',
|
||||
];
|
||||
|
||||
private $permissionManager;
|
||||
|
||||
public function __construct(RolePermissionManager $permissionManager)
|
||||
public function __construct(private RolePermissionManager $permissionManager)
|
||||
{
|
||||
$this->permissionManager = $permissionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,7 +42,7 @@ final class UserVoter extends Voter
|
||||
* @param mixed $subject
|
||||
* @return bool
|
||||
*/
|
||||
protected function supports($attribute, $subject)
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
if (!($subject instanceof User)) {
|
||||
return false;
|
||||
@@ -63,7 +61,7 @@ final class UserVoter extends Voter
|
||||
* @param TokenInterface $token
|
||||
* @return bool
|
||||
*/
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||
{
|
||||
$user = $token->getUser();
|
||||
|
||||
@@ -93,6 +91,12 @@ final class UserVoter extends Voter
|
||||
}
|
||||
}
|
||||
|
||||
if ($attribute === '2fa') {
|
||||
// two factor only works for internal users and
|
||||
// can only be activated by the logged-in user for himself
|
||||
return $subject->isInternalUser() && $subject->getId() === $user->getId();
|
||||
}
|
||||
|
||||
$permission = $attribute;
|
||||
|
||||
// extend me for "team" support later on
|
||||
|
||||
Reference in New Issue
Block a user