Ajout de la gestion des rôles sur les dossiers parents + gestion des liens public si le dossier parent est relié au rôle Visiteur (Mais pas possible de lister les fichiers)
Some checks failed
Apply PHP CS Fixer / php-cs-fixer (push) Failing after 22s
CI / build-test (push) Failing after 1m24s
rector / Rector (push) Failing after 1m22s

This commit is contained in:
Melaine Gérard 2025-01-22 20:11:29 +01:00
parent 14c6ce39ab
commit 38f7355d42
15 changed files with 472 additions and 152 deletions

View File

@ -7,12 +7,12 @@
"php": ">=8.2", "php": ">=8.2",
"ext-ctype": "*", "ext-ctype": "*",
"ext-iconv": "*", "ext-iconv": "*",
"doctrine/dbal": "^4.2.1", "doctrine/dbal": "^4.2.2",
"doctrine/doctrine-bundle": "^2.13.1", "doctrine/doctrine-bundle": "^2.13.2",
"doctrine/doctrine-migrations-bundle": "^3.3.1", "doctrine/doctrine-migrations-bundle": "^3.4.0",
"doctrine/orm": "^3.3.1", "doctrine/orm": "^3.3.1",
"league/flysystem": "^3.29.1", "league/flysystem": "^3.29.1",
"oneup/flysystem-bundle": "^4.12.3", "oneup/flysystem-bundle": "^4.12.4",
"phpdocumentor/reflection-docblock": "^5.6.1", "phpdocumentor/reflection-docblock": "^5.6.1",
"phpstan/phpdoc-parser": "^2.0", "phpstan/phpdoc-parser": "^2.0",
"symfony/apache-pack": "^1.0.1", "symfony/apache-pack": "^1.0.1",
@ -50,7 +50,7 @@
"symfony/validator": "7.2.*", "symfony/validator": "7.2.*",
"symfony/web-link": "7.2.*", "symfony/web-link": "7.2.*",
"symfony/yaml": "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", "symfonycasts/tailwind-bundle": "^0.6.1",
"tales-from-a-dev/flowbite-bundle": "^0.7.1", "tales-from-a-dev/flowbite-bundle": "^0.7.1",
"twig/extra-bundle": "^2.12|^3.18", "twig/extra-bundle": "^2.12|^3.18",
@ -109,16 +109,16 @@
} }
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "^3.68", "friendsofphp/php-cs-fixer": "^3.68.1",
"phpstan/extension-installer": "^1.4", "phpstan/extension-installer": "^1.4.3",
"phpstan/phpstan-strict-rules": "^2.0", "phpstan/phpstan-strict-rules": "^2.0.3",
"phpstan/phpstan-symfony": "^2.0", "phpstan/phpstan-symfony": "^2.0.2",
"phpunit/phpunit": "^11.5.2", "phpunit/phpunit": "^11.5.3",
"rector/rector": "^2.0", "rector/rector": "^2.0.7",
"symfony/browser-kit": "7.2.*", "symfony/browser-kit": "7.2.*",
"symfony/css-selector": "7.2.*", "symfony/css-selector": "7.2.*",
"symfony/debug-bundle": "7.2.*", "symfony/debug-bundle": "7.2.*",
"symfony/maker-bundle": "^1.61", "symfony/maker-bundle": "^1.62.1",
"symfony/phpunit-bridge": "^7.2", "symfony/phpunit-bridge": "^7.2",
"symfony/stopwatch": "7.2.*", "symfony/stopwatch": "7.2.*",
"symfony/web-profiler-bundle": "7.2.*" "symfony/web-profiler-bundle": "7.2.*"

