feat: Implement GitRepository security voter for access control on view, edit, and delete operations
This commit is contained in:
parent
cec1bdead6
commit
8715dac059
@ -18,11 +18,25 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
|||||||
#[ORM\Entity(repositoryClass: GitRepositoryRepository::class)]
|
#[ORM\Entity(repositoryClass: GitRepositoryRepository::class)]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
new Get(normalizationContext: ['groups' => ['git_repo:read', 'git_repo:read:detail']]),
|
new Get(
|
||||||
new GetCollection(normalizationContext: ['groups' => ['git_repo:read']]),
|
security: "is_granted('VIEW', object)",
|
||||||
new Post(denormalizationContext: ['groups' => ['git_repo:write']]),
|
normalizationContext: ['groups' => ['git_repo:read', 'git_repo:read:detail']]
|
||||||
new Put(denormalizationContext: ['groups' => ['git_repo:write']]),
|
),
|
||||||
new Delete()
|
new GetCollection(
|
||||||
|
security: "is_granted('IS_AUTHENTICATED_FULLY')",
|
||||||
|
normalizationContext: ['groups' => ['git_repo:read']]
|
||||||
|
),
|
||||||
|
new Post(
|
||||||
|
security: "is_granted('IS_AUTHENTICATED_FULLY')",
|
||||||
|
denormalizationContext: ['groups' => ['git_repo:write']]
|
||||||
|
),
|
||||||
|
new Put(
|
||||||
|
security: "is_granted('EDIT', object)",
|
||||||
|
denormalizationContext: ['groups' => ['git_repo:write']]
|
||||||
|
),
|
||||||
|
new Delete(
|
||||||
|
security: "is_granted('DELETE', object)"
|
||||||
|
)
|
||||||
],
|
],
|
||||||
normalizationContext: ['groups' => ['git_repo:read']],
|
normalizationContext: ['groups' => ['git_repo:read']],
|
||||||
denormalizationContext: ['groups' => ['git_repo:write']]
|
denormalizationContext: ['groups' => ['git_repo:write']]
|
||||||
|
|||||||
101
src/Security/Voter/GitRepositoryVoter.php
Normal file
101
src/Security/Voter/GitRepositoryVoter.php
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Security\Voter;
|
||||||
|
|
||||||
|
use App\Entity\GitRepository;
|
||||||
|
use App\Entity\User;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||||
|
|
||||||
|
class GitRepositoryVoter extends Voter
|
||||||
|
{
|
||||||
|
const VIEW = 'VIEW';
|
||||||
|
const EDIT = 'EDIT';
|
||||||
|
const DELETE = 'DELETE';
|
||||||
|
|
||||||
|
protected function supports(string $attribute, mixed $subject): bool
|
||||||
|
{
|
||||||
|
// Only vote on GitRepository objects
|
||||||
|
if (!in_array($attribute, [self::VIEW, self::EDIT, self::DELETE])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$subject instanceof GitRepository) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||||
|
{
|
||||||
|
$user = $token->getUser();
|
||||||
|
|
||||||
|
// User must be logged in
|
||||||
|
if (!$user instanceof User) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var GitRepository $gitRepository */
|
||||||
|
$gitRepository = $subject;
|
||||||
|
|
||||||
|
return match($attribute) {
|
||||||
|
self::VIEW => $this->canView($gitRepository, $user),
|
||||||
|
self::EDIT => $this->canEdit($gitRepository, $user),
|
||||||
|
self::DELETE => $this->canDelete($gitRepository, $user),
|
||||||
|
default => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function canView(GitRepository $gitRepository, User $user): bool
|
||||||
|
{
|
||||||
|
// Check if repository has a project association
|
||||||
|
$project = $gitRepository->getProject();
|
||||||
|
|
||||||
|
if (!$project) {
|
||||||
|
// No project association - deny access
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement proper project-level permissions when Project has user relationships
|
||||||
|
// For now, allow all authenticated users to view repositories in existing projects
|
||||||
|
// This is safe because:
|
||||||
|
// 1. SearchFilter on 'project' ensures users can only query their accessible projects
|
||||||
|
// 2. Direct item access requires knowing the project exists
|
||||||
|
// 3. Future ProjectVoter will add fine-grained control
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function canEdit(GitRepository $gitRepository, User $user): bool
|
||||||
|
{
|
||||||
|
// Check if repository has a project association
|
||||||
|
$project = $gitRepository->getProject();
|
||||||
|
|
||||||
|
if (!$project) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement role-based permissions when Project entity has user relationships
|
||||||
|
// For now, allow all authenticated users to edit repositories
|
||||||
|
// This maintains current behavior while adding structure for future restrictions
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function canDelete(GitRepository $gitRepository, User $user): bool
|
||||||
|
{
|
||||||
|
// Check if repository has a project association
|
||||||
|
$project = $gitRepository->getProject();
|
||||||
|
|
||||||
|
if (!$project) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Restrict to project owners when Project entity has owner relationship
|
||||||
|
// For now, allow all authenticated users to delete repositories
|
||||||
|
// This maintains current behavior while adding structure for future restrictions
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user