From 5e25f5ee492e3e57249719a6adcb21fb86eca32a Mon Sep 17 00:00:00 2001 From: Eric WAGNER <eric.wagner@etu.unistra.fr> Date: Sat, 1 Apr 2023 13:48:07 +0200 Subject: [PATCH] =?UTF-8?q?Projet=20termin=C3=A9=20avant=20d=C3=A9poloieme?= =?UTF-8?q?nt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/packages/security.yaml | 16 ++++- src/Controller/LoginFormController.php | 43 ++++++++++++++ src/Security/LoginFormAuthenticator.php | 58 +++++++++++++++++++ templates/cart/index.html.twig | 77 ++++++++++--------------- templates/category/show.html.twig | 22 +++---- templates/clothes/index.html.twig | 63 ++++++++++---------- templates/ecurie/index.html.twig | 2 - templates/ecurie/show.html.twig | 52 +++++++++-------- templates/home.html.twig | 4 +- templates/layouts/_header.html.twig | 28 +++++---- templates/security/login.html.twig | 42 ++++++++++++++ 11 files changed, 281 insertions(+), 126 deletions(-) create mode 100644 src/Controller/LoginFormController.php create mode 100644 src/Security/LoginFormAuthenticator.php create mode 100644 templates/security/login.html.twig diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 5f130df..7b32bea 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -17,6 +17,11 @@ security: main: lazy: true provider: app_user_provider + custom_authenticator: App\Security\LoginFormAuthenticator + logout: + path: app_logout + # where to redirect after logout + target: home # activate different ways to authenticate # https://symfony.com/doc/current/security.html#the-firewall @@ -27,8 +32,15 @@ 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 } + # restreint l'accès à la création de châteaux aux admin + - { path: '^/clothes/new', roles: ROLE_ADMIN } + - { path: '^/cayegory/new', roles: ROLE_ADMIN } + - { path: '^/ecurie/new', roles: ROLE_ADMIN } + + # restreint l'accès à l'édition de châteaux aux admin + - { path: '^/clothes/\d+/edit', roles: ROLE_ADMIN } + - { path: '^/cayegory/\d+/edit', roles: ROLE_ADMIN } + - { path: '^/ecurie/\d+/edit', roles: ROLE_ADMIN } when@test: security: diff --git a/src/Controller/LoginFormController.php b/src/Controller/LoginFormController.php new file mode 100644 index 0000000..7880f2b --- /dev/null +++ b/src/Controller/LoginFormController.php @@ -0,0 +1,43 @@ +<?php + +namespace App\Controller; + +use App\Entity\Category; +use App\Form\CategoryType; +use App\Repository\CategoryRepository; + +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; + +class LoginFormController extends AbstractController +{ + #[Route(path: '/login', name: 'app_login')] + public function login(AuthenticationUtils $authenticationUtils): Response + { + // if ($this->getUser()) { + // return $this->redirectToRoute('target_path'); + // } + + $categoryRepository = $this->getDoctrine() + ->getRepository(Category::class); + + // 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, + 'categories' => $categoryRepository->findAll(), + ]); + } + + #[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/Security/LoginFormAuthenticator.php b/src/Security/LoginFormAuthenticator.php new file mode 100644 index 0000000..a5fb5d1 --- /dev/null +++ b/src/Security/LoginFormAuthenticator.php @@ -0,0 +1,58 @@ +<?php + +namespace App\Security; + +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; +use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Component\Security\Http\Util\TargetPathTrait; + +class LoginFormAuthenticator extends AbstractLoginFormAuthenticator +{ + use TargetPathTrait; + + public const LOGIN_ROUTE = 'app_login'; + + public function __construct(private UrlGeneratorInterface $urlGenerator) + { + } + + public function authenticate(Request $request): Passport + { + $username = $request->request->get('username', ''); + + $request->getSession()->set(Security::LAST_USERNAME, $username); + + return new Passport( + new UserBadge($username), + 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); + } + + // For example: + return new RedirectResponse($this->urlGenerator->generate('home')); + // throw new \Exception('TODO: provide a valid redirect inside '.__FILE__); + } + + protected function getLoginUrl(Request $request): string + { + return $this->urlGenerator->generate(self::LOGIN_ROUTE); + } +} diff --git a/templates/cart/index.html.twig b/templates/cart/index.html.twig index 7bb4f85..d0646e5 100644 --- a/templates/cart/index.html.twig +++ b/templates/cart/index.html.twig @@ -3,51 +3,38 @@ {% block title %}Votre Panier{% endblock %} {% block body %} - <table> - <thead> - <tr> - <td> - Clothes - </td> - <td> - Price - </td> - <td> - Quantite - </td> - <td> - Total - </td> - <td> - Actions - </td> - </tr> - </thead> - <tbody> + <div class="my-6"> + <h1 class="text-6xl text-center">Total <span class="text-[#00AA00]">{{total}} €</span></h1> + </div> + + <div class="flex justify-center"> + <ul class="max-w-[80%] flex flex-wrap"> {% for element in dataPanier %} - <tr> - <td>{{element.clothes.name}}</td> - <td>{{element.clothes.price}} €</td> - <td>{{element.quantite}}</td> - <td>{{element.quantite * element.clothes.price}} €</td> - <td> - <a href="{{ path('app_cart_add', {id: element.clothes.id}) }}"><i class="bi bi-cart-plus"></i></a> - <a href="{{ path('app_cart_remove', {id: element.clothes.id}) }}"><i class="bi bi-cart-dash"></i></a> - <a href="{{ path('app_cart_delete', {id: element.clothes.id}) }}"><i class="bi bi-cart-x"></i></a> - </td> - </tr> - {% else %} - <tr> - <td colspan="5" class="text-center">Votre Panier est vide</td> - </tr> + <li class=" border-b-2 min-h-[710px] max-h-[710px] my-4"> + <article class="flex flex-col justify-center"> + <img src="{{ element.clothes.picture }}" alt="{{ element.clothes.name }}" class="h-[500px] w-[500px]"> + <div class="p-5 w-full"> + <div class="flex flex-row justify-between"> + <h3 class="text-[#CC0000] font-bold">{{ element.clothes.price }} €</h3> + <h3 class="text-[#0000AA] font-bold">x{{element.quantite}}</h3> + <h3 class="text-[#00AA00] font-bold">{{element.quantite * element.clothes.price}} €</h3> + </div> + <p class="min-h-[50px] max-h-[50px]">{{ element.clothes.name }}</p> + <div class="flex flex-row justify-center items-center gap-8"> + <div class="min-w-[50px] min-h-[50px] p-2 bg-[#00AA00] rounded-full text-[#EEEEEE] font-bold text-2xl text-center"> + <a href="{{ path('app_cart_add', {id: element.clothes.id}) }}"><i class="bi bi-cart-plus"></i></a> + </div> + <div class="min-w-[50px] min-h-[50px] p-2 bg-[#0000AA] rounded-full text-[#EEEEEE] font-bold text-2xl text-center"> + <a href="{{ path('app_cart_remove', {id: element.clothes.id}) }}"><i class="bi bi-cart-dash"></i></a> + </div> + <div class="min-w-[50px] min-h-[50px] p-2 bg-[#AA0000] rounded-full text-[#EEEEEE] font-bold text-2xl text-center"> + <a href="{{ path('app_cart_delete', {id: element.clothes.id}) }}"><i class="bi bi-cart-x"></i></a> + </div> + </div> + </div> + </article> + </li> {% endfor %} - </tbody> - <tfoot> - <tr> - <td colspan="3">Total</td> - <td>{{ total }} €</td> - <td></td> - </tr> - </tfoot> - </table> + </ul> + </div> {% endblock %} \ No newline at end of file diff --git a/templates/category/show.html.twig b/templates/category/show.html.twig index d1c49da..ee9a4f4 100644 --- a/templates/category/show.html.twig +++ b/templates/category/show.html.twig @@ -8,27 +8,29 @@ <div class="flex justify-center"> <ul class="max-w-[80%] flex flex-wrap"> {% for clothes in category.clothes %} - <li class="max-w-[33%] border-b-2"> + <li class="max-w-[33%] border-b-2 min-h-[710px] max-h-[710px]"> <article class="flex flex-col justify-center"> - <img src="{{ clothes.picture }}" alt="{{ clothes.name }}"> + <img src="{{ clothes.picture }}" alt="{{ clothes.name }}" class="h-[500px] w-[500px]"> <div class="p-5 w-full"> <h3 class="text-[#CC0000] font-bold">{{ clothes.price }} €</h3> - <p>{{ clothes.name }}</p> + <p class="min-h-[50px] max-h-[50px]">{{ clothes.name }}</p> <a href="{{ path('app_cart_add', {id: clothes.id}) }}" class=""> - <div class="w-full text-center p-3 bg-[#06e100] rounded-xl text-[#111111] font-bold my-5 hover:text-[#EEEEEE]"> + <div class="w-full text-center p-3 bg-[#06e100] rounded-xl text-[#111111] font-bold mt-5 hover:text-[#EEEEEE]"> <i class="bi bi-cart-plus"></i> Ajouter au Panier </div> </a> + {% if is_granted('ROLE_ADMIN') %} + <div class="flex flex-row justify-center"> + <form method="post" action="{{ path('app_clothes_delete', {'id': clothes.id}) }}" onsubmit="return confirm('Êtes vous sûr de vouloir supprimer ce Vêtement ?');"> + <input type="hidden" name="_token" value="{{ csrf_token('delete' ~ clothes.id) }}"> + <button class="btn w-fit text-[#AA0000]"><i class="bi bi-trash-fill text-sm"></i></button> + </form> + </div> + {% endif %} </div> </article> </li> {% endfor %} </ul> </div> - - <a href="{{ path('app_category_index') }}">back to list</a> - - <a href="{{ path('app_category_edit', {'id': category.id}) }}">edit</a> - - {{ include('category/_delete_form.html.twig') }} {% endblock %} diff --git a/templates/clothes/index.html.twig b/templates/clothes/index.html.twig index b557909..7110557 100644 --- a/templates/clothes/index.html.twig +++ b/templates/clothes/index.html.twig @@ -3,37 +3,36 @@ {% block title %}Clothes index{% endblock %} {% block body %} - <h1>Clothes index</h1> + <div class="my-6"> + <h1 class="text-6xl text-center">Tous les Vêtements</h1> + </div> - <table class="table"> - <thead> - <tr> - <th>Id</th> - <th>Name</th> - <th>Picture</th> - <th>Price</th> - <th>actions</th> - </tr> - </thead> - <tbody> - {% for clothes in clothes %} - <tr> - <td>{{ clothes.id }}</td> - <td>{{ clothes.name }}</td> - <td><img src="{{ clothes.picture }}" alt=""></td> - <td>{{ clothes.price }}</td> - <td> - <a href="{{ path('app_clothes_show', {'id': clothes.id}) }}">show</a> - <a href="{{ path('app_clothes_edit', {'id': clothes.id}) }}">edit</a> - </td> - </tr> - {% else %} - <tr> - <td colspan="5">no records found</td> - </tr> - {% endfor %} - </tbody> - </table> - - <a href="{{ path('app_clothes_new') }}">Create new</a> + <div class="flex justify-center"> + <ul class="max-w-[80%] flex flex-wrap"> + {% for clothe in clothes %} + <li class="max-w-[33%] border-b-2 min-h-[710px] max-h-[710px]"> + <article class="flex flex-col justify-center"> + <img src="{{ clothe.picture }}" alt="{{ clothe.name }}" class="h-[500px] w-[500px]"> + <div class="p-5 w-full"> + <h3 class="text-[#CC0000] font-bold">{{ clothe.price }} €</h3> + <p class="min-h-[50px] max-h-[50px]">{{ clothe.name }}</p> + <a href="{{ path('app_cart_add', {id: clothe.id}) }}" class=""> + <div class="w-full text-center p-3 bg-[#06e100] rounded-xl text-[#111111] font-bold mt-5 hover:text-[#EEEEEE]"> + <i class="bi bi-cart-plus"></i> Ajouter au Panier + </div> + </a> + {% if is_granted('ROLE_ADMIN') %} + <div class="flex flex-row justify-center"> + <form method="post" action="{{ path('app_clothes_delete', {'id': clothe.id}) }}" onsubmit="return confirm('Êtes vous sûr de vouloir supprimer ce Vêtement ?');"> + <input type="hidden" name="_token" value="{{ csrf_token('delete' ~ clothe.id) }}"> + <button class="btn w-fit text-[#AA0000]"><i class="bi bi-trash-fill text-sm"></i></button> + </form> + </div> + {% endif %} + </div> + </article> + </li> + {% endfor %} + </ul> + </div> {% endblock %} diff --git a/templates/ecurie/index.html.twig b/templates/ecurie/index.html.twig index aa2020b..9dd78af 100644 --- a/templates/ecurie/index.html.twig +++ b/templates/ecurie/index.html.twig @@ -16,6 +16,4 @@ {% endfor %} </ul> </div> - - <a href="{{ path('app_ecurie_new') }}">Create new</a> {% endblock %} diff --git a/templates/ecurie/show.html.twig b/templates/ecurie/show.html.twig index 779341e..f688173 100644 --- a/templates/ecurie/show.html.twig +++ b/templates/ecurie/show.html.twig @@ -3,28 +3,34 @@ {% block title %}Ecurie{% endblock %} {% block body %} - <h1>Ecurie</h1> +<h1 class="text-3xl capitalize px-[5rem]">{{ ecurie.name }}</h1> - <table class="table"> - <tbody> - <tr> - <th>Id</th> - <td>{{ ecurie.id }}</td> - </tr> - <tr> - <th>Name</th> - <td>{{ ecurie.name }}</td> - </tr> - <tr> - <th>Picture</th> - <td>{{ ecurie.picture }}</td> - </tr> - </tbody> - </table> - - <a href="{{ path('app_ecurie_index') }}">back to list</a> - - <a href="{{ path('app_ecurie_edit', {'id': ecurie.id}) }}">edit</a> - - {{ include('ecurie/_delete_form.html.twig') }} + <div class="flex justify-center"> + <ul class="max-w-[80%] flex flex-wrap"> + {% for clothes in ecurie.clothes %} + <li class="max-w-[33%] border-b-2 min-h-[710px] max-h-[710px]"> + <article class="flex flex-col justify-center"> + <img src="{{ clothes.picture }}" alt="{{ clothes.name }}" class="h-[500px] w-[500px]"> + <div class="p-5 w-full"> + <h3 class="text-[#CC0000] font-bold">{{ clothes.price }} €</h3> + <p class="min-h-[50px] max-h-[50px]">{{ clothes.name }}</p> + <a href="{{ path('app_cart_add', {id: clothes.id}) }}" class=""> + <div class="w-full text-center p-3 bg-[#06e100] rounded-xl text-[#111111] font-bold mt-5 hover:text-[#EEEEEE]"> + <i class="bi bi-cart-plus"></i> Ajouter au Panier + </div> + </a> + {% if is_granted('ROLE_ADMIN') %} + <div class="flex flex-row justify-center"> + <form method="post" action="{{ path('app_clothes_delete', {'id': clothes.id}) }}" onsubmit="return confirm('Êtes vous sûr de vouloir supprimer ce Vêtement ?');"> + <input type="hidden" name="_token" value="{{ csrf_token('delete' ~ clothes.id) }}"> + <button class="btn w-fit text-[#AA0000]"><i class="bi bi-trash-fill text-sm"></i></button> + </form> + </div> + {% endif %} + </div> + </article> + </li> + {% endfor %} + </ul> + </div> {% endblock %} diff --git a/templates/home.html.twig b/templates/home.html.twig index 61bbec7..df7748d 100644 --- a/templates/home.html.twig +++ b/templates/home.html.twig @@ -12,7 +12,7 @@ {% endfor %} </ul> </div> - <a href=""> - <img src="{{ asset('img/DT_A_spot_2023_Teamwear_FR.avif') }}" alt=""> + <a href="{{ path('app_clothes_index') }}"> + <img src="{{ asset('img/DT_A_spot_2023_Teamwear_FR.avif') }}" alt="" class="w-full"> </a> {% endblock %} \ No newline at end of file diff --git a/templates/layouts/_header.html.twig b/templates/layouts/_header.html.twig index 6a0fd0c..54b2039 100644 --- a/templates/layouts/_header.html.twig +++ b/templates/layouts/_header.html.twig @@ -5,20 +5,28 @@ </a> </div> <div class="flex flex-row justify-end gap-5"> - <ul class="flex-flex-row justify-end items-center h-[40px]"> + <ul class="flex flex-row justify-end items-center h-[40px]"> <li class="flex items-center h-[40px] pl-[7px] pr-[5px] text-[#EEEEEE]"> - <a href="" class="hover:underline">Mon Compte</a> + {% if app.user %} + <a href="{{ path('app_logout') }}" class="hover:underline">{{ app.user.username }}</a> + {% else %} + <a href="{{ path('app_login') }}" class="hover:underline">Connexion</a> + {% endif %} </li> + {% if is_granted('ROLE_ADMIN') %} + <li class="flex items-center h-[40px] pl-[7px] pr-[5px] text-[#EEEEEE]"> + <a href="{{ path('app_clothes_new') }}">Ajouter des Vêtements</a> + </li> + <li class="flex items-center h-[40px] pl-[7px] pr-[5px] text-[#EEEEEE]"> + <a href="{{ path('app_category_new') }}">Ajouter une Catégorie</a> + </li> + {% endif %} </ul> - <div class="w-[60px] h-[40px] bg-[#e10600] text-center pt-[0.5rem] pb-[0.4375rem] text-[#EEEEEE] hover:text-[#e10600] hover:bg-[#EEEEEE]" role="complementary"> - <a href="/cart" - data-trk-id="cart-icon" - data-talos="linkCartIcon" - {# class="{{ path('app_cart_show', {id: app.user.id}) }}" #} - title="Vous avez 0 articles dans le panier. Le total est de 0,00 €"> + <a href="{{ path('app_cart_index') }}" class="h-fit"> + <div class="w-[60px] h-[40px] bg-[#e10600] text-center pt-[0.5rem] pb-[0.4375rem] text-[#EEEEEE] hover:text-[#e10600] hover:bg-[#EEEEEE]" role="complementary"> <i class="bi bi-cart-fill"></i> - </a> - </div> + </div> + </a> </div> </header> <nav> diff --git a/templates/security/login.html.twig b/templates/security/login.html.twig new file mode 100644 index 0000000..6c19154 --- /dev/null +++ b/templates/security/login.html.twig @@ -0,0 +1,42 @@ +{% extends 'base.html.twig' %} + +{% block title %}Log in!{% endblock %} + +{% block body %} +<form method="post"> + {% if error %} + <div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div> + {% endif %} + + {% if app.user %} + <div class="mb-3"> + You are logged in as {{ app.user.userIdentifier }}, <a href="{{ path('app_logout') }}">Logout</a> + </div> + {% endif %} + + <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1> + <label for="inputUsername">Username</label> + <input type="text" value="{{ last_username }}" name="username" id="inputUsername" class="form-control" autocomplete="username" required autofocus> + <label for="inputPassword">Password</label> + <input type="password" name="password" id="inputPassword" class="form-control" autocomplete="current-password" required> + + <input type="hidden" name="_csrf_token" + value="{{ csrf_token('authenticate') }}" + > + + {# + 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 + + <div class="checkbox mb-3"> + <label> + <input type="checkbox" name="_remember_me"> Remember me + </label> + </div> + #} + + <button class="btn btn-lg btn-primary" type="submit"> + Sign in + </button> +</form> +{% endblock %} -- GitLab