added "api_access" permission for limiting API access (#4779)

This commit is contained in:
Kevin Papst
2024-04-13 17:27:51 +02:00
committed by GitHub
parent 345bb6601e
commit 9dc9c71a46
15 changed files with 105 additions and 19 deletions

View File

@@ -105,15 +105,16 @@ kimai:
TEAMS: ['view_team','create_team','edit_team','delete_team']
LOCKDOWN: ['lockdown_grace_timesheet','lockdown_override_timesheet']
REPORTING: ['view_reporting','view_other_reporting','project_reporting','customer_reporting']
EVERYONE: ['api_access']
# permissions which are deactivated, as these features are hidden for now
# brave users can try to activate them and be surprised what happens
REGISTER_BETA: []
# mapping a "role name" to an array of "set names"
maps:
ROLE_USER: ['TIMESHEET','PROFILE']
ROLE_TEAMLEAD: ['ACTIVITIES_TEAMLEAD','PROJECTS_TEAMLEAD','CUSTOMERS_TEAMLEAD','TIMESHEET_OTHER','INVOICE','TIMESHEET','PROFILE','EXPORT','BILLABLE','TAGS','REPORTING']
ROLE_ADMIN: ['ACTIVITIES','PROJECTS','CUSTOMERS','INVOICE','INVOICE_ADMIN','TIMESHEET','TIMESHEET_OTHER','PROFILE','TEAMS','RATE','RATE_OTHER','EXPORT','BILLABLE','TAGS','LOCKDOWN','REPORTING']
ROLE_SUPER_ADMIN: ['ACTIVITIES','PROJECTS','CUSTOMERS','INVOICE','INVOICE_ADMIN','TIMESHEET','TIMESHEET_OTHER','PROFILE','PROFILE_OTHER','USER','TEAMS','RATE','RATE_OTHER','EXPORT','BILLABLE','TAGS','LOCKDOWN','REPORTING']
ROLE_USER: ['TIMESHEET','PROFILE', 'EVERYONE']
ROLE_TEAMLEAD: ['ACTIVITIES_TEAMLEAD','PROJECTS_TEAMLEAD','CUSTOMERS_TEAMLEAD','TIMESHEET_OTHER','INVOICE','TIMESHEET','PROFILE','EXPORT','BILLABLE','TAGS','REPORTING', 'EVERYONE']
ROLE_ADMIN: ['ACTIVITIES','PROJECTS','CUSTOMERS','INVOICE','INVOICE_ADMIN','TIMESHEET','TIMESHEET_OTHER','PROFILE','TEAMS','RATE','RATE_OTHER','EXPORT','BILLABLE','TAGS','LOCKDOWN','REPORTING', 'EVERYONE']
ROLE_SUPER_ADMIN: ['ACTIVITIES','PROJECTS','CUSTOMERS','INVOICE','INVOICE_ADMIN','TIMESHEET','TIMESHEET_OTHER','PROFILE','PROFILE_OTHER','USER','TEAMS','RATE','RATE_OTHER','EXPORT','BILLABLE','TAGS','LOCKDOWN','REPORTING', 'EVERYONE']
# mapping a "role name" to an array of "permission names"
roles:
ROLE_USER: ['view_team_member','time_team_project','create_tag','view_reporting']

View File

@@ -20,6 +20,7 @@ security:
api:
access_token:
token_handler: App\API\Authentication\AccessTokenHandler
success_handler: App\API\Authentication\AccessTokenSuccessHandler
remember_me: false
request_matcher: App\API\Authentication\ApiRequestMatcher
user_checker: App\Security\UserChecker

View File

@@ -26,7 +26,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
#[Route(path: '/actions')]
#[IsGranted('IS_AUTHENTICATED')]
#[IsGranted('API')]
#[OA\Tag(name: 'Actions')]
final class ActionsController extends BaseApiController
{

View File

@@ -33,7 +33,7 @@ use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route(path: '/activities')]
#[IsGranted('IS_AUTHENTICATED')]
#[IsGranted('API')]
#[OA\Tag(name: 'Activity')]
final class ActivityController extends BaseApiController
{

View File

@@ -0,0 +1,25 @@
<?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\API\Authentication;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
final class AccessTokenSuccessHandler implements AuthenticationSuccessHandlerInterface
{
public function onAuthenticationSuccess(Request $request, TokenInterface $token): ?Response
{
$token->setAttribute('api-token', true);
return null;
}
}

View File

@@ -19,11 +19,11 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('IS_AUTHENTICATED')]
#[IsGranted('API')]
#[OA\Tag(name: 'Default')]
final class ConfigurationController extends BaseApiController
{
public function __construct(private ViewHandlerInterface $viewHandler)
public function __construct(private readonly ViewHandlerInterface $viewHandler)
{
}

View File

@@ -33,7 +33,7 @@ use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route(path: '/customers')]
#[IsGranted('IS_AUTHENTICATED')]
#[IsGranted('API')]
#[OA\Tag(name: 'Customer')]
final class CustomerController extends BaseApiController
{

View File

@@ -35,7 +35,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Validator\Constraints;
#[Route(path: '/projects')]
#[IsGranted('IS_AUTHENTICATED')]
#[IsGranted('API')]
#[OA\Tag(name: 'Project')]
final class ProjectController extends BaseApiController
{

View File

@@ -20,11 +20,11 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[IsGranted('IS_AUTHENTICATED')]
#[IsGranted('API')]
#[OA\Tag(name: 'Default')]
final class StatusController extends BaseApiController
{
public function __construct(private ViewHandlerInterface $viewHandler)
public function __construct(private readonly ViewHandlerInterface $viewHandler)
{
}

View File

@@ -23,7 +23,7 @@ use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route(path: '/tags')]
#[IsGranted('IS_AUTHENTICATED')]
#[IsGranted('API')]
#[OA\Tag(name: 'Tag')]
final class TagController extends BaseApiController
{
@@ -31,7 +31,10 @@ final class TagController extends BaseApiController
public const GROUPS_ENTITY = ['Default', 'Entity', 'Tag'];
public const GROUPS_FORM = ['Default', 'Entity', 'Tag'];
public function __construct(private ViewHandlerInterface $viewHandler, private TagRepository $repository)
public function __construct(
private readonly ViewHandlerInterface $viewHandler,
private readonly TagRepository $repository
)
{
}

View File

@@ -30,7 +30,7 @@ use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route(path: '/teams')]
#[IsGranted('IS_AUTHENTICATED')]
#[IsGranted('API')]
#[OA\Tag(name: 'Team')]
final class TeamController extends BaseApiController
{
@@ -38,7 +38,10 @@ final class TeamController extends BaseApiController
public const GROUPS_FORM = ['Default', 'Entity', 'Team', 'Team_Entity', 'Not_Expanded'];
public const GROUPS_COLLECTION = ['Default', 'Collection', 'Team'];
public function __construct(private ViewHandlerInterface $viewHandler, private TeamRepository $repository)
public function __construct(
private readonly ViewHandlerInterface $viewHandler,
private readonly TeamRepository $repository
)
{
}

View File

@@ -42,7 +42,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Validator\Constraints;
#[Route(path: '/timesheets')]
#[IsGranted('IS_AUTHENTICATED')]
#[IsGranted('API')]
#[OA\Tag(name: 'Timesheet')]
final class TimesheetController extends BaseApiController
{

View File

@@ -33,7 +33,7 @@ use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route(path: '/users')]
#[IsGranted('IS_AUTHENTICATED')]
#[IsGranted('API')]
#[OA\Tag(name: 'User')]
final class UserController extends BaseApiController
{

53
src/Voter/ApiVoter.php Normal file
View File

@@ -0,0 +1,53 @@
<?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;
/**
* A voter to check permissions on API.
*
* @extends Voter<string, null>
*/
final class ApiVoter extends Voter
{
public function __construct(private readonly RolePermissionManager $permissionManager)
{
}
public function supportsAttribute(string $attribute): bool
{
return $attribute === 'API';
}
public function supportsType(string $subjectType): bool
{
return $subjectType === 'null';
}
protected function supports(string $attribute, mixed $subject): bool
{
return $subject === null && $this->supportsAttribute($attribute);
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
return $this->permissionManager->hasRolePermission($user, 'api_access');
}
}

View File

@@ -34,7 +34,7 @@ class PermissionControllerTest extends ControllerBaseTest
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
$this->assertAccessIsGranted($client, '/admin/permissions');
$this->assertHasDataTable($client);
$this->assertDataTableRowCount($client, 'datatable_user_admin_permissions', 132);
$this->assertDataTableRowCount($client, 'datatable_user_admin_permissions', 133);
$this->assertPageActions($client, [
'create modal-ajax-form' => $this->createUrl('/admin/permissions/roles/create'),
]);