Skip to content

Commit

Permalink
added "api_access" permission for limiting API access (#4779)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinpapst committed Apr 13, 2024
1 parent 345bb66 commit 9dc9c71
Show file tree
Hide file tree
Showing 15 changed files with 105 additions and 19 deletions.
9 changes: 5 additions & 4 deletions config/packages/kimai.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down
1 change: 1 addition & 0 deletions config/packages/security.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/API/ActionsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
use Symfony\Contracts\Translation\TranslatorInterface;

#[Route(path: '/actions')]
#[IsGranted('IS_AUTHENTICATED')]
#[IsGranted('API')]
#[OA\Tag(name: 'Actions')]
final class ActionsController extends BaseApiController
{
Expand Down
2 changes: 1 addition & 1 deletion src/API/ActivityController.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[Route(path: '/activities')]
#[IsGranted('IS_AUTHENTICATED')]
#[IsGranted('API')]
#[OA\Tag(name: 'Activity')]
final class ActivityController extends BaseApiController
{
Expand Down
25 changes: 25 additions & 0 deletions src/API/Authentication/AccessTokenSuccessHandler.php
Original file line number Diff line number Diff line change
@@ -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;
}
}
4 changes: 2 additions & 2 deletions src/API/ConfigurationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
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)
{
}

Expand Down
2 changes: 1 addition & 1 deletion src/API/CustomerController.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[Route(path: '/customers')]
#[IsGranted('IS_AUTHENTICATED')]
#[IsGranted('API')]
#[OA\Tag(name: 'Customer')]
final class CustomerController extends BaseApiController
{
Expand Down
2 changes: 1 addition & 1 deletion src/API/ProjectController.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
use Symfony\Component\Validator\Constraints;

#[Route(path: '/projects')]
#[IsGranted('IS_AUTHENTICATED')]
#[IsGranted('API')]
#[OA\Tag(name: 'Project')]
final class ProjectController extends BaseApiController
{
Expand Down
4 changes: 2 additions & 2 deletions src/API/StatusController.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
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)
{
}

Expand Down
7 changes: 5 additions & 2 deletions src/API/TagController.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,18 @@
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[Route(path: '/tags')]
#[IsGranted('IS_AUTHENTICATED')]
#[IsGranted('API')]
#[OA\Tag(name: 'Tag')]
final class TagController extends BaseApiController
{
public const GROUPS_COLLECTION = ['Default', 'Collection', 'Tag'];
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
)
{
}

Expand Down
7 changes: 5 additions & 2 deletions src/API/TeamController.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,18 @@
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[Route(path: '/teams')]
#[IsGranted('IS_AUTHENTICATED')]
#[IsGranted('API')]
#[OA\Tag(name: 'Team')]
final class TeamController extends BaseApiController
{
public const GROUPS_ENTITY = ['Default', 'Entity', 'Team', 'Team_Entity', 'Not_Expanded'];
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
)
{
}

Expand Down
2 changes: 1 addition & 1 deletion src/API/TimesheetController.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
use Symfony\Component\Validator\Constraints;

#[Route(path: '/timesheets')]
#[IsGranted('IS_AUTHENTICATED')]
#[IsGranted('API')]
#[OA\Tag(name: 'Timesheet')]
final class TimesheetController extends BaseApiController
{
Expand Down
2 changes: 1 addition & 1 deletion src/API/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[Route(path: '/users')]
#[IsGranted('IS_AUTHENTICATED')]
#[IsGranted('API')]
#[OA\Tag(name: 'User')]
final class UserController extends BaseApiController
{
Expand Down
53 changes: 53 additions & 0 deletions src/Voter/ApiVoter.php
Original file line number Diff line number Diff line change
@@ -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');
}
}
2 changes: 1 addition & 1 deletion tests/Controller/PermissionControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function testPermissions(): void
$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'),
]);
Expand Down

0 comments on commit 9dc9c71

Please sign in to comment.