38
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "d58d5009e09974e2358412fe10fa1005", "content-hash": "6438b8b149fca5ad2f3c96aa06e93da2",
"packages": [ "packages": [
{ {
"name": "composer/semver", "name": "composer/semver",
@ -9371,16 +9371,16 @@
}, },
{ {
"name": "phpstan/phpstan", "name": "phpstan/phpstan",
"version": "2.1.1", "version": "2.1.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan.git", "url": "https://github.com/phpstan/phpstan.git",
"reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7" "reference": "7d08f569e582ade182a375c366cbd896eccadd3a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7d08f569e582ade182a375c366cbd896eccadd3a",
"reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7", "reference": "7d08f569e582ade182a375c366cbd896eccadd3a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -9425,20 +9425,20 @@
"type": "github" "type": "github"
} }
], ],
"time": "2025-01-05T16:43:48+00:00" "time": "2025-01-21T14:54:06+00:00"
}, },
{ {
"name": "phpstan/phpstan-strict-rules", "name": "phpstan/phpstan-strict-rules",
"version": "2.0.2", "version": "2.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git", "url": "https://github.com/phpstan/phpstan-strict-rules.git",
"reference": "02277ce4b0dd03d74f15282064f8f027d1dec9cc" "reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/02277ce4b0dd03d74f15282064f8f027d1dec9cc", "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/8b88b5f818bfa301e0c99154ab622dace071c3ba",
"reference": "02277ce4b0dd03d74f15282064f8f027d1dec9cc", "reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -9471,28 +9471,28 @@
"description": "Extra strict and opinionated rules for PHPStan", "description": "Extra strict and opinionated rules for PHPStan",
"support": { "support": {
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues", "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", "name": "phpstan/phpstan-symfony",
"version": "2.0.1", "version": "2.0.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan-symfony.git", "url": "https://github.com/phpstan/phpstan-symfony.git",
"reference": "c08cd8e54a08d651bc402d304cfa161c3c3766c4" "reference": "65f02c7e585f3c7372e42e14d3d87da034031553"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/c08cd8e54a08d651bc402d304cfa161c3c3766c4", "url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/65f02c7e585f3c7372e42e14d3d87da034031553",
"reference": "c08cd8e54a08d651bc402d304cfa161c3c3766c4", "reference": "65f02c7e585f3c7372e42e14d3d87da034031553",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-simplexml": "*", "ext-simplexml": "*",
"php": "^7.4 || ^8.0", "php": "^7.4 || ^8.0",
"phpstan/phpstan": "^2.0" "phpstan/phpstan": "^2.1.2"
}, },
"conflict": { "conflict": {
"symfony/framework-bundle": "<3.0" "symfony/framework-bundle": "<3.0"
@ -9542,9 +9542,9 @@
"description": "Symfony Framework extensions and rules for PHPStan", "description": "Symfony Framework extensions and rules for PHPStan",
"support": { "support": {
"issues": "https://github.com/phpstan/phpstan-symfony/issues", "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", "name": "phpunit/php-code-coverage",

View File

@ -1,37 +0,0 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20241229133017 extends AbstractMigration
{
public function getDescription(): string
{
return 'Ajout de la table user et messenger_messages';
}
public function up(Schema $schema): void
{
$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('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');
}
}

View File

@ -1,64 +0,0 @@
<?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 Version20250116210156 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
$this->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)');
}
}

View File

@ -0,0 +1,40 @@
<?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 Version20250122182101 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('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');
}
}

View File

@ -0,0 +1,31 @@
<?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 Version20250122182130 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('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');
}
}

View File

@ -0,0 +1,35 @@
<?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 Version20250122183447 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
$this->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)');
}
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Command; namespace App\Command;
use App\Entity\User; use App\Entity\User;
use App\Enum\RoleEnum;
use App\Repository\UserRepository; use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
@ -36,6 +37,7 @@ class CreateUserCommand extends Command
$email = $io->ask('Email de l\'utilisateur'); $email = $io->ask('Email de l\'utilisateur');
$password = $io->askHidden('Mot de passe de l\'utilisateur'); $password = $io->askHidden('Mot de passe de l\'utilisateur');
$isAdmin = $io->confirm('Est-ce un administrateur ?'); $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 { try {
$user = $this->userRepository->findOneBy(['email' => $email]); $user = $this->userRepository->findOneBy(['email' => $email]);
@ -43,19 +45,18 @@ class CreateUserCommand extends Command
$io->error('Un utilisateur existe déjà avec cet email'); $io->error('Un utilisateur existe déjà avec cet email');
return Command::FAILURE; return Command::FAILURE;
} }
$user = new User(); $user = new User();
$user->setEmail($email); $user->setEmail($email);
$user->setPassword($this->passwordHasher->hashPassword($user, $password)); $user->setPassword($this->passwordHasher->hashPassword($user, $password));
$user->setRoles($isAdmin ? ['ROLE_ADMIN'] : ['ROLE_USER']); $user->setRoles($isAdmin ? ['ROLE_ADMIN'] : ['ROLE_USER']);
$user->initId(); $user->setFolderRole(RoleEnum::from($folderRole));
$this->entityManager->persist($user); $this->entityManager->persist($user);
$this->entityManager->flush(); $this->entityManager->flush();
$io->success('Utilisateur créé avec succès'); $io->success('Utilisateur créé avec succès');
} catch (\Exception) { } catch (\Exception $e) {
$io->error('Une erreur est survenue lors de la création de l\'utilisateur'); $io->error('Une erreur est survenue lors de la création de l\'utilisateur : ' . $e->getMessage());
} }
return Command::SUCCESS; return Command::SUCCESS;

