From 82b593794241613ef0148ed087279f4cd1190564 Mon Sep 17 00:00:00 2001 From: Ayoub ZIANI Date: Wed, 8 Feb 2023 15:55:06 +0000 Subject: [PATCH] =?UTF-8?q?Ajout=20entit=C3=A9=20User,=20systeme=20d'authe?= =?UTF-8?q?ntification=20et=20d'enregistrement=20pour=20question=207?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 ++ config/packages/security.yaml | 15 +- migrations/Version20230208152026.php | 33 +++++ src/Controller/AtelierController.php | 2 + src/Controller/RegistrationController.php | 50 +++++++ src/Controller/SecurityController.php | 32 +++++ src/Entity/User.php | 132 ++++++++++++++++++ src/Form/RegistrationFormType.php | 49 +++++++ src/Repository/UserRepository.php | 83 +++++++++++ .../AppAuthentificatorAuthenticator.php | 56 ++++++++ templates/registration/register.html.twig | 19 +++ templates/security/login.html.twig | 42 ++++++ 12 files changed, 521 insertions(+), 2 deletions(-) create mode 100644 migrations/Version20230208152026.php create mode 100644 src/Controller/RegistrationController.php create mode 100644 src/Controller/SecurityController.php create mode 100644 src/Entity/User.php create mode 100644 src/Form/RegistrationFormType.php create mode 100644 src/Repository/UserRepository.php create mode 100644 src/Security/AppAuthentificatorAuthenticator.php create mode 100644 templates/registration/register.html.twig create mode 100644 templates/security/login.html.twig diff --git a/README.md b/README.md index 1c424bb..49cef42 100644 --- a/README.md +++ b/README.md @@ -55,4 +55,14 @@ npm run dev ### Question 6 ```bash symfony composer require cebe/markdown "~1.2.0" +``` + +### Question 7 +```bash +symfony console make:user +symfony console make:entity User +symfony console m:mig +symfony console d:m:m +symfony console make:auth +symfony console make:registration-form ``` \ No newline at end of file diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 367af25..507945f 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -4,14 +4,25 @@ security: Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider providers: - users_in_memory: { memory: null } + # used to reload user from session & other features (e.g. switch_user) + app_user_provider: + entity: + class: App\Entity\User + property: email + role_hierarchy: + ROLE_INSTRUCTOR: ROLE_USER firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: lazy: true - provider: users_in_memory + provider: app_user_provider + custom_authenticator: App\Security\AppAuthentificatorAuthenticator + logout: + path: app_logout + # where to redirect after logout + # target: app_any_route # activate different ways to authenticate # https://symfony.com/doc/current/security.html#the-firewall diff --git a/migrations/Version20230208152026.php b/migrations/Version20230208152026.php new file mode 100644 index 0000000..5a5bd79 --- /dev/null +++ b/migrations/Version20230208152026.php @@ -0,0 +1,33 @@ +addSql('CREATE TABLE user (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, email VARCHAR(180) NOT NULL, roles CLOB NOT NULL --(DC2Type:json) + , password VARCHAR(255) NOT NULL, nom VARCHAR(255) NOT NULL, prenom VARCHAR(255) NOT NULL)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649E7927C74 ON user (email)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE user'); + } +} diff --git a/src/Controller/AtelierController.php b/src/Controller/AtelierController.php index 8e6ee25..043b583 100644 --- a/src/Controller/AtelierController.php +++ b/src/Controller/AtelierController.php @@ -10,7 +10,9 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Security\Http\Attribute\IsGranted; +#[IsGranted('ROLE_INSTRUCTOR')] #[Route('/atelier')] class AtelierController extends AbstractController { diff --git a/src/Controller/RegistrationController.php b/src/Controller/RegistrationController.php new file mode 100644 index 0000000..0411e24 --- /dev/null +++ b/src/Controller/RegistrationController.php @@ -0,0 +1,50 @@ +createForm(RegistrationFormType::class, $user); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + // encode the plain password + $user->setPassword( + $userPasswordHasher->hashPassword( + $user, + $form->get('plainPassword')->getData() + ) + ); + + $entityManager->persist($user); + $entityManager->flush(); + // do anything else you need here, like send an email + + return $userAuthenticator->authenticateUser( + $user, + $authenticator, + $request + ); + } + + return $this->render('registration/register.html.twig', [ + 'registrationForm' => $form->createView(), + ]); + } +} diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php new file mode 100644 index 0000000..73da5ac --- /dev/null +++ b/src/Controller/SecurityController.php @@ -0,0 +1,32 @@ +getUser()) { + return $this->redirectToRoute('app_atelier_index'); + } + + // get the login error if there is one + $error = $authenticationUtils->getLastAuthenticationError(); + // last username entered by the user + $lastUsername = $authenticationUtils->getLastUsername(); + + return $this->render('security/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]); + } + + #[Route(path: '/logout', name: 'app_logout')] + public function logout(): void + { + throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.'); + } +} diff --git a/src/Entity/User.php b/src/Entity/User.php new file mode 100644 index 0000000..3114dce --- /dev/null +++ b/src/Entity/User.php @@ -0,0 +1,132 @@ +id; + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function setEmail(string $email): self + { + $this->email = $email; + + return $this; + } + + /** + * A visual identifier that represents this user. + * + * @see UserInterface + */ + public function getUserIdentifier(): string + { + return (string) $this->email; + } + + /** + * @see UserInterface + */ + public function getRoles(): array + { + $roles = $this->roles; + // guarantee every user at least has ROLE_USER + $roles[] = 'ROLE_USER'; + $roles[] = 'ROLE_INSTRUCTOR'; + + return array_unique($roles); + } + + public function setRoles(array $roles): self + { + $this->roles = $roles; + + return $this; + } + + /** + * @see PasswordAuthenticatedUserInterface + */ + public function getPassword(): string + { + return $this->password; + } + + public function setPassword(string $password): self + { + $this->password = $password; + + return $this; + } + + /** + * @see UserInterface + */ + public function eraseCredentials() + { + // If you store any temporary, sensitive data on the user, clear it here + // $this->plainPassword = null; + } + + public function getNom(): ?string + { + return $this->nom; + } + + public function setNom(string $nom): self + { + $this->nom = $nom; + + return $this; + } + + public function getPrenom(): ?string + { + return $this->prenom; + } + + public function setPrenom(string $prenom): self + { + $this->prenom = $prenom; + + return $this; + } +} diff --git a/src/Form/RegistrationFormType.php b/src/Form/RegistrationFormType.php new file mode 100644 index 0000000..ac0b7d8 --- /dev/null +++ b/src/Form/RegistrationFormType.php @@ -0,0 +1,49 @@ +add('email') + ->add('nom') + ->add('prenom') + ->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, + ]), + ], + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => User::class, + ]); + } +} diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php new file mode 100644 index 0000000..9b60483 --- /dev/null +++ b/src/Repository/UserRepository.php @@ -0,0 +1,83 @@ + + * + * @method User|null find($id, $lockMode = null, $lockVersion = null) + * @method User|null findOneBy(array $criteria, array $orderBy = null) + * @method User[] findAll() + * @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, User::class); + } + + public function save(User $entity, bool $flush = false): void + { + $this->getEntityManager()->persist($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + public function remove(User $entity, bool $flush = false): void + { + $this->getEntityManager()->remove($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + /** + * Used to upgrade (rehash) the user's password automatically over time. + */ + public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void + { + if (!$user instanceof User) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', \get_class($user))); + } + + $user->setPassword($newHashedPassword); + + $this->save($user, true); + } + +// /** +// * @return User[] Returns an array of User objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('u') +// ->andWhere('u.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('u.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?User +// { +// return $this->createQueryBuilder('u') +// ->andWhere('u.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/src/Security/AppAuthentificatorAuthenticator.php b/src/Security/AppAuthentificatorAuthenticator.php new file mode 100644 index 0000000..946b252 --- /dev/null +++ b/src/Security/AppAuthentificatorAuthenticator.php @@ -0,0 +1,56 @@ +request->get('email', ''); + + $request->getSession()->set(Security::LAST_USERNAME, $email); + + return new Passport( + new UserBadge($email), + new PasswordCredentials($request->request->get('password', '')), + [ + new CsrfTokenBadge('authenticate', $request->request->get('_csrf_token')), + ] + ); + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response + { + if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) { + return new RedirectResponse($targetPath); + } + + return new RedirectResponse($this->urlGenerator->generate('app_atelier_index')); + } + + protected function getLoginUrl(Request $request): string + { + return $this->urlGenerator->generate(self::LOGIN_ROUTE); + } +} diff --git a/templates/registration/register.html.twig b/templates/registration/register.html.twig new file mode 100644 index 0000000..3f3581f --- /dev/null +++ b/templates/registration/register.html.twig @@ -0,0 +1,19 @@ +{% extends 'base.html.twig' %} + +{% block title %}Register{% endblock %} + +{% block body %} +

Register

+ + {{ form_start(registrationForm) }} + {{ form_row(registrationForm.email) }} + {{ form_row(registrationForm.nom) }} + {{ form_row(registrationForm.prenom) }} + {{ form_row(registrationForm.plainPassword, { + label: 'Password' + }) }} + + + + {{ form_end(registrationForm) }} +{% endblock %} diff --git a/templates/security/login.html.twig b/templates/security/login.html.twig new file mode 100644 index 0000000..efc5c40 --- /dev/null +++ b/templates/security/login.html.twig @@ -0,0 +1,42 @@ +{% extends 'base.html.twig' %} + +{% block title %}Log in!{% endblock %} + +{% block body %} +
+ {% if error %} +
{{ error.messageKey|trans(error.messageData, 'security') }}
+ {% endif %} + + {% if app.user %} +
+ You are logged in as {{ app.user.userIdentifier }}, Logout +
+ {% endif %} + +

Please sign in

+ + + + + + + + {# + Uncomment this section and add a remember_me option below your firewall to activate remember me functionality. + See https://symfony.com/doc/current/security/remember_me.html + +
+ +
+ #} + + +
+{% endblock %}