Team API docs (#5897)

* let view_team permission be handled by global ACLs
* code style and API docs
* improve permission check
This commit is contained in:
Kevin Papst
2026-04-11 17:22:59 +02:00
committed by GitHub
parent 999d820d4c
commit cbdf91f316
5 changed files with 68 additions and 91 deletions

View File

@@ -71,7 +71,7 @@ final class TeamController extends BaseApiController
* Fetch team
*/
#[IsGranted('view_team')]
#[OA\Response(response: 200, description: 'Returns one team entity', content: new OA\JsonContent(ref: '#/components/schemas/Team'))]
#[OA\Response(response: 200, description: 'Returns the team', content: new OA\JsonContent(ref: '#/components/schemas/Team'))]
#[Route(methods: ['GET'], path: '/{id}', name: 'get_team', requirements: ['id' => '\d+'])]
public function getAction(Team $team): Response
{
@@ -84,8 +84,8 @@ final class TeamController extends BaseApiController
/**
* Delete team
*/
#[IsGranted('delete_team')]
#[OA\Delete(responses: [new OA\Response(response: 204, description: 'Delete one team')])]
#[IsGranted('delete', 'team')]
#[OA\Delete(responses: [new OA\Response(response: 204, description: 'Empty')])]
#[OA\Parameter(name: 'id', in: 'path', description: 'Team ID to delete', required: true)]
#[Route(methods: ['DELETE'], path: '/{id}', name: 'delete_team', requirements: ['id' => '\d+'])]
public function deleteAction(Team $team): Response
@@ -129,7 +129,7 @@ final class TeamController extends BaseApiController
/**
* Update team
*/
#[IsGranted('edit_team')]
#[IsGranted('edit', 'team')]
#[OA\Patch(description: 'Update an existing team, you can pass all or just a subset of all attributes (passing members will replace all existing ones)', responses: [new OA\Response(response: 200, description: 'Returns the updated team', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[OA\RequestBody(required: true, content: new OA\JsonContent(ref: '#/components/schemas/TeamEditForm'))]
#[OA\Parameter(name: 'id', in: 'path', description: 'Team ID to update', required: true)]
@@ -169,7 +169,7 @@ final class TeamController extends BaseApiController
/**
* Add team member
*/
#[IsGranted('edit_team')]
#[IsGranted('edit', 'team')]
#[OA\Post(responses: [new OA\Response(response: 200, description: 'Adds a new user to a team.', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[OA\Parameter(name: 'id', in: 'path', description: 'The team which will receive the new member', required: true)]
#[OA\Parameter(name: 'userId', in: 'path', description: 'The team member to add (User ID)', required: true)]
@@ -193,7 +193,7 @@ final class TeamController extends BaseApiController
/**
* Remove team member
*/
#[IsGranted('edit_team')]
#[IsGranted('edit', 'team')]
#[OA\Delete(responses: [new OA\Response(response: 200, description: 'Removes a user from the team. The teamlead cannot be removed.', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[OA\Parameter(name: 'id', in: 'path', description: 'The team from which the member will be removed', required: true)]
#[OA\Parameter(name: 'userId', in: 'path', description: 'The team member to remove (User ID)', required: true)]
@@ -219,10 +219,12 @@ final class TeamController extends BaseApiController
}
/**
* Grant team access to customer
* Grant customer access
*
* The team is granted access to the customer.
*/
#[IsGranted('edit_team')]
#[OA\Post(responses: [new OA\Response(response: 200, description: 'Adds a new customer to a team.', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[IsGranted('edit', 'team')]
#[OA\Post(responses: [new OA\Response(response: 200, description: 'Returns the team including the customer', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[OA\Parameter(name: 'id', in: 'path', description: 'The team that is granted access', required: true)]
#[OA\Parameter(name: 'customerId', in: 'path', description: 'The customer to grant acecess to (Customer ID)', required: true)]
#[Route(methods: ['POST'], path: '/{id}/customers/{customerId}', name: 'post_team_customer', requirements: ['id' => '\d+', 'customerId' => '\d+'])]
@@ -242,10 +244,12 @@ final class TeamController extends BaseApiController
}
/**
* Revoke customer access from team
* Revoke customer access
*
* This removes access to the customer from the team.
*/
#[IsGranted('edit_team')]
#[OA\Delete(responses: [new OA\Response(response: 200, description: 'Removes a customer from the team.', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[IsGranted('edit', 'team')]
#[OA\Delete(responses: [new OA\Response(response: 200, description: 'Returns the team without the customer', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[OA\Parameter(name: 'id', in: 'path', description: 'The team whose permission will be revoked', required: true)]
#[OA\Parameter(name: 'customerId', in: 'path', description: 'The customer to remove (Customer ID)', required: true)]
#[Route(methods: ['DELETE'], path: '/{id}/customers/{customerId}', name: 'delete_team_customer', requirements: ['id' => '\d+', 'customerId' => '\d+'])]
@@ -265,10 +269,12 @@ final class TeamController extends BaseApiController
}
/**
* Grant team access to project
* Grant project access
*
* The team is granted access to the project.
*/
#[IsGranted('edit_team')]
#[OA\Post(responses: [new OA\Response(response: 200, description: 'Adds a new project to a team.', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[IsGranted('edit', 'team')]
#[OA\Post(responses: [new OA\Response(response: 200, description: 'Returns the team including the project', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[OA\Parameter(name: 'id', in: 'path', description: 'The team that is granted access', required: true)]
#[OA\Parameter(name: 'projectId', in: 'path', description: 'The project to grant acecess to (Project ID)', required: true)]
#[Route(methods: ['POST'], path: '/{id}/projects/{projectId}', name: 'post_team_project', requirements: ['id' => '\d+', 'projectId' => '\d+'])]
@@ -288,10 +294,12 @@ final class TeamController extends BaseApiController
}
/**
* Revoke project access from team
* Revoke project access
*
* This removes access to the project from the team.
*/
#[IsGranted('edit_team')]
#[OA\Delete(responses: [new OA\Response(response: 200, description: 'Removes a project from the team.', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[IsGranted('edit', 'team')]
#[OA\Delete(responses: [new OA\Response(response: 200, description: 'Returns the team without the project', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[OA\Parameter(name: 'id', in: 'path', description: 'The team whose permission will be revoked', required: true)]
#[OA\Parameter(name: 'projectId', in: 'path', description: 'The project to remove (Project ID)', required: true)]
#[Route(methods: ['DELETE'], path: '/{id}/projects/{projectId}', name: 'delete_team_project', requirements: ['id' => '\d+', 'projectId' => '\d+'])]
@@ -311,10 +319,12 @@ final class TeamController extends BaseApiController
}
/**
* Grant team access to activity
* Grant activity access
*
* The team is granted access to the activity.
*/
#[IsGranted('edit_team')]
#[OA\Post(responses: [new OA\Response(response: 200, description: 'Adds a new activity to a team.', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[IsGranted('edit', 'team')]
#[OA\Post(responses: [new OA\Response(response: 200, description: 'Returns the team including the activity', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[OA\Parameter(name: 'id', in: 'path', description: 'The team that is granted access', required: true)]
#[OA\Parameter(name: 'activityId', in: 'path', description: 'The activity to grant acecess to (Activity ID)', required: true)]
#[Route(methods: ['POST'], path: '/{id}/activities/{activityId}', name: 'post_team_activity', requirements: ['id' => '\d+', 'activityId' => '\d+'])]
@@ -334,10 +344,12 @@ final class TeamController extends BaseApiController
}
/**
* Revoke activity access from team
* Revoke activity access
*
* This removes access to the activity from the team.
*/
#[IsGranted('edit_team')]
#[OA\Delete(responses: [new OA\Response(response: 200, description: 'Removes a activity from the team.', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[IsGranted('edit', 'team')]
#[OA\Delete(responses: [new OA\Response(response: 200, description: 'Returns the team without the activity', content: new OA\JsonContent(ref: '#/components/schemas/Team'))])]
#[OA\Parameter(name: 'id', in: 'path', description: 'The team whose permission will be revoked', required: true)]
#[OA\Parameter(name: 'activityId', in: 'path', description: 'The activity to remove (Activity ID)', required: true)]
#[Route(methods: ['DELETE'], path: '/{id}/activities/{activityId}', name: 'delete_team_activity', requirements: ['id' => '\d+', 'activityId' => '\d+'])]