View File

@ -59,7 +59,6 @@ class AdminController extends AbstractController
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$role = $form->get('role')->getData(); $role = $form->get('role')->getData();
$user->setRoles([$role]); $user->setRoles([$role]);
$user->initId();
if ($form->has('plainPassword')) { if ($form->has('plainPassword')) {
$plainPassword = $form->get('plainPassword')->getData(); $plainPassword = $form->get('plainPassword')->getData();

View File

@ -4,9 +4,14 @@ declare(strict_types=1);
namespace App\Controller; namespace App\Controller;
use App\Entity\ParentDirectory;
use App\Entity\User;
use App\Enum\RoleEnum;
use App\Form\CreateDirectoryType; use App\Form\CreateDirectoryType;
use App\Form\RenameType; use App\Form\RenameType;
use App\Form\UploadType; use App\Form\UploadType;
use App\Repository\ParentDirectoryRepository;
use Doctrine\ORM\EntityManagerInterface;
use League\Flysystem\Filesystem; use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemException; use League\Flysystem\FilesystemException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@ -22,19 +27,37 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Http\Attribute\IsGranted; use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route('/files', 'app_files_')] #[Route('/files', 'app_files_')]
#[IsGranted('ROLE_USER')]
class FilesController extends AbstractController class FilesController extends AbstractController
{ {
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly ParentDirectoryRepository $parentDirectoryRepository
) {
}
/** /**
* @throws FilesystemException * @throws FilesystemException
*/ */
#[Route('/', name: 'index')] #[Route('/', name: 'index')]
#[IsGranted('ROLE_USER')]
public function index(Filesystem $defaultAdapter, UrlGeneratorInterface $urlGenerator, #[MapQueryParameter('path')] string $path = ''): Response public function index(Filesystem $defaultAdapter, UrlGeneratorInterface $urlGenerator, #[MapQueryParameter('path')] string $path = ''): Response
{ {
$path = $this->normalizePath($path); $path = $this->normalizePath($path);
/**
* @var User $user
*/
$user = $this->getUser();
if ('' !== $path && !$defaultAdapter->directoryExists($path)) { if ('' !== $path) {
throw $this->createNotFoundException("Ce dossier n'existe pas !"); $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); $files = $defaultAdapter->listContents('/' . $path);
@ -44,6 +67,22 @@ class FilesController extends AbstractController
foreach ($files as $file) { foreach ($files as $file) {
$filename = basename((string) $file['path']); $filename = basename((string) $file['path']);
if (!str_starts_with($filename, '.')) { 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[] = [ $realFiles[] = [
'type' => $file['type'], 'type' => $file['type'],
'path' => $file['path'], 'path' => $file['path'],
@ -70,10 +109,34 @@ class FilesController extends AbstractController
]); ]);
} }
/**
* @throws FilesystemException
*/
#[Route('/file-proxy', name: 'app_file_proxy')] #[Route('/file-proxy', name: 'app_file_proxy')]
public function fileProxy(Filesystem $defaultAdapter, #[MapQueryParameter('filename')] string $filename) public function fileProxy(Filesystem $defaultAdapter, #[MapQueryParameter('filename')] string $filename)
{ {
$file = $this->normalizePath($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); $mimetype = $defaultAdapter->mimeType($file);
if ('' === $mimetype) { if ('' === $mimetype) {
$mimetype = 'application/octet-stream'; $mimetype = 'application/octet-stream';
@ -99,10 +162,25 @@ class FilesController extends AbstractController
* @throws FilesystemException * @throws FilesystemException
*/ */
#[Route('/file-delete', name: 'delete')] #[Route('/file-delete', name: 'delete')]
#[IsGranted('ROLE_USER')]
public function fileDelete(Filesystem $defaultAdapter, #[MapQueryParameter('filename')] string $filename): RedirectResponse public function fileDelete(Filesystem $defaultAdapter, #[MapQueryParameter('filename')] string $filename): RedirectResponse
{ {
/**
* @var User $user
*/
$user = $this->getUser();
$file = $this->normalizePath($filename); $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)) { if ('' !== $file && !str_starts_with($file, '.') && $defaultAdapter->fileExists($file)) {
$defaultAdapter->delete($file); $defaultAdapter->delete($file);
@ -120,9 +198,21 @@ class FilesController extends AbstractController
* @throws FilesystemException * @throws FilesystemException
*/ */
#[Route('/directory-delete', name: 'delete_directory')] #[Route('/directory-delete', name: 'delete_directory')]
#[IsGranted('ROLE_USER')]
public function directoryDelete(Filesystem $defaultAdapter, #[MapQueryParameter('path')] string $path): RedirectResponse public function directoryDelete(Filesystem $defaultAdapter, #[MapQueryParameter('path')] string $path): RedirectResponse
{ {
$path = $this->normalizePath($path); $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)) { if ('' !== $path && !str_starts_with($path, '.') && $defaultAdapter->directoryExists($path)) {
$defaultAdapter->deleteDirectory($path); $defaultAdapter->deleteDirectory($path);
@ -141,14 +231,29 @@ class FilesController extends AbstractController
* @throws FilesystemException * @throws FilesystemException
*/ */
#[Route('/rename', name: 'rename')] #[Route('/rename', name: 'rename')]
#[IsGranted('ROLE_USER')]
public function rename(#[MapQueryParameter('path')] string $filepath, Request $request, Filesystem $defaultAdapter): Response public function rename(#[MapQueryParameter('path')] string $filepath, Request $request, Filesystem $defaultAdapter): Response
{ {
$filepath = $this->normalizePath($filepath); $filepath = $this->normalizePath($filepath);
/**
* @var User $user
*/
$user = $this->getUser();
if ('' === $filepath || str_starts_with($filepath, '.') || !$defaultAdapter->fileExists($filepath)) { if ('' === $filepath || str_starts_with($filepath, '.') || !$defaultAdapter->fileExists($filepath)) {
throw $this->createNotFoundException("Ce fichier n'existe pas !"); 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 = [ $data = [
'newName' => pathinfo($filepath, PATHINFO_BASENAME), 'newName' => pathinfo($filepath, PATHINFO_BASENAME),
]; ];
@ -183,9 +288,22 @@ class FilesController extends AbstractController
* @throws FilesystemException * @throws FilesystemException
*/ */
#[Route('/create-directory', name: 'create_directory')] #[Route('/create-directory', name: 'create_directory')]
#[IsGranted('ROLE_USER')]
public function createDirectory(Request $request, Filesystem $defaultAdapter, #[MapQueryParameter('base')] string $basePath): Response public function createDirectory(Request $request, Filesystem $defaultAdapter, #[MapQueryParameter('base')] string $basePath): Response
{ {
$basePath = $this->normalizePath($basePath); $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 = $this->createForm(CreateDirectoryType::class);
$form->handleRequest($request); $form->handleRequest($request);
@ -195,10 +313,36 @@ class FilesController extends AbstractController
$name = $data['name']; $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->createDirectory($basePath . '/' . $name);
$defaultAdapter->write($basePath . '/' . $name . '/.gitkeep', ''); $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éé.'); $this->addFlash('success', 'Le dossier a bien été créé.');
return $this->redirectToRoute('app_files_index', [ return $this->redirectToRoute('app_files_index', [
@ -216,9 +360,21 @@ class FilesController extends AbstractController
* @throws FilesystemException * @throws FilesystemException
*/ */
#[Route('/rename-directory', name: 'rename-directory')] #[Route('/rename-directory', name: 'rename-directory')]
#[IsGranted('ROLE_USER')]
public function renameDirectory(#[MapQueryParameter('path')] string $filepath, Request $request, Filesystem $defaultAdapter): Response public function renameDirectory(#[MapQueryParameter('path')] string $filepath, Request $request, Filesystem $defaultAdapter): Response
{ {
$filepath = $this->normalizePath($filepath); $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)) { if ('' === $filepath || str_starts_with($filepath, '.') || !$defaultAdapter->directoryExists($filepath)) {
throw $this->createNotFoundException("Ce dossier n'existe pas !"); throw $this->createNotFoundException("Ce dossier n'existe pas !");
@ -258,13 +414,30 @@ class FilesController extends AbstractController
* @throws FilesystemException * @throws FilesystemException
*/ */
#[Route('/upload', name: 'upload')] #[Route('/upload', name: 'upload')]
#[IsGranted('ROLE_USER')]
public function upload(#[MapQueryParameter('path')] string $path, Request $request, Filesystem $defaultAdapter): Response public function upload(#[MapQueryParameter('path')] string $path, Request $request, Filesystem $defaultAdapter): Response
{ {
$path = $this->normalizePath($path); $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); $form = $this->createForm(UploadType::class);
if ('' !== $path && !$defaultAdapter->directoryExists($path)) { if (!$defaultAdapter->directoryExists($path)) {
throw $this->createNotFoundException("Ce dossier n'existe pas !"); throw $this->createNotFoundException("Ce dossier n'existe pas !");
} }

View File

@ -0,0 +1,51 @@
<?php
namespace App\Entity;
use App\Enum\RoleEnum;
use App\Repository\ParentDirectoryRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ParentDirectoryRepository::class)]
class ParentDirectory
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $name = null;
#[ORM\Column(enumType: RoleEnum::class)]
private ?RoleEnum $ownerRole = null;
public function getId(): ?int
{
return $this->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;
}
}

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Entity; namespace App\Entity;
use App\Enum\RoleEnum;
use App\Repository\UserRepository; use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
@ -18,8 +19,9 @@ use Symfony\Component\Uid\Uuid;
class User implements UserInterface, PasswordAuthenticatedUserInterface class User implements UserInterface, PasswordAuthenticatedUserInterface
{ {
#[ORM\Id] #[ORM\Id]
#[ORM\Column(type: 'uuid', length: 180)] #[ORM\GeneratedValue]
private ?Uuid $id = null; #[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)] #[ORM\Column(length: 255)]
private ?string $email = null; private ?string $email = null;
@ -36,16 +38,13 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
#[ORM\Column] #[ORM\Column]
private ?string $password = null; private ?string $password = null;
public function initId(): void #[ORM\Column(enumType: RoleEnum::class)]
{ private ?RoleEnum $folder_role = null;
if ($this->id instanceof \Symfony\Component\Uid\Uuid) {
return;
}
$this->id = Uuid::v4(); /**
} * @return int|null
*/
public function getId(): ?Uuid public function getId(): ?int
{ {
return $this->id; return $this->id;
} }
@ -119,4 +118,16 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
return $this; return $this;
} }
public function getFolderRole(): ?RoleEnum
{
return $this->folder_role;
}
public function setFolderRole(RoleEnum $folder_role): static
{
$this->folder_role = $folder_role;
return $this;
}
} }

35
src/Enum/RoleEnum.php Normal file
View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace App\Enum;
enum RoleEnum: string
{
case CONSEIL_ADMINISTRATION = 'Conseil d\'administration';
case ADMINISTRATEUR = 'Administrateur';
case MEMBRE = 'Membre';
case MEMBRE_HONORAIRE = 'Membre honoraire';
case PARTENAIRE = 'Partenaire';
case VISITEUR = 'Visiteur';
public function getHigherRoles(): array
{
$roles = [];
$isFound = false;
foreach (RoleEnum::cases() as $role) {
if ($role === $this) {
$isFound = true;
}
if ($isFound) {
break;
}
$roles[] = $role;
}
return $roles;
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Repository;
use App\Entity\ParentDirectory;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<ParentDirectory>
*/
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()
// ;
// }
}

View File

@ -12,9 +12,11 @@
<a href="{{ path('app_files_create_directory', { <a href="{{ path('app_files_create_directory', {
base: path base: path
}) }}" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">Créer un dossier</a> }) }}" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">Créer un dossier</a>
{% if path != '' %}
<a href="{{ path('app_files_upload', { <a href="{{ path('app_files_upload', {
path: path path: path
}) }}" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">Ajouter des fichiers</a> }) }}" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">Ajouter des fichiers</a>
{% endif %}
</div> </div>
<div class="mt-4"> <div class="mt-4">
{% include 'partials/breadbrumb.html.twig' %} {% include 'partials/breadbrumb.html.twig' %}