From 38f7355d4225a50fd9f1279f7c1d84106249606a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Melaine=20G=C3=A9rard?= Date: Wed, 22 Jan 2025 20:11:29 +0100 Subject: [PATCH] =?UTF-8?q?:sparkles:=20Ajout=20de=20la=20gestion=20des=20?= =?UTF-8?q?r=C3=B4les=20sur=20les=20dossiers=20parents=20+=20gestion=20des?= =?UTF-8?q?=20liens=20public=20si=20le=20dossier=20parent=20est=20reli?= =?UTF-8?q?=C3=A9=20au=20r=C3=B4le=20Visiteur=20(Mais=20pas=20possible=20d?= =?UTF-8?q?e=20lister=20les=20fichiers)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 24 +-- composer.lock | 38 ++-- migrations/Version20241229133017.php | 37 ---- migrations/Version20250116210156.php | 64 ------- migrations/Version20250122182101.php | 40 ++++ migrations/Version20250122182130.php | 31 ++++ migrations/Version20250122183447.php | 35 ++++ src/Command/CreateUserCommand.php | 9 +- src/Controller/AdminController.php | 1 - src/Controller/FilesController.php | 181 ++++++++++++++++++- src/Entity/ParentDirectory.php | 51 ++++++ src/Entity/User.php | 33 ++-- src/Enum/RoleEnum.php | 35 ++++ src/Repository/ParentDirectoryRepository.php | 43 +++++ templates/files/index.html.twig | 2 + 15 files changed, 472 insertions(+), 152 deletions(-) delete mode 100755 migrations/Version20241229133017.php delete mode 100644 migrations/Version20250116210156.php create mode 100644 migrations/Version20250122182101.php create mode 100644 migrations/Version20250122182130.php create mode 100644 migrations/Version20250122183447.php create mode 100644 src/Entity/ParentDirectory.php create mode 100644 src/Enum/RoleEnum.php create mode 100644 src/Repository/ParentDirectoryRepository.php diff --git a/composer.json b/composer.json index 18fe58e..173336a 100755 --- a/composer.json +++ b/composer.json @@ -7,12 +7,12 @@ "php": ">=8.2", "ext-ctype": "*", "ext-iconv": "*", - "doctrine/dbal": "^4.2.1", - "doctrine/doctrine-bundle": "^2.13.1", - "doctrine/doctrine-migrations-bundle": "^3.3.1", + "doctrine/dbal": "^4.2.2", + "doctrine/doctrine-bundle": "^2.13.2", + "doctrine/doctrine-migrations-bundle": "^3.4.0", "doctrine/orm": "^3.3.1", "league/flysystem": "^3.29.1", - "oneup/flysystem-bundle": "^4.12.3", + "oneup/flysystem-bundle": "^4.12.4", "phpdocumentor/reflection-docblock": "^5.6.1", "phpstan/phpdoc-parser": "^2.0", "symfony/apache-pack": "^1.0.1", @@ -50,7 +50,7 @@ "symfony/validator": "7.2.*", "symfony/web-link": "7.2.*", "symfony/yaml": "7.2.*", - "symfonycasts/reset-password-bundle": "^1.23", + "symfonycasts/reset-password-bundle": "^1.23.1", "symfonycasts/tailwind-bundle": "^0.6.1", "tales-from-a-dev/flowbite-bundle": "^0.7.1", "twig/extra-bundle": "^2.12|^3.18", @@ -109,16 +109,16 @@ } }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.68", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan-strict-rules": "^2.0", - "phpstan/phpstan-symfony": "^2.0", - "phpunit/phpunit": "^11.5.2", - "rector/rector": "^2.0", + "friendsofphp/php-cs-fixer": "^3.68.1", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan-strict-rules": "^2.0.3", + "phpstan/phpstan-symfony": "^2.0.2", + "phpunit/phpunit": "^11.5.3", + "rector/rector": "^2.0.7", "symfony/browser-kit": "7.2.*", "symfony/css-selector": "7.2.*", "symfony/debug-bundle": "7.2.*", - "symfony/maker-bundle": "^1.61", + "symfony/maker-bundle": "^1.62.1", "symfony/phpunit-bridge": "^7.2", "symfony/stopwatch": "7.2.*", "symfony/web-profiler-bundle": "7.2.*" diff --git a/composer.lock b/composer.lock index 5b188c0..d5a6b26 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d58d5009e09974e2358412fe10fa1005", + "content-hash": "6438b8b149fca5ad2f3c96aa06e93da2", "packages": [ { "name": "composer/semver", @@ -9371,16 +9371,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.1", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7" + "reference": "7d08f569e582ade182a375c366cbd896eccadd3a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", - "reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7d08f569e582ade182a375c366cbd896eccadd3a", + "reference": "7d08f569e582ade182a375c366cbd896eccadd3a", "shasum": "" }, "require": { @@ -9425,20 +9425,20 @@ "type": "github" } ], - "time": "2025-01-05T16:43:48+00:00" + "time": "2025-01-21T14:54:06+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "02277ce4b0dd03d74f15282064f8f027d1dec9cc" + "reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/02277ce4b0dd03d74f15282064f8f027d1dec9cc", - "reference": "02277ce4b0dd03d74f15282064f8f027d1dec9cc", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/8b88b5f818bfa301e0c99154ab622dace071c3ba", + "reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba", "shasum": "" }, "require": { @@ -9471,28 +9471,28 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.2" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.3" }, - "time": "2025-01-19T13:03:11+00:00" + "time": "2025-01-21T10:52:14+00:00" }, { "name": "phpstan/phpstan-symfony", - "version": "2.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-symfony.git", - "reference": "c08cd8e54a08d651bc402d304cfa161c3c3766c4" + "reference": "65f02c7e585f3c7372e42e14d3d87da034031553" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/c08cd8e54a08d651bc402d304cfa161c3c3766c4", - "reference": "c08cd8e54a08d651bc402d304cfa161c3c3766c4", + "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/65f02c7e585f3c7372e42e14d3d87da034031553", + "reference": "65f02c7e585f3c7372e42e14d3d87da034031553", "shasum": "" }, "require": { "ext-simplexml": "*", "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.0" + "phpstan/phpstan": "^2.1.2" }, "conflict": { "symfony/framework-bundle": "<3.0" @@ -9542,9 +9542,9 @@ "description": "Symfony Framework extensions and rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-symfony/issues", - "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.1" + "source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.2" }, - "time": "2025-01-04T13:58:15+00:00" + "time": "2025-01-21T18:57:07+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/migrations/Version20241229133017.php b/migrations/Version20241229133017.php deleted file mode 100755 index 6ed3c1e..0000000 --- a/migrations/Version20241229133017.php +++ /dev/null @@ -1,37 +0,0 @@ -addSql('CREATE TABLE "user" (id BLOB NOT NULL --(DC2Type:uuid) - , email VARCHAR(255) NOT NULL, roles CLOB NOT NULL --(DC2Type:json) - , password VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)'); - $this->addSql('CREATE TABLE messenger_messages (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, body CLOB NOT NULL, headers CLOB NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable) - , available_at DATETIME NOT NULL --(DC2Type:datetime_immutable) - , delivered_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable) - )'); - $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)'); - $this->addSql('CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)'); - $this->addSql('CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)'); - } - - public function down(Schema $schema): void - { - $this->addSql('DROP TABLE "user"'); - $this->addSql('DROP TABLE messenger_messages'); - } -} diff --git a/migrations/Version20250116210156.php b/migrations/Version20250116210156.php deleted file mode 100644 index 3332af8..0000000 --- a/migrations/Version20250116210156.php +++ /dev/null @@ -1,64 +0,0 @@ -addSql('CREATE TABLE reset_password_request (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, selector VARCHAR(20) NOT NULL, hashed_token VARCHAR(100) NOT NULL, requested_at DATETIME NOT NULL, expires_at DATETIME NOT NULL, user_id BLOB NOT NULL, CONSTRAINT FK_7CE748AA76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); - $this->addSql('CREATE INDEX IDX_7CE748AA76ED395 ON reset_password_request (user_id)'); - $this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, email, roles, password FROM user'); - $this->addSql('DROP TABLE user'); - $this->addSql('CREATE TABLE user (id BLOB NOT NULL, email VARCHAR(255) NOT NULL, roles CLOB NOT NULL, password VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); - $this->addSql('INSERT INTO user (id, email, roles, password) SELECT id, email, roles, password FROM __temp__user'); - $this->addSql('DROP TABLE __temp__user'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON user (email)'); - $this->addSql('CREATE TEMPORARY TABLE __temp__messenger_messages AS SELECT id, body, headers, queue_name, created_at, available_at, delivered_at FROM messenger_messages'); - $this->addSql('DROP TABLE messenger_messages'); - $this->addSql('CREATE TABLE messenger_messages (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, body CLOB NOT NULL, headers CLOB NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL, available_at DATETIME NOT NULL, delivered_at DATETIME DEFAULT NULL)'); - $this->addSql('INSERT INTO messenger_messages (id, body, headers, queue_name, created_at, available_at, delivered_at) SELECT id, body, headers, queue_name, created_at, available_at, delivered_at FROM __temp__messenger_messages'); - $this->addSql('DROP TABLE __temp__messenger_messages'); - $this->addSql('CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)'); - $this->addSql('CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)'); - $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('DROP TABLE reset_password_request'); - $this->addSql('CREATE TEMPORARY TABLE __temp__messenger_messages AS SELECT id, body, headers, queue_name, created_at, available_at, delivered_at FROM messenger_messages'); - $this->addSql('DROP TABLE messenger_messages'); - $this->addSql('CREATE TABLE messenger_messages (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, body CLOB NOT NULL, headers CLOB NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable) - , available_at DATETIME NOT NULL --(DC2Type:datetime_immutable) - , delivered_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable) - )'); - $this->addSql('INSERT INTO messenger_messages (id, body, headers, queue_name, created_at, available_at, delivered_at) SELECT id, body, headers, queue_name, created_at, available_at, delivered_at FROM __temp__messenger_messages'); - $this->addSql('DROP TABLE __temp__messenger_messages'); - $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)'); - $this->addSql('CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)'); - $this->addSql('CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)'); - $this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, email, roles, password FROM "user"'); - $this->addSql('DROP TABLE "user"'); - $this->addSql('CREATE TABLE "user" (id BLOB NOT NULL --(DC2Type:uuid) - , email VARCHAR(255) NOT NULL, roles CLOB NOT NULL --(DC2Type:json) - , password VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); - $this->addSql('INSERT INTO "user" (id, email, roles, password) SELECT id, email, roles, password FROM __temp__user'); - $this->addSql('DROP TABLE __temp__user'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)'); - } -} diff --git a/migrations/Version20250122182101.php b/migrations/Version20250122182101.php new file mode 100644 index 0000000..14dede1 --- /dev/null +++ b/migrations/Version20250122182101.php @@ -0,0 +1,40 @@ +addSql('CREATE TABLE reset_password_request (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, selector VARCHAR(20) NOT NULL, hashed_token VARCHAR(100) NOT NULL, requested_at DATETIME NOT NULL, expires_at DATETIME NOT NULL, user_id INTEGER NOT NULL, CONSTRAINT FK_7CE748AA76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('CREATE INDEX IDX_7CE748AA76ED395 ON reset_password_request (user_id)'); + $this->addSql('CREATE TABLE "user" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, email VARCHAR(255) NOT NULL, roles CLOB NOT NULL, password VARCHAR(255) NOT NULL)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)'); + $this->addSql('CREATE TABLE messenger_messages (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, body CLOB NOT NULL, headers CLOB NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL, available_at DATETIME NOT NULL, delivered_at DATETIME DEFAULT NULL)'); + $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)'); + $this->addSql('CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)'); + $this->addSql('CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE reset_password_request'); + $this->addSql('DROP TABLE "user"'); + $this->addSql('DROP TABLE messenger_messages'); + } +} diff --git a/migrations/Version20250122182130.php b/migrations/Version20250122182130.php new file mode 100644 index 0000000..13ce069 --- /dev/null +++ b/migrations/Version20250122182130.php @@ -0,0 +1,31 @@ +addSql('CREATE TABLE parent_directory (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, owner_role VARCHAR(255) NOT NULL)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE parent_directory'); + } +} diff --git a/migrations/Version20250122183447.php b/migrations/Version20250122183447.php new file mode 100644 index 0000000..43dbe80 --- /dev/null +++ b/migrations/Version20250122183447.php @@ -0,0 +1,35 @@ +addSql('ALTER TABLE user ADD COLUMN folder_role VARCHAR(255) NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, email, roles, password FROM "user"'); + $this->addSql('DROP TABLE "user"'); + $this->addSql('CREATE TABLE "user" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, email VARCHAR(255) NOT NULL, roles CLOB NOT NULL, password VARCHAR(255) NOT NULL)'); + $this->addSql('INSERT INTO "user" (id, email, roles, password) SELECT id, email, roles, password FROM __temp__user'); + $this->addSql('DROP TABLE __temp__user'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)'); + } +} diff --git a/src/Command/CreateUserCommand.php b/src/Command/CreateUserCommand.php index 6ead417..4de1627 100755 --- a/src/Command/CreateUserCommand.php +++ b/src/Command/CreateUserCommand.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Command; use App\Entity\User; +use App\Enum\RoleEnum; use App\Repository\UserRepository; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Console\Attribute\AsCommand; @@ -36,6 +37,7 @@ class CreateUserCommand extends Command $email = $io->ask('Email de l\'utilisateur'); $password = $io->askHidden('Mot de passe de l\'utilisateur'); $isAdmin = $io->confirm('Est-ce un administrateur ?'); + $folderRole = $io->choice('Rôle du dossier', array_map(static fn ($role) => $role->value, RoleEnum::cases()), RoleEnum::VISITEUR->value); try { $user = $this->userRepository->findOneBy(['email' => $email]); @@ -43,19 +45,18 @@ class CreateUserCommand extends Command $io->error('Un utilisateur existe déjà avec cet email'); return Command::FAILURE; } - $user = new User(); $user->setEmail($email); $user->setPassword($this->passwordHasher->hashPassword($user, $password)); $user->setRoles($isAdmin ? ['ROLE_ADMIN'] : ['ROLE_USER']); - $user->initId(); + $user->setFolderRole(RoleEnum::from($folderRole)); $this->entityManager->persist($user); $this->entityManager->flush(); $io->success('Utilisateur créé avec succès'); - } catch (\Exception) { - $io->error('Une erreur est survenue lors de la création de l\'utilisateur'); + } catch (\Exception $e) { + $io->error('Une erreur est survenue lors de la création de l\'utilisateur : ' . $e->getMessage()); } return Command::SUCCESS; diff --git a/src/Controller/AdminController.php b/src/Controller/AdminController.php index 05edad4..8d5f68d 100755 --- a/src/Controller/AdminController.php +++ b/src/Controller/AdminController.php @@ -59,7 +59,6 @@ class AdminController extends AbstractController if ($form->isSubmitted() && $form->isValid()) { $role = $form->get('role')->getData(); $user->setRoles([$role]); - $user->initId(); if ($form->has('plainPassword')) { $plainPassword = $form->get('plainPassword')->getData(); diff --git a/src/Controller/FilesController.php b/src/Controller/FilesController.php index d3b7a36..d1ec375 100755 --- a/src/Controller/FilesController.php +++ b/src/Controller/FilesController.php @@ -4,9 +4,14 @@ declare(strict_types=1); namespace App\Controller; +use App\Entity\ParentDirectory; +use App\Entity\User; +use App\Enum\RoleEnum; use App\Form\CreateDirectoryType; use App\Form\RenameType; use App\Form\UploadType; +use App\Repository\ParentDirectoryRepository; +use Doctrine\ORM\EntityManagerInterface; use League\Flysystem\Filesystem; use League\Flysystem\FilesystemException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -22,19 +27,37 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Http\Attribute\IsGranted; #[Route('/files', 'app_files_')] -#[IsGranted('ROLE_USER')] class FilesController extends AbstractController { + public function __construct( + private readonly EntityManagerInterface $entityManager, + private readonly ParentDirectoryRepository $parentDirectoryRepository + ) { + } + /** * @throws FilesystemException */ #[Route('/', name: 'index')] + #[IsGranted('ROLE_USER')] public function index(Filesystem $defaultAdapter, UrlGeneratorInterface $urlGenerator, #[MapQueryParameter('path')] string $path = ''): Response { $path = $this->normalizePath($path); + /** + * @var User $user + */ + $user = $this->getUser(); - if ('' !== $path && !$defaultAdapter->directoryExists($path)) { - throw $this->createNotFoundException("Ce dossier n'existe pas !"); + if ('' !== $path) { + $parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $path]); + + if (null === $parentDir || !$defaultAdapter->directoryExists($path)) { + throw $this->createNotFoundException("Ce dossier n'existe pas !"); + } + + if ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true)) { + throw $this->createNotFoundException("Vous n'avez pas le droit d'accéder à ce dossier !"); + } } $files = $defaultAdapter->listContents('/' . $path); @@ -44,6 +67,22 @@ class FilesController extends AbstractController foreach ($files as $file) { $filename = basename((string) $file['path']); if (!str_starts_with($filename, '.')) { + // On vérifie si l'utilisateur a le droit d'accéder au fichier (vérifier que owner_role du parentDirectory correspondant est bien le folderRole de l'utilisateur) + $pathFile = explode('/', $file['path']); + if ('' !== $path) { + $parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $pathFile[0]]); + + if (null === $parentDir || ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true))) { + continue; + } + } elseif ('file' !== $file['type']) { + $parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $filename]); + + if (null === $parentDir || ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true))) { + continue; + } + } + $realFiles[] = [ 'type' => $file['type'], 'path' => $file['path'], @@ -70,10 +109,34 @@ class FilesController extends AbstractController ]); } + /** + * @throws FilesystemException + */ #[Route('/file-proxy', name: 'app_file_proxy')] public function fileProxy(Filesystem $defaultAdapter, #[MapQueryParameter('filename')] string $filename) { $file = $this->normalizePath($filename); + + $parentDir = $this->parentDirectoryRepository->findOneBy(['name' => explode('/', $file)[0]]); + + if (null === $parentDir) { + throw $this->createNotFoundException("Vous n'avez pas le droit d'accéder à ce fichier !"); + } + + // Si l'owner role sur le parent est visiteur, on peut accéder au fichier sans être connecté + if (RoleEnum::VISITEUR !== $parentDir->getOwnerRole()) { + $this->denyAccessUnlessGranted('ROLE_USER'); + + /** + * @var User $user + */ + $user = $this->getUser(); + + if ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true)) { + throw $this->createNotFoundException("Vous n'avez pas le droit d'accéder à ce fichier !"); + } + } + $mimetype = $defaultAdapter->mimeType($file); if ('' === $mimetype) { $mimetype = 'application/octet-stream'; @@ -99,10 +162,25 @@ class FilesController extends AbstractController * @throws FilesystemException */ #[Route('/file-delete', name: 'delete')] + #[IsGranted('ROLE_USER')] public function fileDelete(Filesystem $defaultAdapter, #[MapQueryParameter('filename')] string $filename): RedirectResponse { + /** + * @var User $user + */ + $user = $this->getUser(); $file = $this->normalizePath($filename); + $realPath = explode('/', $file); + + if (count($realPath) > 1) { + $parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $realPath[0]]); + + if (null === $parentDir || ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true))) { + throw $this->createNotFoundException("Vous n'avez pas le droit de supprimer ce fichier !"); + } + } + if ('' !== $file && !str_starts_with($file, '.') && $defaultAdapter->fileExists($file)) { $defaultAdapter->delete($file); @@ -120,9 +198,21 @@ class FilesController extends AbstractController * @throws FilesystemException */ #[Route('/directory-delete', name: 'delete_directory')] + #[IsGranted('ROLE_USER')] public function directoryDelete(Filesystem $defaultAdapter, #[MapQueryParameter('path')] string $path): RedirectResponse { $path = $this->normalizePath($path); + /** + * @var User $user + */ + $user = $this->getUser(); + + $realPath = explode('/', $path); + $parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $realPath[0]]); + + if (null === $parentDir || ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true))) { + throw $this->createNotFoundException("Vous n'avez pas le droit de supprimer ce dossier !"); + } if ('' !== $path && !str_starts_with($path, '.') && $defaultAdapter->directoryExists($path)) { $defaultAdapter->deleteDirectory($path); @@ -141,14 +231,29 @@ class FilesController extends AbstractController * @throws FilesystemException */ #[Route('/rename', name: 'rename')] + #[IsGranted('ROLE_USER')] public function rename(#[MapQueryParameter('path')] string $filepath, Request $request, Filesystem $defaultAdapter): Response { $filepath = $this->normalizePath($filepath); + /** + * @var User $user + */ + $user = $this->getUser(); if ('' === $filepath || str_starts_with($filepath, '.') || !$defaultAdapter->fileExists($filepath)) { throw $this->createNotFoundException("Ce fichier n'existe pas !"); } + $realPath = explode('/', $filepath); + + if (count($realPath) > 1) { + $parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $realPath[0]]); + + if (null === $parentDir || ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true))) { + throw $this->createNotFoundException("Vous n'avez pas le droit de renommer ce fichier !"); + } + } + $data = [ 'newName' => pathinfo($filepath, PATHINFO_BASENAME), ]; @@ -183,9 +288,22 @@ class FilesController extends AbstractController * @throws FilesystemException */ #[Route('/create-directory', name: 'create_directory')] + #[IsGranted('ROLE_USER')] public function createDirectory(Request $request, Filesystem $defaultAdapter, #[MapQueryParameter('base')] string $basePath): Response { $basePath = $this->normalizePath($basePath); + $realPath = explode('/', $basePath); + /** + * @var User $user + */ + $user = $this->getUser(); + + if (count($realPath) > 1) { + $parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $realPath[0]]); + if (null === $parentDir || ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true))) { + throw $this->createNotFoundException("Vous n'avez pas le droit de créer de sous-dossier dans ce dossier !"); + } + } $form = $this->createForm(CreateDirectoryType::class); $form->handleRequest($request); @@ -195,10 +313,36 @@ class FilesController extends AbstractController $name = $data['name']; + if (explode('/', $name) > 1) { + $name = explode('/', $name)[0]; + } + + if ($defaultAdapter->directoryExists($basePath . '/' . $name)) { + $this->addFlash('error', 'Le dossier existe déjà.'); + + return $this->redirectToRoute('app_files_index', [ + 'path' => $basePath, + ]); + } + $defaultAdapter->createDirectory($basePath . '/' . $name); $defaultAdapter->write($basePath . '/' . $name . '/.gitkeep', ''); + // si basePath est vide, on crée un parentDirectory + if ('' === $basePath) { + /** + * @var User $user + */ + $user = $this->getUser(); + $parentDirectory = new ParentDirectory(); + $parentDirectory->setName($name); + $parentDirectory->setOwnerRole($user->getFolderRole()); + + $this->entityManager->persist($parentDirectory); + $this->entityManager->flush(); + } + $this->addFlash('success', 'Le dossier a bien été créé.'); return $this->redirectToRoute('app_files_index', [ @@ -216,9 +360,21 @@ class FilesController extends AbstractController * @throws FilesystemException */ #[Route('/rename-directory', name: 'rename-directory')] + #[IsGranted('ROLE_USER')] public function renameDirectory(#[MapQueryParameter('path')] string $filepath, Request $request, Filesystem $defaultAdapter): Response { $filepath = $this->normalizePath($filepath); + /** + * @var User $user + */ + $user = $this->getUser(); + + $realPath = explode('/', $filepath); + $parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $realPath[0]]); + + if (null === $parentDir || ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true))) { + throw $this->createNotFoundException("Vous n'avez pas le droit de renommer ce dossier !"); + } if ('' === $filepath || str_starts_with($filepath, '.') || !$defaultAdapter->directoryExists($filepath)) { throw $this->createNotFoundException("Ce dossier n'existe pas !"); @@ -258,13 +414,30 @@ class FilesController extends AbstractController * @throws FilesystemException */ #[Route('/upload', name: 'upload')] + #[IsGranted('ROLE_USER')] public function upload(#[MapQueryParameter('path')] string $path, Request $request, Filesystem $defaultAdapter): Response { $path = $this->normalizePath($path); + /** + * @var User $user + */ + $user = $this->getUser(); + + if ('' === $path) { + throw $this->createNotFoundException("Vous ne pouvez pas uploader de fichier à la racine !"); + } + + $realPath = explode('/', $path); + $parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $realPath[0]]); + + if (null === $parentDir || ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true))) { + throw $this->createNotFoundException("Vous n'avez pas le droit d'uploader des fichiers dans ce dossier !"); + } + $form = $this->createForm(UploadType::class); - if ('' !== $path && !$defaultAdapter->directoryExists($path)) { + if (!$defaultAdapter->directoryExists($path)) { throw $this->createNotFoundException("Ce dossier n'existe pas !"); } diff --git a/src/Entity/ParentDirectory.php b/src/Entity/ParentDirectory.php new file mode 100644 index 0000000..174b7ec --- /dev/null +++ b/src/Entity/ParentDirectory.php @@ -0,0 +1,51 @@ +id; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + public function getOwnerRole(): ?RoleEnum + { + return $this->ownerRole; + } + + public function setOwnerRole(RoleEnum $ownerRole): static + { + $this->ownerRole = $ownerRole; + + return $this; + } +} diff --git a/src/Entity/User.php b/src/Entity/User.php index 9424af3..154a06c 100755 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Entity; +use App\Enum\RoleEnum; use App\Repository\UserRepository; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; @@ -18,8 +19,9 @@ use Symfony\Component\Uid\Uuid; class User implements UserInterface, PasswordAuthenticatedUserInterface { #[ORM\Id] - #[ORM\Column(type: 'uuid', length: 180)] - private ?Uuid $id = null; + #[ORM\GeneratedValue] + #[ORM\Column] + private ?int $id = null; #[ORM\Column(length: 255)] private ?string $email = null; @@ -36,16 +38,13 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column] private ?string $password = null; - public function initId(): void - { - if ($this->id instanceof \Symfony\Component\Uid\Uuid) { - return; - } + #[ORM\Column(enumType: RoleEnum::class)] + private ?RoleEnum $folder_role = null; - $this->id = Uuid::v4(); - } - - public function getId(): ?Uuid + /** + * @return int|null + */ + public function getId(): ?int { return $this->id; } @@ -119,4 +118,16 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return $this; } + + public function getFolderRole(): ?RoleEnum + { + return $this->folder_role; + } + + public function setFolderRole(RoleEnum $folder_role): static + { + $this->folder_role = $folder_role; + + return $this; + } } diff --git a/src/Enum/RoleEnum.php b/src/Enum/RoleEnum.php new file mode 100644 index 0000000..f621c4f --- /dev/null +++ b/src/Enum/RoleEnum.php @@ -0,0 +1,35 @@ + + */ +class ParentDirectoryRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, ParentDirectory::class); + } + +// /** +// * @return ParentDirectory[] Returns an array of ParentDirectory objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('p') +// ->andWhere('p.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('p.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?ParentDirectory +// { +// return $this->createQueryBuilder('p') +// ->andWhere('p.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/templates/files/index.html.twig b/templates/files/index.html.twig index 71159c2..ff24390 100755 --- a/templates/files/index.html.twig +++ b/templates/files/index.html.twig @@ -12,9 +12,11 @@ Créer un dossier + {% if path != '' %} Ajouter des fichiers + {% endif %}
{% include 'partials/breadbrumb.html.twig' %}