diff --git a/.env b/.env index 3af2b76..303d9c0 100644 --- a/.env +++ b/.env @@ -48,3 +48,7 @@ OAUTH_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem OAUTH_PASSPHRASE=8170ea18d2e3e05b5c7ae0672a754bf4 OAUTH_ENCRYPTION_KEY=f1b7c279f7992205a0df45e295d07066 ###< league/oauth2-server-bundle ### + +###> nelmio/cors-bundle ### +CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$' +###< nelmio/cors-bundle ### diff --git a/assets/controllers.json b/assets/controllers.json index 480ad64..3ed9c65 100644 --- a/assets/controllers.json +++ b/assets/controllers.json @@ -11,7 +11,7 @@ }, "@symfony/ux-turbo": { "turbo-core": { - "enabled": true, + "enabled": false, "fetch": "eager" }, "mercure-turbo-stream": { diff --git a/composer.json b/composer.json index 98d405f..0a1aa61 100644 --- a/composer.json +++ b/composer.json @@ -12,6 +12,7 @@ "doctrine/doctrine-migrations-bundle": "^3.4", "doctrine/orm": "^3.3", "league/oauth2-server-bundle": "^0.11.0", + "nelmio/cors-bundle": "^2.5", "phpdocumentor/reflection-docblock": "^5.6", "phpstan/phpdoc-parser": "^2.1", "symfony/asset": "7.2.*", diff --git a/composer.lock b/composer.lock index 4fb8bff..8134a85 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": "2848578c0a7baf9e3d125fcc61becab2", + "content-hash": "685b46acb679219276b83724c15afb9d", "packages": [ { "name": "composer/semver", @@ -2084,6 +2084,68 @@ ], "time": "2025-03-24T10:02:05+00:00" }, + { + "name": "nelmio/cors-bundle", + "version": "2.5.0", + "source": { + "type": "git", + "url": "https://github.com/nelmio/NelmioCorsBundle.git", + "reference": "3a526fe025cd20e04a6a11370cf5ab28dbb5a544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nelmio/NelmioCorsBundle/zipball/3a526fe025cd20e04a6a11370cf5ab28dbb5a544", + "reference": "3a526fe025cd20e04a6a11370cf5ab28dbb5a544", + "shasum": "" + }, + "require": { + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "mockery/mockery": "^1.3.6", + "symfony/phpunit-bridge": "^5.4 || ^6.0 || ^7.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Nelmio\\CorsBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nelmio", + "homepage": "http://nelm.io" + }, + { + "name": "Symfony Community", + "homepage": "https://github.com/nelmio/NelmioCorsBundle/contributors" + } + ], + "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Symfony application", + "keywords": [ + "api", + "cors", + "crossdomain" + ], + "support": { + "issues": "https://github.com/nelmio/NelmioCorsBundle/issues", + "source": "https://github.com/nelmio/NelmioCorsBundle/tree/2.5.0" + }, + "time": "2024-06-24T21:25:28+00:00" + }, { "name": "nyholm/psr7", "version": "1.8.2", diff --git a/config/bundles.php b/config/bundles.php index e10698c..5552afd 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -16,4 +16,5 @@ return [ Symfony\UX\TogglePassword\TogglePasswordBundle::class => ['all' => true], Symfony\UX\Icons\UXIconsBundle::class => ['all' => true], League\Bundle\OAuth2ServerBundle\LeagueOAuth2ServerBundle::class => ['all' => true], + Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true], ]; diff --git a/config/jwt/private.pem b/config/jwt/private.pem index d28d652..9cd5ffe 100644 --- a/config/jwt/private.pem +++ b/config/jwt/private.pem @@ -1,30 +1,30 @@ -----BEGIN ENCRYPTED PRIVATE KEY----- -MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIo7xCOI7GcgECAggA -MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECJMiC+tT9+rhBIIEyB04+Jb/N38i -ZyG37vEEL7wX2l8VFZt5qCwEYMJj3WvdiiYZNRw1mOA1ZdlDVnwjNxDn/svzFdnv -NXDTCeuKQOade02ySh6DFsI4IGWPhIq56kIGNolxMoqW1VlDaJbrW6qgeUcbHqlc -V09MIMAE3fs5JeM7qR2b+6exiBrk2L4b/+IdJKlDJztkd6yIo/47VDcmk9a2l0AT -wHip0FEvyjEC6UoKLCfDf07gRQp6c9YSYqoLDFnTdUqMEXk2ZRWwO7WnGGWpOCcA -0Wurzdrdm0n/i80153tKKkJLFbzKOxZAmcV/pzRrI7iw5yDeDQ+WAU8N6rm8c151 -tg8ACUX3gg1otkwO6HZ8z6p7ki1UUmIn06wwTz1lTQDewy2Un6jzpKsVoZ/JJc7p -XVXYGfym1+MRn7nU0gmepVw8o11813qVFAJePmxq+3RPILVZ8YSI0Qe3Pf19NNOS -gfEyylKxHOnwxkDOVoU2c58pu921zwJFS+93sXh5uy83FpNqso10m1n/cr8XiMYc -WX5qfgUoPgB9poC++9xCT6sISTZWLOLiIGuzBoNNi0kHX/1bco8mxRUk9TbjMuNi -Zrx7KARwtY/ddfLD9DPxLYYWHh65zQCrtplY3ILbiXw4mUrJqPhpgw7tWsoDmF/X -vQV/ZQQHjbM5UmCq1zCYq2meoeqV5e1ixyNpfe4xIgCAfwEw9UytQ+uQ5L/XAcGM -AE3diuQMSw8UKMcslqKQtDGdQIuD5STRIjKp/L5/Ks5u50cjuvQ5xI6mLmwBB2G2 -0eMBqSNQFMqAqI1lDSHZSk1tNCqcWYbNaaqPSx4VMW99sWy+gNJK4vSGD99RRDWV -VI9nmjB8/FsY81lDaHBFjq8VyLglu6eEzij3j5dDUFeedYb4OqnUZtIg2H+TSXnj -mxwbImsucCUVHOrCc6JOvXZOnTCK4qum4pGpDxzp2xtYuPOlOVSsCwysXNcr77wD -4i+3fSh0M3iB0dsrRwVqZ9ZLS2+5zgaLxoem6mR5Gg4OesK7Xf6mtgBrpD5mOAGp -zTuj9wwQUajh3kRPhKzfzr2XqtsGiZsSjBUtOvV5PimhUdpPMYcRT7odcnxcJOhU -Xde4/DGoxgJWmtei4BwMMLUexP94bGKA5w318PJAZ5qV2gY4MXhIgDn+HLEJ1tK7 -EBuuvGk+PRQElwVHTuOhGWvE7hyDA5Z2jnxGNtyntFWJfFddocTEyx6A/rPrbcBm -DFINWQ6JZIY/xTLXVfF7fKx+fQpqe6R2gZrYNJ5G3Z4/nbyuRaq/bENoKbd+O51f -LeRsyXLu5FbBFM4S61LZ/BseMHMxf3Q7l9gtp3EUrurIz36KZ2fPUVdqMsp2dvZ4 -z8aFGQrBcwKS3u9iwrf64w/LEsVIGhmxFuL8KMqG949wgd/CjnvDbzot6A3ioGSd -kl62Z3rU1i0Y8T9ubdbuabpKGxpmRAHo0Y4nrnHZTLqvEeW3NCOMmOF6OjBg8Q+s -pLbgCIjsr6LapdMzj2GiBL0no69uRO4Si+cFaMyMkowMbqoo+cB6z7jqbsTc++i3 -y+uJKGrXeqS9Fwj4QaK4NRzWo/wYRmvFyo0hjxeRmXRQR4DZ85zGn+9mNmzQa+uH -bqPMXh92TaQXrWxDgzO9Ag== +MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIf5Dm9gHr5xICAggA +MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECAmIm56CqfdaBIIEyIeYoi/NL/yC +EM3P0ZEesYY08FoEjaAonqDAFB2w8caOfSc2quZSij7zaDAjU5ehg7X/3kWqypUW +F1bhMn5v3Lad6XOgXeTigYaLZBXshazuqnbqs9xBU00MPkwetl1CT4ATUzvEPPq1 +LOQ2FfEUfBZAR51PVYvKJwwctzYW42PNh8940XqskAs6kfWQ3qZPlnCRjQDfuKcI +YhKeJz6cCYt5JHqBGC5mRiFkRqDe10gks0/yUCmds9MpKPLYKP9MrUs8QcxWpakf +ypUMPM2mSik4U+D7Gdq7CqulqDPLm+8drKgUeq6wTgWwodpGnUe5FeG+cx4hxH5x +fAKuP6gscvYk3p5ir138TVW4uEFehkBw7yHpqUqSjN5jIlmX634siYoNhWP6Ek32 +nl+gGUlITWSC6cewiqpL7IfQr+DDXpTPcN5Lu9+6rmh4P0XGE+J2a/tdXeK3xdMZ +3MyFItGNIxklL+yuvRBJ2o4D5JRDhiSwmXdFR5WOJYN0SRKYkEnmjAAHnhK6eWLP +bsD0tdUilZvyGmcPMXkM3HjfAQKqDldi8rmdqe8IJHpCKZ5twryy/eb1EArofN0U +H9XIs/pdJldg2HTJVIen5iAONqMSB4LyxMUfXWnvg/qPazvgYZvKbTXyp0iVhJ77 +UcWyIO6xx/3BvnuO/eCgelu+eNQqI9UOMhVr7X0gGpXb88g+Gu8scrlWDVTM6o9P +j1rOuAYaZX/69jGOmH3QqUkILcwkfevSuNUBgLOilh/mabR+tOfM2o6/8Z1Zd1PS +nplYzvDs/Pib71PEF64DVCRlG2QioqV2MT7gOenShyZhZS4+oxJ3nWf9Z9n5jwDJ +XSqkHRD8CDZZS0f1FOET1c+GZtoyfxGHdGyLM8shA6IRnxBoAKWVW8vdTfyPJFcy +vq9gPvlrfyuREcRKeSnySzkAQbYLVH631tLcsbsd5yfyYS0o6BY1mNDi1j83Xfmp +Or2a137ZKKTfRtSJybj+QOiBXMR8uJnR9HPjRQn8DrMYzcgv/kw48AIUy2+4sZIw +cPxrtKFyhWIYSnHil4Ri/cENZApbbo1UQc4ktyjuyxyI20gx21vfaHWbx6GpUNwG +H551tioa5P4cte4syOxMi89KRdCYKGgUTibhEfMODD6rA/l3W9PGINqOGu3Z2SxN +4HGV4oJx6Nvw1vTq2phSVo3Qp9JyvO3FFE8oA/b+ElL0PoYjsLsNiKBxggPvZgKp +nTOFYYUMQl1+6CUf2iSR0b8VLhRM5o9N+Xwp2D2SEjWZvMhIKQxxmwVd3aSn8ebQ +CSW0lGFjltwc5FEvw1aNmhx7K1gSCMcBnSEx53ghLU5xCA+50g5brM1joZn3sQ2c +WsY/dLNtfqlTnKSeVQKyLEZj6Xl9/KYyVg3yYt43HuSz3FAt6ekGloA9rVR3v7tZ +m6tBWyOqQG4tnp8+V/2CMGnpq2VQABqxwZxpm5u+tbfSlonwrXpiy3/fgsYZJE02 +kVwNb4FPClKjyVZGRrWfITaS4Gxm+hDdfKVipunIzS+7MADwwYyibPVXyuCw1NTg +pt89WCTt0csNNGcpd1m/rrH5J080fEHGXWpeobbTbeiYsK0+qeYQXEeMvE7lyFIP +SzDJqo7z76xQc3dcBX8MNQ== -----END ENCRYPTED PRIVATE KEY----- diff --git a/config/jwt/public.pem b/config/jwt/public.pem index 97002d8..38afe8c 100644 --- a/config/jwt/public.pem +++ b/config/jwt/public.pem @@ -1,9 +1,9 @@ -----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQueIPrQEJyma0oiV2wG -9gl4mpjZQx0QDj/HXyi2hqTjd6z9cfcONmlggD7xoLuiNNmTuVNezHMMC4VNq8/G -zNQu7Gp18K0uw0WXWWpHtslE3yz9c30FPB4whpz+NMlSXiQEaA2xJxIPxgaMrCG2 -vc8hMPqiN5pid9ErdkGJLaZd9Q/HqIvVPmw9pVK6HTogfHu61hiaHtA5wDxetFH2 -l7V0oXcbES7fpTXetlNNpIcQ5j5G04HCPWNl8abCcKNUMoDjAXcvKnXNTBaDSfSZ -+JxMjjtVpU8r7sEDmQRlh4CeRqYfimNusm8WO3Yod+PLO33doUhEwBMJOu1s3+oG -rQIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwZehUUiAV9vMER+tCpKB +lLr53bCV2z34r7Qg4gojciR1n7J6esptuLo/JMIYsU1EFmH8dIJ9guc/IufW7hDj +iwB2l75brVJ+EG8RRN2R4/IlUXz01a56W+TgLGyVM9iNegfwrNQjEEr3WNnz878h +0KQDsG3+kndh60pIuIiiEqG/yoO9C4enyL5XEDcsyKebb5PeCSTewgGm+DY7vj1F +FSZMXxEnOQP/Symz8st1KS7DbbYma/OCkrAsZ+iHC1Ozis0D28uxcdyrPZi36bkV +MKNST42uV86CTEan2yaHaynRUFNHC+bZjA+izOALtg3CyguOqrmL1LL5ID8Q1K8f +jQIDAQAB -----END PUBLIC KEY----- diff --git a/config/packages/league_oauth2_server.yaml b/config/packages/league_oauth2_server.yaml index 6b415b6..ecc8f57 100644 --- a/config/packages/league_oauth2_server.yaml +++ b/config/packages/league_oauth2_server.yaml @@ -3,6 +3,10 @@ league_oauth2_server: private_key: '%env(resolve:OAUTH_PRIVATE_KEY)%' private_key_passphrase: '%env(resolve:OAUTH_PASSPHRASE)%' encryption_key: '%env(resolve:OAUTH_ENCRYPTION_KEY)%' + access_token_ttl: PT3H # 3 hours + refresh_token_ttl: P1M # 1 month + auth_code_ttl: PT10M # 10 minutes + require_code_challenge_for_public_clients: false resource_server: public_key: '%env(resolve:OAUTH_PUBLIC_KEY)%' scopes: diff --git a/config/packages/nelmio_cors.yaml b/config/packages/nelmio_cors.yaml new file mode 100644 index 0000000..d0f632d --- /dev/null +++ b/config/packages/nelmio_cors.yaml @@ -0,0 +1,30 @@ +nelmio_cors: + defaults: + origin_regex: true + allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] + allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'] + allow_headers: ['Content-Type', 'Authorization'] + expose_headers: ['Link'] + max_age: 3600 + paths: + '^/token$': + origin_regex: true + allow_origin: ['*'] + allow_headers: ['Content-Type', 'Authorization'] + allow_methods: ['POST', 'OPTIONS'] + allow_credentials: true + max_age: 3600 + '^/authorize$': + origin_regex: true + allow_origin: ['*'] + allow_headers: ['Content-Type', 'Authorization'] + allow_methods: ['GET', 'POST', 'OPTIONS'] + allow_credentials: true + max_age: 3600 + '^/login$': + origin_regex: true + allow_origin: ['*'] + allow_headers: ['Content-Type', 'Authorization'] + allow_methods: ['GET', 'POST', 'OPTIONS'] + allow_credentials: true + max_age: 3600 \ No newline at end of file diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 330c3ed..9a21c5b 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -48,6 +48,7 @@ security: - { path: ^/login, roles: PUBLIC_ACCESS } - { path: ^/token, roles: PUBLIC_ACCESS } - { path: ^/oauth2/token, roles: PUBLIC_ACCESS } + - { path: ^/token, roles: PUBLIC_ACCESS } - { path: ^/authorize, roles: IS_AUTHENTICATED_REMEMBERED } - { path: ^/oauth2/userinfo, roles: IS_AUTHENTICATED_FULLY } - { path: ^/, roles: ROLE_USER } diff --git a/config/routes.yaml b/config/routes.yaml index 41ef814..2ca147f 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -2,4 +2,4 @@ controllers: resource: path: ../src/Controller/ namespace: App\Controller - type: attribute + type: attribute \ No newline at end of file diff --git a/config/services.yaml b/config/services.yaml index 2d6a76f..b82a1fa 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -19,6 +19,9 @@ services: - '../src/DependencyInjection/' - '../src/Entity/' - '../src/Kernel.php' + App\EventSubscriber\: + resource: '../src/EventSubscriber/' + tags: ['kernel.event_subscriber'] # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones diff --git a/src/Controller/OAuth2Controller.php b/src/Controller/OAuth2Controller.php index f606682..65564e3 100644 --- a/src/Controller/OAuth2Controller.php +++ b/src/Controller/OAuth2Controller.php @@ -5,6 +5,7 @@ namespace App\Controller; use App\Entity\User; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; class OAuth2Controller extends AbstractController @@ -17,7 +18,7 @@ class OAuth2Controller extends AbstractController return new JsonResponse([ 'message' => 'Authentification réussie !', 'email' => $user->getEmail(), - 'name' => $user->getUsername(), + 'name' => $user->getName() ]); } @@ -33,7 +34,7 @@ class OAuth2Controller extends AbstractController return new JsonResponse([ 'sub' => $user->getId(), - 'username' => $user->getUsername(), + 'username' => $user->getName(), 'email' => $user->getEmail(), 'roles' => $user->getRoles(), ]); diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index c6aa096..1c5130d 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -6,9 +6,18 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; +use Symfony\Component\HttpFoundation\Request; +use App\Service\CguUserService; class SecurityController extends AbstractController { + private CguUserService $cguUserService; + + public function __construct(CguUserService $cguUserService) + { + $this->cguUserService = $cguUserService; + } + #[Route(path: '/login', name: 'app_login')] public function login(AuthenticationUtils $authenticationUtils): Response { @@ -31,4 +40,23 @@ class SecurityController extends AbstractController { throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.'); } + + #[Route(path: '/consent', name: 'app_consent')] + public function consent(Request $request): Response + { + // Handle form submission + if ($request->isMethod('POST')) { + // Check if user declined consent + if (!$request->request->has('decline')) { + // User accepted the CGU, save this in the database + $this->cguUserService->acceptLatestCgu($this->getUser()); + } + + // Redirect back to the OAuth authorization endpoint with all the query parameters + return $this->redirectToRoute('oauth2_authorize', $request->query->all()); + } + + // For GET requests, just show the consent form + return $this->render('security/consent.html.twig'); + } } diff --git a/src/EventSubscriber/AuthorizationCodeSubscriber.php b/src/EventSubscriber/AuthorizationCodeSubscriber.php index f41cedd..626ab8b 100644 --- a/src/EventSubscriber/AuthorizationCodeSubscriber.php +++ b/src/EventSubscriber/AuthorizationCodeSubscriber.php @@ -11,6 +11,7 @@ use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Http\FirewallMapInterface; use Symfony\Component\Security\Http\Util\TargetPathTrait; +use App\Service\CguUserService; class AuthorizationCodeSubscriber implements EventSubscriberInterface { @@ -20,35 +21,42 @@ class AuthorizationCodeSubscriber implements EventSubscriberInterface private UrlGeneratorInterface $urlGenerator; private RequestStack $requestStack; private $firewallName; + private $cguUserService; - public function __construct(Security $security, UrlGeneratorInterface $urlGenerator, RequestStack $requestStack, FirewallMapInterface $firewallMap) + public function __construct(Security $security, UrlGeneratorInterface $urlGenerator, RequestStack $requestStack, FirewallMapInterface $firewallMap, CguUserService $cguUserService) { $this->security = $security; $this->urlGenerator = $urlGenerator; $this->requestStack = $requestStack; $this->firewallName = $firewallMap->getFirewallConfig($requestStack->getCurrentRequest())->getName(); + $this->cguUserService = $cguUserService; } public function onLeagueOauth2ServerEventAuthorizationRequestResolve(AuthorizationRequestResolveEvent $event): void { $request = $this->requestStack->getCurrentRequest(); $user = $this->security->getUser(); - $this->saveTargetPath($request->getSession(), $this->firewallName, $request->getUri()); + + // Default response is to redirect to login if not logged in $response = new RedirectResponse($this->urlGenerator->generate('app_login'), 307); - if ($user instanceof UserInterface) { - //On approuve le consentement automatiquement - $event->resolveAuthorization(true); - $request->getSession()->remove('consent_granted'); + + if (!$user instanceof UserInterface) { + // Save the target path and redirect to login + $this->saveTargetPath($request->getSession(), $this->firewallName, $request->getUri()); + $event->setResponse($response); return; - //Decommenter et implemeter pour rediriger vers les constentement - /*if ($request->getSession()->get('consent_granted') !== null) { - $event->resolveAuthorization($request->getSession()->get('consent_granted')); - $request->getSession()->remove('consent_granted'); - return; - } - $response = new RedirectResponse($this->urlGenerator->generate('app_consent', $request->query->all()), 307);*/ } - $event->setResponse($response); + + // User is logged in, check if they've accepted the latest CGU + if (!$this->cguUserService->isLatestCguAccepted($user)) { + // Redirect to consent page with all query parameters + $response = new RedirectResponse($this->urlGenerator->generate('app_consent', $request->query->all()), 307); + $event->setResponse($response); + return; + } + + // User has accepted CGU, authorize the request + $event->resolveAuthorization(true); } public static function getSubscribedEvents(): array diff --git a/src/Repository/CguRepository.php b/src/Repository/CguRepository.php index 924547b..b1a8a46 100644 --- a/src/Repository/CguRepository.php +++ b/src/Repository/CguRepository.php @@ -16,6 +16,15 @@ class CguRepository extends ServiceEntityRepository parent::__construct($registry, Cgu::class); } + public function findLatestCgu(): ?Cgu + { + return $this->createQueryBuilder('c') + ->orderBy('c.id', 'DESC') + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + } + // /** // * @return Cgu[] Returns an array of Cgu objects // */ diff --git a/src/Service/CguUserService.php b/src/Service/CguUserService.php new file mode 100644 index 0000000..a6f2961 --- /dev/null +++ b/src/Service/CguUserService.php @@ -0,0 +1,53 @@ +entityManager->getRepository(Cgu::class)->findLatestCgu(); + if (!$latestCgu) { + // If no CGU exists, set to false + return false; + } + + $cguUser = $this->entityManager->getRepository(CguUser::class)->findOneBy(['users' => $user, 'cgu' => $latestCgu]); + if (!$cguUser) { + // If the relation doesn't exist, the user hasn't seen or accepted the latest CGU + return false; + } + + return $cguUser->isAccepted(); + } + + public function acceptLatestCgu(UserInterface $user): void + { + $latestCgu = $this->entityManager->getRepository(Cgu::class)->findLatestCgu(); + if (!$latestCgu) { + // No CGU to accept + return; + } + + $cguUser = $this->entityManager->getRepository(CguUser::class)->findOneBy(['users' => $user, 'cgu' => $latestCgu]); + if (!$cguUser) { + // Create a new CguUser relation if it doesn't exist + $cguUser = new CguUser(); + $cguUser->setUsers($user); + $cguUser->setCgu($latestCgu); + $this->entityManager->persist($cguUser); + } + + $cguUser->setIsAccepted(true); + $this->entityManager->flush(); + } +} diff --git a/symfony.lock b/symfony.lock index c4bfb47..5996d0f 100644 --- a/symfony.lock +++ b/symfony.lock @@ -39,6 +39,18 @@ "config/routes/league_oauth2_server.yaml" ] }, + "nelmio/cors-bundle": { + "version": "2.5", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.5", + "ref": "6bea22e6c564fba3a1391615cada1437d0bde39c" + }, + "files": [ + "config/packages/nelmio_cors.yaml" + ] + }, "nyholm/psr7": { "version": "1.8", "recipe": { diff --git a/templates/elements/navbar.html.twig b/templates/elements/navbar.html.twig index 2e544bf..0ffaf77 100644 --- a/templates/elements/navbar.html.twig +++ b/templates/elements/navbar.html.twig @@ -63,12 +63,13 @@ {{ ux_icon('bi:menu-up', {height: '20px', width: '20px'}) }}
-
diff --git a/templates/security/consent.html.twig b/templates/security/consent.html.twig new file mode 100644 index 0000000..8f7e04c --- /dev/null +++ b/templates/security/consent.html.twig @@ -0,0 +1,36 @@ +{% extends 'base.html.twig' %} + +{% block title %} Consent {% endblock %} + +{% block body %} + + {% if app.user %} +
+
+

Data Usage Consent

+
+
+

Dear {{ app.user.userIdentifier }},

+

We request your consent to process your personal data. By agreeing, you acknowledge that:

+
    +
  • We will store and process your personal information securely
  • +
  • Your data will only be used for the purposes specified in our privacy policy
  • +
  • You can withdraw your consent at any time
  • +
+ +
+
+ + +
+ + +
+
+
+
+
+ {% endif %} +{% endblock %} \ No newline at end of file