Lines 78.37% 29 / 37
Functions and Methods 83.33% 5 / 6
Classes and Traits 0.00% 0 / 1
Name Lines Functions and Methods CRAP Classes and Traits
GoogleIdentityAuthenticator 78.37% 29 / 37 83.33% 5 / 6 12.22 0.00% 0 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 supports 100.00% 1 / 1 100.00% 1 / 1 1
 authenticate 27.27% 3 / 11 0.00% 0 / 1 10.15
 onAuthenticationSuccess 100.00% 6 / 6 100.00% 1 / 1 2
 onAuthenticationFailure 100.00% 7 / 7 100.00% 1 / 1 1
 getUser 100.00% 11 / 11 100.00% 1 / 1 2
1<?php
2
3namespace App\Security;
4
5use App\Entity\User;
6use App\Repository\UserRepository;
7use Doctrine\ORM\EntityManagerInterface;
8use League\OAuth2\Client\Provider\GoogleUser;
9use Symfony\Component\HttpFoundation\RedirectResponse;
10use Symfony\Component\HttpFoundation\Request;
11use Symfony\Component\HttpFoundation\Session\Session;
12use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
13use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
14use Symfony\Component\Security\Core\Exception\AuthenticationException;
15use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
16use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
17use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
18use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
19use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
20use Symfony\Component\Security\Http\Util\TargetPathTrait;
21
22class GoogleIdentityAuthenticator extends AbstractAuthenticator
23{
24    use TargetPathTrait;
25
26    public function __construct(
27        private readonly GoogleIdTokenVerifierInterface $idTokenVerifier,
28        private readonly UserRepository $userRepository,
29        private readonly EntityManagerInterface $entityManager,
30        private readonly UrlGeneratorInterface $urlGenerator,
31    ) {
32    }
33
34    #[\Override]
35    public function supports(Request $request): bool
36    {
37        return '/connect/google/verify' === $request->getPathInfo();
38    }
39
40    #[\Override]
41    public function authenticate(Request $request): Passport
42    {
43        $idToken = (string) $request->request->get('credential');
44
45        if ('' === $idToken || '0' === $idToken) {
46            throw new AuthenticationException('Missing credentials :(');
47        }
48
49        $payload = $this->idTokenVerifier->verify($idToken);
50
51        if (null === $payload) {
52            throw new AuthenticationException('Invalid ID token :(');
53        }
54
55        $user = $this->getUser(new GoogleUser($payload));
56
57        return new SelfValidatingPassport(
58            new UserBadge($user->getUserIdentifier()),
59            [new RememberMeBadge()],
60        );
61    }
62
63    #[\Override]
64    public function onAuthenticationSuccess(
65        Request $request,
66        TokenInterface $token,
67        string $firewallName
68    ): RedirectResponse {
69        if ($targetPath = $this->getTargetPath(
70            $request->getSession(),
71            $firewallName
72        )
73        ) {
74            return new RedirectResponse($targetPath);
75        }
76
77        return new RedirectResponse($this->urlGenerator->generate('app_default'));
78    }
79
80    #[\Override]
81    public function onAuthenticationFailure(
82        Request $request,
83        AuthenticationException $exception
84    ): RedirectResponse {
85        $message = \strtr(
86            $exception->getMessageKey(),
87            $exception->getMessageData()
88        );
89
90        /**
91         * @var Session $session
92         */
93        $session = $request->getSession();
94        $session->getFlashBag()->add('danger', $message);
95
96        return new RedirectResponse($this->urlGenerator->generate('app_login'));
97    }
98
99    private function getUser(GoogleUser $googleUser): User
100    {
101        $user = $this->userRepository->findOneBy(
102            ['googleId' => $googleUser->getId()]
103        );
104
105        if (null !== $user) {
106            return $user;
107        }
108
109        // Link to existing account with same email, or register a new user
110        $user = $this->userRepository->findOneBy(['identifier' => $googleUser->getEmail()])
111            ?? new User()->setIdentifier((string) $googleUser->getEmail());
112
113        $user->setGoogleId((string) $googleUser->getId());
114
115        $this->entityManager->persist($user);
116        $this->entityManager->flush();
117
118        return $user;
119    }
120}