added new permission to separate time and money budget (#3352)
This commit is contained in:
@@ -212,6 +212,7 @@ class ActivityController extends BaseApiController
|
||||
|
||||
$form = $this->createForm(ActivityApiEditForm::class, $activity, [
|
||||
'include_budget' => $this->isGranted('budget', $activity),
|
||||
'include_time' => $this->isGranted('time', $activity),
|
||||
]);
|
||||
|
||||
$form->submit($request->request->all());
|
||||
@@ -276,6 +277,7 @@ class ActivityController extends BaseApiController
|
||||
|
||||
$form = $this->createForm(ActivityApiEditForm::class, $activity, [
|
||||
'include_budget' => $this->isGranted('budget', $activity),
|
||||
'include_time' => $this->isGranted('time', $activity),
|
||||
]);
|
||||
|
||||
$form->setData($activity);
|
||||
|
||||
@@ -182,6 +182,7 @@ class CustomerController extends BaseApiController
|
||||
|
||||
$form = $this->createForm(CustomerApiEditForm::class, $customer, [
|
||||
'include_budget' => $this->isGranted('budget', $customer),
|
||||
'include_time' => $this->isGranted('time', $customer),
|
||||
]);
|
||||
|
||||
$form->submit($request->request->all());
|
||||
@@ -246,6 +247,7 @@ class CustomerController extends BaseApiController
|
||||
|
||||
$form = $this->createForm(CustomerApiEditForm::class, $customer, [
|
||||
'include_budget' => $this->isGranted('budget', $customer),
|
||||
'include_time' => $this->isGranted('time', $customer),
|
||||
]);
|
||||
|
||||
$form->setData($customer);
|
||||
|
||||
@@ -226,6 +226,7 @@ class ProjectController extends BaseApiController
|
||||
'timezone' => $this->getDateTimeFactory()->getTimezone()->getName(),
|
||||
'date_format' => self::DATE_FORMAT,
|
||||
'include_budget' => $this->isGranted('budget', $project),
|
||||
'include_time' => $this->isGranted('time', $project),
|
||||
]);
|
||||
|
||||
$form->submit($request->request->all());
|
||||
@@ -292,6 +293,7 @@ class ProjectController extends BaseApiController
|
||||
'timezone' => $this->getDateTimeFactory()->getTimezone()->getName(),
|
||||
'date_format' => self::DATE_FORMAT,
|
||||
'include_budget' => $this->isGranted('budget', $project),
|
||||
'include_time' => $this->isGranted('time', $project),
|
||||
]);
|
||||
|
||||
$form->setData($project);
|
||||
|
||||
@@ -133,7 +133,7 @@ final class ActivityController extends AbstractController
|
||||
$rates = $rateRepository->getRatesForActivity($activity);
|
||||
}
|
||||
|
||||
if ($this->isGranted('budget', $activity)) {
|
||||
if ($this->isGranted('budget', $activity) || $this->isGranted('time', $activity)) {
|
||||
$stats = $statisticService->getBudgetStatisticModel($activity, $now);
|
||||
}
|
||||
|
||||
@@ -424,7 +424,8 @@ final class ActivityController extends AbstractController
|
||||
'action' => $url,
|
||||
'method' => 'POST',
|
||||
'currency' => $currency,
|
||||
'include_budget' => $this->isGranted('budget', $activity)
|
||||
'include_budget' => $this->isGranted('budget', $activity),
|
||||
'include_time' => $this->isGranted('time', $activity),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ final class CustomerController extends AbstractController
|
||||
$timezone = new \DateTimeZone($customer->getTimezone());
|
||||
}
|
||||
|
||||
if ($this->isGranted('budget', $customer)) {
|
||||
if ($this->isGranted('budget', $customer) || $this->isGranted('time', $customer)) {
|
||||
$stats = $statisticService->getBudgetStatisticModel($customer, $now);
|
||||
}
|
||||
|
||||
@@ -530,7 +530,8 @@ final class CustomerController extends AbstractController
|
||||
return $this->createForm(CustomerEditForm::class, $customer, [
|
||||
'action' => $url,
|
||||
'method' => 'POST',
|
||||
'include_budget' => $this->isGranted('budget', $customer)
|
||||
'include_budget' => $this->isGranted('budget', $customer),
|
||||
'include_time' => $this->isGranted('time', $customer),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@ final class ProjectController extends AbstractController
|
||||
$rates = $rateRepository->getRatesForProject($project);
|
||||
}
|
||||
|
||||
if ($this->isGranted('budget', $project)) {
|
||||
if ($this->isGranted('budget', $project) || $this->isGranted('time', $project)) {
|
||||
$stats = $statisticService->getBudgetStatisticModel($project, $now);
|
||||
}
|
||||
|
||||
@@ -563,6 +563,7 @@ final class ProjectController extends AbstractController
|
||||
'currency' => $currency,
|
||||
'timezone' => $this->getDateTimeFactory()->getTimezone()->getName(),
|
||||
'include_budget' => $this->isGranted('budget', $project),
|
||||
'include_time' => $this->isGranted('time', $project),
|
||||
'time_increment' => 15,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ final class ProjectDateRangeController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @Route(path="/reporting/project_daterange", name="report_project_daterange", methods={"GET","POST"})
|
||||
* @Security("is_granted('view_reporting') and is_granted('budget_project')")
|
||||
* @Security("is_granted('view_reporting') and is_granted('budget_any', 'project')")
|
||||
*/
|
||||
public function __invoke(Request $request, ProjectStatisticService $service)
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@ final class ProjectDetailsController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @Route(path="/reporting/project_details", name="report_project_details", methods={"GET"})
|
||||
* @Security("is_granted('view_reporting') and (is_granted('details_project') or is_granted('details_teamlead_project') or is_granted('details_team_project'))")
|
||||
* @Security("is_granted('view_reporting') and is_granted('details', 'project')")
|
||||
*/
|
||||
public function __invoke(Request $request, ProjectStatisticService $service)
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ final class ProjectInactiveController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @Route(path="/reporting/project_inactive", name="report_project_inactive", methods={"GET","POST"})
|
||||
* @Security("is_granted('view_reporting') and is_granted('budget_project')")
|
||||
* @Security("is_granted('view_reporting') and is_granted('budget_any', 'project')")
|
||||
*/
|
||||
public function __invoke(Request $request, ProjectStatisticService $service)
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ final class ProjectViewController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @Route(path="/reporting/project_view", name="report_project_view", methods={"GET","POST"})
|
||||
* @Security("is_granted('view_reporting') and is_granted('budget_project')")
|
||||
* @Security("is_granted('view_reporting') and is_granted('budget_any', 'project')")
|
||||
*/
|
||||
public function __invoke(Request $request, ProjectStatisticService $service)
|
||||
{
|
||||
|
||||
@@ -78,7 +78,7 @@ class CustomerSubscriber extends AbstractActionsSubscriber
|
||||
$event->addDelete($this->path('admin_customer_delete', ['id' => $customer->getId()]));
|
||||
}
|
||||
|
||||
if ($this->isGranted('view_reporting') && $this->isGranted('budget_project')) {
|
||||
if ($this->isGranted('view_reporting') && $this->isGranted('budget_any', 'project')) {
|
||||
$event->addAction('report_project_view', ['url' => $this->path('report_project_view', ['customer' => $customer->getId()]), 'icon' => 'reporting', 'translation_domain' => 'reporting']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ class ActivityEditForm extends AbstractType
|
||||
'customer' => false,
|
||||
'currency' => Customer::DEFAULT_CURRENCY,
|
||||
'include_budget' => false,
|
||||
'include_time' => false,
|
||||
'attr' => [
|
||||
'data-form-event' => 'kimai.activityUpdate'
|
||||
],
|
||||
|
||||
@@ -114,6 +114,7 @@ class CustomerEditForm extends AbstractType
|
||||
'csrf_token_id' => 'admin_customer_edit',
|
||||
'currency' => Customer::DEFAULT_CURRENCY,
|
||||
'include_budget' => false,
|
||||
'include_time' => false,
|
||||
'attr' => [
|
||||
'data-form-event' => 'kimai.customerUpdate'
|
||||
],
|
||||
|
||||
@@ -26,22 +26,30 @@ trait EntityFormTrait
|
||||
{
|
||||
$this->addColor($builder);
|
||||
|
||||
if ($options['include_budget']) {
|
||||
$builder
|
||||
->add('budget', MoneyType::class, [
|
||||
'empty_data' => '0.00',
|
||||
'label' => 'label.budget',
|
||||
'required' => false,
|
||||
'currency' => $options['currency'],
|
||||
])
|
||||
->add('timeBudget', DurationType::class, [
|
||||
'empty_data' => 0,
|
||||
'label' => 'label.timeBudget',
|
||||
'icon' => 'clock',
|
||||
'required' => false,
|
||||
])
|
||||
->add('budgetType', BudgetType::class)
|
||||
;
|
||||
$showMoney = $options['include_budget'];
|
||||
$showTime = $options['include_time'];
|
||||
$showBudget = $showMoney || $showTime;
|
||||
|
||||
if ($showMoney) {
|
||||
$builder->add('budget', MoneyType::class, [
|
||||
'empty_data' => '0.00',
|
||||
'label' => 'label.budget',
|
||||
'required' => false,
|
||||
'currency' => $options['currency'],
|
||||
]);
|
||||
}
|
||||
|
||||
if ($showTime) {
|
||||
$builder->add('timeBudget', DurationType::class, [
|
||||
'empty_data' => 0,
|
||||
'label' => 'label.timeBudget',
|
||||
'icon' => 'clock',
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($showBudget) {
|
||||
$builder->add('budgetType', BudgetType::class);
|
||||
}
|
||||
|
||||
$builder->add('metaFields', MetaFieldsCollectionType::class);
|
||||
|
||||
@@ -112,6 +112,7 @@ class ProjectEditForm extends AbstractType
|
||||
'currency' => Customer::DEFAULT_CURRENCY,
|
||||
'date_format' => null,
|
||||
'include_budget' => false,
|
||||
'include_time' => false,
|
||||
'timezone' => date_default_timezone_get(),
|
||||
'time_increment' => 1,
|
||||
'attr' => [
|
||||
|
||||
@@ -42,25 +42,28 @@ final class ReportingService
|
||||
$event = new ReportingEvent($user);
|
||||
|
||||
if ($this->security->isGranted('view_reporting')) {
|
||||
$showBudget = $this->security->isGranted('budget_any', 'project');
|
||||
$details = $this->security->isGranted('details', 'project');
|
||||
$viewOther = $this->security->isGranted('view_other_reporting') && $this->security->isGranted('view_other_timesheet');
|
||||
$event->addReport(new Report('week_by_user', 'report_user_week', 'report_user_week', 'user'));
|
||||
$event->addReport(new Report('month_by_user', 'report_user_month', 'report_user_month', 'user'));
|
||||
$event->addReport(new Report('year_by_user', 'report_user_year', 'report_user_year', 'user'));
|
||||
if ($this->security->isGranted('view_other_reporting') && $this->security->isGranted('view_other_timesheet')) {
|
||||
if ($viewOther) {
|
||||
$event->addReport(new Report('weekly_users_list', 'report_weekly_users', 'report_weekly_users', 'users'));
|
||||
$event->addReport(new Report('monthly_users_list', 'report_monthly_users', 'report_monthly_users', 'users'));
|
||||
$event->addReport(new Report('yearly_users_list', 'report_yearly_users', 'report_yearly_users', 'users'));
|
||||
}
|
||||
if ($this->security->isGranted('budget_project')) {
|
||||
if ($showBudget) {
|
||||
$event->addReport(new Report('project_view', 'report_project_view', 'report_project_view', 'project'));
|
||||
}
|
||||
if ($this->security->isGranted('details_project') || $this->security->isGranted('details_teamlead_project') || $this->security->isGranted('details_team_project')) {
|
||||
if ($details) {
|
||||
$event->addReport(new Report('project_details', 'report_project_details', 'report_project_details', 'project'));
|
||||
}
|
||||
if ($this->security->isGranted('budget_project')) {
|
||||
if ($showBudget) {
|
||||
$event->addReport(new Report('daterange_projects', 'report_project_daterange', 'report_project_daterange', 'project'));
|
||||
$event->addReport(new Report('inactive_projects', 'report_project_inactive', 'report_inactive_project', 'project'));
|
||||
}
|
||||
if ($this->security->isGranted('view_other_reporting') && $this->security->isGranted('view_other_timesheet')) {
|
||||
if ($viewOther) {
|
||||
$event->addReport(new Report('report_customer_monthly_projects', 'report_customer_monthly_projects', 'report_customer_monthly_projects', 'customer'));
|
||||
}
|
||||
|
||||
|
||||
@@ -14,4 +14,5 @@ final class TimesheetBudgetUsed extends TimesheetConstraint
|
||||
// same messages, so we can re-use the validation translation!
|
||||
public $messageRate = 'The budget is completely used.';
|
||||
public $messageTime = 'The budget is completely used.';
|
||||
public $messagePermission = 'Sorry, the budget is used up.';
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ use App\Timesheet\RateServiceInterface;
|
||||
use App\Utils\Duration;
|
||||
use App\Utils\LocaleHelper;
|
||||
use DateTime;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
@@ -32,8 +33,9 @@ final class TimesheetBudgetUsedValidator extends ConstraintValidator
|
||||
private $timesheetRepository;
|
||||
private $rateService;
|
||||
private $configuration;
|
||||
private $security;
|
||||
|
||||
public function __construct(SystemConfiguration $configuration, CustomerStatisticService $customerStatisticService, ProjectStatisticService $projectStatisticService, ActivityStatisticService $activityStatisticService, TimesheetRepository $timesheetRepository, RateServiceInterface $rateService)
|
||||
public function __construct(SystemConfiguration $configuration, CustomerStatisticService $customerStatisticService, ProjectStatisticService $projectStatisticService, ActivityStatisticService $activityStatisticService, TimesheetRepository $timesheetRepository, RateServiceInterface $rateService, AuthorizationCheckerInterface $security)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
$this->customerStatisticService = $customerStatisticService;
|
||||
@@ -41,6 +43,7 @@ final class TimesheetBudgetUsedValidator extends ConstraintValidator
|
||||
$this->activityStatisticService = $activityStatisticService;
|
||||
$this->timesheetRepository = $timesheetRepository;
|
||||
$this->rateService = $rateService;
|
||||
$this->security = $security;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,7 +184,12 @@ final class TimesheetBudgetUsedValidator extends ConstraintValidator
|
||||
$free = $budget - $rate;
|
||||
$free = max($free, 0);
|
||||
|
||||
$this->context->buildViolation($constraint->messageRate)
|
||||
$message = $constraint->messageRate;
|
||||
if (!$this->security->isGranted('budget_money', $field)) {
|
||||
$message = $constraint->messagePermission;
|
||||
}
|
||||
|
||||
$this->context->buildViolation($message)
|
||||
->atPath($field)
|
||||
->setTranslationDomain('validators')
|
||||
->setParameters([
|
||||
@@ -200,7 +208,12 @@ final class TimesheetBudgetUsedValidator extends ConstraintValidator
|
||||
$free = $budget - $duration;
|
||||
$free = max($free, 0);
|
||||
|
||||
$this->context->buildViolation($constraint->messageTime)
|
||||
$message = $constraint->messageTime;
|
||||
if (!$this->security->isGranted('budget_time', $field)) {
|
||||
$message = $constraint->messagePermission;
|
||||
}
|
||||
|
||||
$this->context->buildViolation($message)
|
||||
->atPath($field)
|
||||
->setTranslationDomain('validators')
|
||||
->setParameters([
|
||||
|
||||
@@ -28,6 +28,7 @@ final class ActivityVoter extends Voter
|
||||
'view',
|
||||
'edit',
|
||||
'budget',
|
||||
'time',
|
||||
'delete',
|
||||
'permissions',
|
||||
];
|
||||
|
||||
@@ -29,6 +29,7 @@ final class CustomerVoter extends Voter
|
||||
'create',
|
||||
'edit',
|
||||
'budget',
|
||||
'time',
|
||||
'delete',
|
||||
'permissions',
|
||||
'comments',
|
||||
|
||||
121
src/Voter/EntityMultiRoleVoter.php
Normal file
121
src/Voter/EntityMultiRoleVoter.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?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\Activity;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\Project;
|
||||
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 EntityMultiRoleVoter extends Voter
|
||||
{
|
||||
/**
|
||||
* support rules based on the given activity
|
||||
*/
|
||||
private const ALLOWED_ATTRIBUTES = [
|
||||
'budget_money',
|
||||
'budget_time',
|
||||
'budget_any',
|
||||
'details',
|
||||
];
|
||||
private const ALLOWED_SUBJECTS = [
|
||||
'customer',
|
||||
'project',
|
||||
'activity',
|
||||
];
|
||||
|
||||
private $permissionManager;
|
||||
|
||||
public function __construct(RolePermissionManager $permissionManager)
|
||||
{
|
||||
$this->permissionManager = $permissionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $attribute
|
||||
* @param Activity|Project|Customer|string $subject
|
||||
* @return bool
|
||||
*/
|
||||
protected function supports($attribute, $subject)
|
||||
{
|
||||
if (!\in_array($attribute, self::ALLOWED_ATTRIBUTES)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (\is_string($subject) && \in_array($subject, self::ALLOWED_SUBJECTS)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($subject instanceof Activity || $subject instanceof Project || $subject instanceof Customer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $attribute
|
||||
* @param Activity|Project|Customer|string $subject
|
||||
* @param TokenInterface $token
|
||||
* @return bool
|
||||
*/
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
||||
{
|
||||
$user = $token->getUser();
|
||||
|
||||
if (!$user instanceof User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$suffix = null;
|
||||
|
||||
if (\is_string($subject) && \in_array($subject, self::ALLOWED_SUBJECTS)) {
|
||||
$suffix = $subject;
|
||||
} elseif ($subject instanceof Activity) {
|
||||
$suffix = 'activity';
|
||||
} elseif ($subject instanceof Project) {
|
||||
$suffix = 'project';
|
||||
} elseif ($subject instanceof Customer) {
|
||||
$suffix = 'customer';
|
||||
}
|
||||
|
||||
if ($suffix === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$permissions = [];
|
||||
|
||||
if ($attribute === 'details') {
|
||||
$permissions[] = 'details';
|
||||
}
|
||||
|
||||
if ($attribute === 'budget_money' || $attribute === 'budget_any') {
|
||||
$permissions[] = 'budget';
|
||||
$permissions[] = 'budget_teamlead';
|
||||
$permissions[] = 'budget_team';
|
||||
}
|
||||
|
||||
if ($attribute === 'budget_time' || $attribute === 'budget_any') {
|
||||
$permissions[] = 'time';
|
||||
$permissions[] = 'time_teamlead';
|
||||
$permissions[] = 'time_team';
|
||||
}
|
||||
foreach ($permissions as $permission) {
|
||||
if ($this->permissionManager->hasRolePermission($user, $permission . '_' . $suffix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ final class ProjectVoter extends Voter
|
||||
'view',
|
||||
'edit',
|
||||
'budget',
|
||||
'time',
|
||||
'delete',
|
||||
'permissions',
|
||||
'comments',
|
||||
|
||||
@@ -83,7 +83,10 @@ class UserTeamProjects extends SimpleWidget implements AuthorizedWidget, UserWid
|
||||
*/
|
||||
public function getPermissions(): array
|
||||
{
|
||||
return ['budget_team_project', 'budget_teamlead_project', 'budget_project'];
|
||||
return [
|
||||
'budget_team_project', 'budget_teamlead_project', 'budget_project',
|
||||
'time_team_project', 'time_teamlead_project', 'time_project',
|
||||
];
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
|
||||
Reference in New Issue
Block a user