diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/README.md b/README.md index 5cffd8c..b935f4c 100644 --- a/README.md +++ b/README.md @@ -93,4 +93,9 @@ Pas de commandes. ### Question 14 ```bash symfony console doctrine:fixtures:load +``` + +### Question 15 +```bash +symfony console doctrine:fixtures:load ``` \ No newline at end of file diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 8196f0a..5beaf40 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -10,7 +10,8 @@ security: class: App\Entity\User property: email role_hierarchy: - ROLE_INSTRUCTOR: ROLE_USER + ROLE_ADMINISTRATEUR: [ ROLE_INSTRUCTEUR, ROLE_APPRENTI, ROLE_USER ] + ROLE_INSTRUCTEUR: ROLE_USER ROLE_APPRENTI: ROLE_USER firewalls: dev: @@ -34,8 +35,8 @@ security: # Easy way to control access for large sections of your site # Note: Only the *first* access control that matches will be used access_control: - # - { path: ^/admin, roles: ROLE_ADMIN } - # - { path: ^/profile, roles: ROLE_USER } + # - { path: ^/admin, roles: ROLE_ADMIN } + # - { path: ^/profile, roles: ROLE_USER } when@test: security: diff --git a/src/Controller/AtelierController.php b/src/Controller/AtelierController.php index 0c99ca8..bb860ce 100644 --- a/src/Controller/AtelierController.php +++ b/src/Controller/AtelierController.php @@ -24,7 +24,7 @@ class AtelierController extends AbstractController ]); } - #[IsGranted('ROLE_INSTRUCTOR')] + #[IsGranted('ROLE_INSTRUCTEUR')] #[Route('/byme', name: 'app_atelier_by_me', methods: ['GET'])] public function index_created_by_user(AtelierRepository $atelierRepository, MarkdownAtelier $markdown): Response { @@ -44,7 +44,7 @@ class AtelierController extends AbstractController ]); } - #[IsGranted('ROLE_INSTRUCTOR')] + #[IsGranted('ROLE_INSTRUCTEUR')] #[Route('/new', name: 'app_atelier_new', methods: ['GET', 'POST'])] public function new(Request $request, AtelierRepository $atelierRepository): Response { @@ -74,7 +74,7 @@ class AtelierController extends AbstractController ]); } - #[IsGranted('ROLE_INSTRUCTOR')] + #[IsGranted('ROLE_INSTRUCTEUR')] #[Route('/{id}/edit', name: 'app_atelier_edit', methods: ['GET', 'POST'])] public function edit(Request $request, Atelier $atelier, AtelierRepository $atelierRepository): Response { @@ -97,7 +97,7 @@ class AtelierController extends AbstractController ]); } - #[IsGranted('ROLE_INSTRUCTOR')] + #[IsGranted('ROLE_INSTRUCTEUR')] #[Route('/{id}', name: 'app_atelier_delete', methods: ['POST'])] public function delete(Request $request, Atelier $atelier, AtelierRepository $atelierRepository): Response { diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index 73da5ac..3269b24 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -13,7 +13,7 @@ class SecurityController extends AbstractController public function login(AuthenticationUtils $authenticationUtils): Response { if ($this->getUser()) { - return $this->redirectToRoute('app_atelier_index'); + return $this->redirectToRoute('app_index'); } // get the login error if there is one diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php new file mode 100644 index 0000000..6e6bad2 --- /dev/null +++ b/src/Controller/UserController.php @@ -0,0 +1,88 @@ +render('user/index.html.twig', [ + 'users' => $userRepository->findAll(), + ]); + } + + #[Route('/new', name: 'app_user_new', methods: ['GET', 'POST'])] + public function new(Request $request, UserRepository $userRepository, UserPasswordHasherInterface $userPasswordHasher): Response + { + $user = new User(); + $form = $this->createForm(NewUserType::class, $user); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $user->setPassword( + $userPasswordHasher->hashPassword( + $user, + $form->get('plainPassword')->getData() + ) + ); + $userRepository->save($user, true); + + return $this->redirectToRoute('app_user_index', [], Response::HTTP_SEE_OTHER); + } + + return $this->renderForm('user/new.html.twig', [ + 'user' => $user, + 'form' => $form, + ]); + } + + #[Route('/{id}', name: 'app_user_show', methods: ['GET'])] + public function show(User $user): Response + { + return $this->render('user/show.html.twig', [ + 'user' => $user, + ]); + } + + #[Route('/{id}/edit', name: 'app_user_edit', methods: ['GET', 'POST'])] + public function edit(Request $request, User $user, UserRepository $userRepository): Response + { + $form = $this->createForm(UserType::class, $user); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $userRepository->save($user, true); + + return $this->redirectToRoute('app_user_index', [], Response::HTTP_SEE_OTHER); + } + + return $this->renderForm('user/edit.html.twig', [ + 'user' => $user, + 'form' => $form, + ]); + } + + #[Route('/{id}', name: 'app_user_delete', methods: ['POST'])] + public function delete(Request $request, User $user, UserRepository $userRepository): Response + { + if ($this->isCsrfTokenValid('delete' . $user->getId(), $request->request->get('_token'))) { + $userRepository->remove($user, true); + } + + return $this->redirectToRoute('app_user_index', [], Response::HTTP_SEE_OTHER); + } +} diff --git a/src/DataFixtures/AtelierFixture.php b/src/DataFixtures/AtelierFixture.php index be7dc3c..8606d61 100644 --- a/src/DataFixtures/AtelierFixture.php +++ b/src/DataFixtures/AtelierFixture.php @@ -7,29 +7,45 @@ use App\Entity\User; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Persistence\ObjectManager; use Faker\Generator; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; class AtelierFixture extends Fixture { + private UserPasswordHasherInterface $userPasswordHasher; + + public function __construct(UserPasswordHasherInterface $userPasswordHasher) + { + $this->userPasswordHasher = $userPasswordHasher; + } + public function load(ObjectManager $manager): void { $faker = \Faker\Factory::create("fr_FR"); $user = new User(); - $user->setEmail('test@hotmail.com') - ->setNom("test") - ->setPrenom("test") - ->setPassword(""); + $user + ->setEmail('admin@admin.fr') + ->setNom("admin") + ->setPrenom("admin") + ->setRoles(['ROLE_ADMINISTRATEUR']); + + $user->setPassword( + $this->userPasswordHasher->hashPassword( + $user, + 'admin' + ) + ); + $manager->persist($user); $users = $this->createUsers($manager, $faker); - $manager->persist($user); for ($i = 0; $i <= 20; $i++) { $atelier = new Atelier(); $atelier->setNom($faker->word) ->setDescription("# " . $faker->sentence(3) . "\n" . $faker->paragraph()) ->setInstructeur($user); - foreach ($faker->randomElements($users, $faker->randomNumber() % sizeof($users)) as $user) { - $atelier->addEleve($user); + foreach ($faker->randomElements($users, $faker->randomNumber() % sizeof($users)) as $eleve) { + $atelier->addEleve($eleve); } $manager->persist($atelier); diff --git a/src/Entity/User.php b/src/Entity/User.php index e56617c..9db0d01 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -83,9 +83,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface { $roles = $this->roles; // guarantee every user at least has ROLE_USER - $roles[] = 'ROLE_USER'; - $roles[] = 'ROLE_INSTRUCTOR'; - $roles[] = 'ROLE_APPRENTI'; + if (sizeof($roles) == 0) { + $roles[] = "ROLE_APPRENTI"; + } return array_unique($roles); } diff --git a/src/Form/NewUserType.php b/src/Form/NewUserType.php new file mode 100644 index 0000000..2940762 --- /dev/null +++ b/src/Form/NewUserType.php @@ -0,0 +1,71 @@ +add('email') + ->add('plainPassword', PasswordType::class, [ + // instead of being set onto the object directly, + // this is read and encoded in the controller + 'mapped' => false, + 'attr' => ['autocomplete' => 'new-password'], + 'constraints' => [ + new NotBlank([ + 'message' => 'Please enter a password', + ]), + new Length([ + 'min' => 6, + 'minMessage' => 'Your password should be at least {{ limit }} characters', + // max length allowed by Symfony for security reasons + 'max' => 4096, + ]), + ], + ]) + ->add('roles', ChoiceType::class, [ + 'choices' => [ + 'Apprenti' => 'ROLE_APPRENTI', + 'Instructeur' => 'ROLE_INSTRUCTEUR', + 'Admin' => 'ROLE_ADMINISTRATEUR' + ], + 'required' => true, + 'multiple' => false, + 'expanded' => false, + ]) + ->add('nom') + ->add('prenom'); + + $builder->get('roles') + ->addModelTransformer(new CallbackTransformer( + function ($rolesArray) { + // transform the array to a string + return count($rolesArray) ? $rolesArray[0] : null; + }, + function ($rolesString) { + // transform the string back to an array + return [$rolesString]; + } + )); + + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => User::class, + ]); + } +} diff --git a/src/Form/UserType.php b/src/Form/UserType.php new file mode 100644 index 0000000..02f7926 --- /dev/null +++ b/src/Form/UserType.php @@ -0,0 +1,52 @@ +add('email') + ->add('roles', ChoiceType::class, [ + 'choices' => [ + 'Apprenti' => 'ROLE_APPRENTI', + 'Instructeur' => 'ROLE_INSTRUCTEUR', + 'Admin' => 'ROLE_ADMINISTRATEUR' + ], + 'required' => true, + 'multiple' => false, + 'expanded' => false, + ]) + ->add('nom') + ->add('prenom'); + + $builder->get('roles') + ->addModelTransformer(new CallbackTransformer( + function ($rolesArray) { + // transform the array to a string + return count($rolesArray)? $rolesArray[0]: null; + }, + function ($rolesString) { + // transform the string back to an array + return [$rolesString]; + } + )); + + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => User::class, + ]); + } +} diff --git a/src/Security/AppAuthentificatorAuthenticator.php b/src/Security/AppAuthentificatorAuthenticator.php index 946b252..7ca73ed 100644 --- a/src/Security/AppAuthentificatorAuthenticator.php +++ b/src/Security/AppAuthentificatorAuthenticator.php @@ -46,7 +46,7 @@ class AppAuthentificatorAuthenticator extends AbstractLoginFormAuthenticator return new RedirectResponse($targetPath); } - return new RedirectResponse($this->urlGenerator->generate('app_atelier_index')); + return new RedirectResponse($this->urlGenerator->generate('app_index')); } protected function getLoginUrl(Request $request): string diff --git a/templates/components/navbar.html.twig b/templates/components/navbar.html.twig index a590160..31c43b8 100644 --- a/templates/components/navbar.html.twig +++ b/templates/components/navbar.html.twig @@ -13,16 +13,22 @@ - {% if app.user %} + + {% if app.user and is_granted('ROLE_INSTRUCTEUR') %} {% endif %} - {% if app.user %} + {% if app.user and is_granted('ROLE_APPRENTI') %} {% endif %} + {% if app.user and is_granted('ROLE_ADMINISTRATEUR') %} + + {% endif %} {% if app.user %} diff --git a/templates/user/_delete_form.html.twig b/templates/user/_delete_form.html.twig new file mode 100644 index 0000000..6d59fa6 --- /dev/null +++ b/templates/user/_delete_form.html.twig @@ -0,0 +1,4 @@ +
+ + +
diff --git a/templates/user/_form.html.twig b/templates/user/_form.html.twig new file mode 100644 index 0000000..bf20b98 --- /dev/null +++ b/templates/user/_form.html.twig @@ -0,0 +1,4 @@ +{{ form_start(form) }} + {{ form_widget(form) }} + +{{ form_end(form) }} diff --git a/templates/user/edit.html.twig b/templates/user/edit.html.twig new file mode 100644 index 0000000..141d94a --- /dev/null +++ b/templates/user/edit.html.twig @@ -0,0 +1,13 @@ +{% extends 'base.html.twig' %} + +{% block title %}Edit User{% endblock %} + +{% block body %} +