View File

@@ -38,15 +38,9 @@ final class TeamController extends AbstractController
{
}
/**
* @param TeamRepository $repository
* @param Request $request
* @param int $page
* @return Response
*/
#[Route(path: '/', defaults: ['page' => 1], name: 'admin_team', methods: ['GET'])]
#[Route(path: '/page/{page}', requirements: ['page' => '[1-9]\d*'], name: 'admin_team_paginated', methods: ['GET'])]
public function listTeams(TeamRepository $repository, Request $request, $page): Response
public function listTeams(int $page, TeamRepository $repository, Request $request): Response
{
$query = new TeamQuery();
$query->setPage($page);
@@ -81,10 +75,6 @@ final class TeamController extends AbstractController
]);
}
/**
* @param Request $request
* @return Response
*/
#[Route(path: '/create', name: 'admin_team_create', methods: ['GET', 'POST'])]
#[IsGranted('create_team')]
public function createTeam(Request $request): Response

View File

@@ -24,7 +24,6 @@ final class TeamVoter extends Voter
* support rules based on the given $subject (here: Team)
*/
private const ALLOWED_ATTRIBUTES = [
'view',
'edit',
'delete',
];
@@ -52,17 +51,13 @@ final class TeamVoter extends Voter
{
$user = $token->getUser();
if (!$user instanceof User) {
if (!$user instanceof User || !($subject instanceof Team)) {
return false;
}
switch ($attribute) {
case 'edit':
case 'delete':
// changing existing teams should be limited to admins and teamleads
if (!$user->isAdmin() && !$user->isSuperAdmin() && !$user->isTeamleadOf($subject)) {
return false;
}
// changing existing teams should be limited to admins and teamleads
if (!$user->isAdmin() && !$user->isSuperAdmin() && !$user->isTeamleadOf($subject)) {
return false;
}
return $this->permissionManager->hasRolePermission($user, $attribute . '_team');