Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
20.69% covered (danger)
20.69%
6 / 29
50.00% covered (danger)
50.00%
3 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
GoogleIdentityAuthenticator
20.69% covered (danger)
20.69%
6 / 29
50.00% covered (danger)
50.00%
3 / 6
59.89
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 supports
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 authenticate
25.00% covered (danger)
25.00%
3 / 12
0.00% covered (danger)
0.00%
0 / 1
10.75
 getSuccessRedirectUrl
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFailureRedirectUrl
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUser
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3declare(strict_types=1);
4
5namespace App\Security;
6
7use App\Entity\User;
8use App\Repository\UserRepository;
9use Doctrine\ORM\EntityManagerInterface;
10use Google\Client;
11use League\OAuth2\Client\Provider\GoogleUser;
12use Symfony\Component\HttpFoundation\Request;
13use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
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    use AuthenticationResultTrait;
26
27    public function __construct(
28        private readonly string $oauthGoogleId,
29        private readonly UserRepository $userRepository,
30        private readonly EntityManagerInterface $entityManager,
31        private readonly UrlGeneratorInterface $urlGenerator,
32    ) {}
33
34    public function supports(Request $request): bool
35    {
36        return $request->getPathInfo() === '/connect/google/verify';
37    }
38
39    public function authenticate(Request $request): Passport
40    {
41        $idToken = (string)$request->request->get('credential');
42
43        if ($idToken === '' || $idToken === '0') {
44            throw new AuthenticationException('Missing credentials :(');
45        }
46
47        $payload = new Client(['client_id' => $this->oauthGoogleId])
48            ->verifyIdToken($idToken);
49
50        if (!$payload) {
51            throw new AuthenticationException('Invalid ID token :(');
52        }
53
54        $user = $this->getUser(new GoogleUser($payload));
55
56        return new SelfValidatingPassport(
57            new UserBadge($user->getUserIdentifier()),
58            [new RememberMeBadge()],
59        );
60    }
61
62    private function getSuccessRedirectUrl(): string
63    {
64        return $this->urlGenerator->generate('default');
65    }
66
67    private function getFailureRedirectUrl(): string
68    {
69        return $this->urlGenerator->generate('login');
70    }
71
72    private function getUser(GoogleUser $googleUser): User
73    {
74        $user = $this->userRepository->findOneBy(
75            ['googleId' => $googleUser->getId()]
76        );
77
78        if ($user instanceof User) {
79            return $user;
80        }
81
82        // Register a new user
83        /** @var string $email */
84        $email = $googleUser->getEmail();
85        /** @var string $googleId */
86        $googleId = $googleUser->getId();
87        $user = new User()
88            ->setIdentifier($email)
89            ->setGoogleId($googleId);
90
91        $this->entityManager->persist($user);
92        $this->entityManager->flush();
93
94        return $user;
95    }
96}