Edit User

+ + {{ include('user/_form.html.twig', {'button_label': 'Update'}) }} + + back to list + + {{ include('user/_delete_form.html.twig') }} +{% endblock %} diff --git a/templates/user/index.html.twig b/templates/user/index.html.twig new file mode 100644 index 0000000..e2e3f7f --- /dev/null +++ b/templates/user/index.html.twig @@ -0,0 +1,41 @@ +{% extends 'base.html.twig' %} + +{% block title %}User index{% endblock %} + +{% block body %} +

User index

+ + + + + + + + + + + + + + {% for user in users %} + + + + + + + + + {% else %} + + + + {% endfor %} + +
IdEmailRolesNomPrenomactions
{{ user.id }}{{ user.email }}{{ user.roles ? user.roles[0] : '' }}{{ user.nom }}{{ user.prenom }} + show + edit +
no records found
+ + Create new +{% endblock %} diff --git a/templates/user/new.html.twig b/templates/user/new.html.twig new file mode 100644 index 0000000..35e728d --- /dev/null +++ b/templates/user/new.html.twig @@ -0,0 +1,11 @@ +{% extends 'base.html.twig' %} + +{% block title %}New User{% endblock %} + +{% block body %} +

Create new User

+ + {{ include('user/_form.html.twig') }} + + back to list +{% endblock %} diff --git a/templates/user/show.html.twig b/templates/user/show.html.twig new file mode 100644 index 0000000..ffc5b61 --- /dev/null +++ b/templates/user/show.html.twig @@ -0,0 +1,38 @@ +{% extends 'base.html.twig' %} + +{% block title %}User{% endblock %} + +{% block body %} +

User

+ + + + + + + + + + + + + + + + + + + + + + + + +
Id{{ user.id }}
Email{{ user.email }}
Roles{{ user.roles ? user.roles[0] : '' }}
Nom{{ user.nom }}
Prenom{{ user.prenom }}
+ + back to list + + edit + + {{ include('user/_delete_form.html.twig') }} +{% endblock %}