refs #421. Some improvements

pull/7/head
Manuel Aranda Rosales 2024-06-21 10:03:59 +02:00
parent 14c9f44c63
commit 5d1b553c0b
11 changed files with 192 additions and 14 deletions

View File

@ -11,8 +11,8 @@ resources:
ApiPlatform\Metadata\GetCollection:
provider: App\State\Provider\OperativeSystemProvider
filters:
- 'api_platform.filter.operative-system.order'
- 'api_platform.filter.operative-system.search'
- 'api_platform.filter.operative_system.order'
- 'api_platform.filter.operative_system.search'
ApiPlatform\Metadata\Get:
provider: App\State\Provider\OperativeSystemProvider

View File

@ -7,13 +7,16 @@ resources:
groups: ['default', 'organizational-unit:read']
denormalization_context:
groups: ['organizational-unit:write']
validation_context:
groups: ['organizational-unit:write']
operations:
ApiPlatform\Metadata\GetCollection:
provider: App\State\Provider\OrganizationalUnitProvider
filters:
- 'api_platform.filter.organizational_unit.order'
- 'api_platform.filter.organizational_unit.search'
ApiPlatform\Metadata\Get:
security: 'is_granted("ORGANIZATIONAL_UNIT_VIEW", object)'
provider: App\State\Provider\OrganizationalUnitProvider
securityMessage: 'Sorry, but you are not allowed to access this resource.'
ApiPlatform\Metadata\Put:
provider: App\State\Provider\OrganizationalUnitProvider
ApiPlatform\Metadata\Patch:

View File

@ -4,7 +4,14 @@ api_platform:
version: 1.0.0
path_segment_name_generator: 'api_platform.path_segment_name_generator.dash'
formats:
jsonld: ['application/ld+json', 'application/json']
jsonld: [ 'application/ld+json' ]
jsonhal: [ 'application/hal+json' ]
jsonapi: [ 'application/vnd.api+json' ]
json: [ 'application/json' ]
xml: [ 'application/xml', 'text/xml' ]
yaml: [ 'application/x-yaml' ]
csv: [ 'text/csv' ]
html: [ 'text/html' ]
patch_formats:
jsonld: ['application/ld+json', 'application/json']
mapping:

View File

@ -1,8 +1,3 @@
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
services:
@ -11,8 +6,6 @@ services:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
@ -20,6 +13,11 @@ services:
- '../src/Entity/'
- '../src/Kernel.php'
App\Doctrine\UserAllowedOrganizationalUnitExtension:
tags:
- { name: api_platform.doctrine.orm.query_extension.collection }
- { name: api_platform.doctrine.orm.query_extension.item }
App\OpenApi\OpenApiFactory:
decorates: 'api_platform.openapi.factory'
arguments: [ '@App\OpenApi\OpenApiFactory.inner' ]

View File

@ -41,6 +41,19 @@ services:
tags:
- [ 'api_platform.filter' ]
api_platform.filter.organizational_unit.order:
parent: 'api_platform.doctrine.orm.order_filter'
arguments:
$properties: { 'id': ~, 'name': ~, 'type': ~ }
$orderParameterName: 'order'
tags:
- [ 'api_platform.filter' ]
api_platform.filter.organizational_unit.search:
parent: 'api_platform.doctrine.orm.search_filter'
arguments: [ { 'id': 'exact', 'name': 'exact', 'type': 'exact' } ]
tags:
- [ 'api_platform.filter' ]
api_platform.filter.partition.order:
parent: 'api_platform.doctrine.orm.order_filter'

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240620095914 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE `partition` DROP FOREIGN KEY FK_9EB910E419EB6921');
$this->addSql('ALTER TABLE `partition` ADD CONSTRAINT FK_9EB910E419EB6921 FOREIGN KEY (client_id) REFERENCES client (id) ON DELETE SET NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE `partition` DROP FOREIGN KEY FK_9EB910E419EB6921');
$this->addSql('ALTER TABLE `partition` ADD CONSTRAINT FK_9EB910E419EB6921 FOREIGN KEY (client_id) REFERENCES client (id)');
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240620100039 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE `partition` DROP FOREIGN KEY FK_9EB910E419EB6921');
$this->addSql('ALTER TABLE `partition` ADD CONSTRAINT FK_9EB910E419EB6921 FOREIGN KEY (client_id) REFERENCES client (id) ON DELETE CASCADE');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE `partition` DROP FOREIGN KEY FK_9EB910E419EB6921');
$this->addSql('ALTER TABLE `partition` ADD CONSTRAINT FK_9EB910E419EB6921 FOREIGN KEY (client_id) REFERENCES client (id) ON DELETE SET NULL');
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Doctrine;
use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use App\Entity\OrganizationalUnit;
use App\Entity\User;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bundle\SecurityBundle\Security;
readonly class UserAllowedOrganizationalUnitExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
{
public function __construct(
private Security $security,
)
{
}
public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void
{
$this->addWhere($queryBuilder, $resourceClass);
}
public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, Operation $operation = null, array $context = []): void
{
$this->addWhere($queryBuilder, $resourceClass);
}
private function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
{
/** @var User $user */
$user = $this->security->getUser();
if (OrganizationalUnit::class !== $resourceClass || null === $user || in_array('ROLE_SUPER_ADMIN', $user->getRoles())) {
return;
}
$organizationalUnitIds = [];
foreach ($user->getAllowedOrganizationalUnits() as $allowedOrganizationalUnit) {
$organizationalUnitIds[] = $allowedOrganizationalUnit->getId();
}
$rootAlias = $queryBuilder->getRootAliases()[0];
$queryBuilder->andWhere(sprintf('%s.id in (:ou)', $rootAlias));
$queryBuilder->setParameter('ou', $organizationalUnitIds);
}
}

View File

@ -28,6 +28,7 @@ class Partition extends AbstractEntity
private ?string $filesystem = null;
#[ORM\ManyToOne(inversedBy: 'partitions')]
#[ORM\JoinColumn( onDelete: 'CASCADE')]
private ?Client $client = null;
#[ORM\Column]

View File

@ -17,8 +17,8 @@ class User extends AbstractEntity implements UserInterface, PasswordAuthenticate
{
use ToggleableTrait;
const ROLE_SUPER_ADMIN = 'ROLE_SUPER_ADMIN';
const ROLE_USER = 'ROLE_USER';
const string ROLE_SUPER_ADMIN = 'ROLE_SUPER_ADMIN';
const string ROLE_USER = 'ROLE_USER';
#[ORM\Column(length: 180)]
private ?string $username = null;

View File

@ -0,0 +1,42 @@
<?php
namespace App\Security\Voter;
use App\Dto\Output\OrganizationalUnitOutput;
use App\Entity\OrganizationalUnit;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;
class OrganizationalUnitVoter extends Voter
{
public const string VIEW = 'ORGANIZATIONAL_UNIT_VIEW';
protected function supports(string $attribute, mixed $subject): bool
{
return $attribute === self::VIEW
&& $subject instanceof OrganizationalUnitOutput;
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
/** @var User $user */
$user = $token->getUser();
// if the user is anonymous, do not grant access
if (!$user instanceof UserInterface) {
return false;
}
if ($attribute === 'ORGANIZATIONAL_UNIT_VIEW' ) {
foreach ($user->getAllowedOrganizationalUnits() as $allowedOrganizationalUnit) {
if ($allowedOrganizationalUnit->getId() === $subject->getEntity()->getId()) {
return true;
}
}
}
return false;
}
}