Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.72% covered (warning)
83.72%
36 / 43
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
GoogleIdentityAuthenticator
83.72% covered (warning)
83.72%
36 / 43
83.33% covered (warning)
83.33%
5 / 6
12.62
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
50.00% covered (danger)
50.00%
7 / 14
0.00% covered (danger)
0.00%
0 / 1
6.00
 getUser
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
3
 onAuthenticationSuccess
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 onAuthenticationFailure
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace App\Security;
6
7use App\Entity\User;
8use App\Repository\UserRepository;
9use App\Type\GoogleUser;
10use Doctrine\ORM\EntityManagerInterface;
11use Google\Client;
12use Override;
13use Symfony\Component\DependencyInjection\Attribute\Autowire;
14use Symfony\Component\HttpFoundation\RedirectResponse;
15use Symfony\Component\HttpFoundation\Request;
16use Symfony\Component\HttpFoundation\Session\Session;
17use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
18use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
19use Symfony\Component\Security\Core\Exception\AuthenticationException;
20use Symfony\Component\Security\Core\Exception\UserNotFoundException;
21use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
22use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
23use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
24use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
25use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
26use Symfony\Component\Security\Http\Util\TargetPathTrait;
27
28class GoogleIdentityAuthenticator extends AbstractAuthenticator
29{
30    use TargetPathTrait;
31
32    public function __construct(
33        #[Autowire('%env(OAUTH_GOOGLE_ID)%')]
34        private readonly string $oauthGoogleId,
35        private readonly UserRepository $userRepository,
36        private readonly EntityManagerInterface $entityManager,
37        private readonly UrlGeneratorInterface $urlGenerator,
38    ) {}
39
40    #[Override]
41    public function supports(Request $request): bool
42    {
43        return '/connect/google/verify' === $request->getPathInfo();
44    }
45
46    #[Override]
47    public function authenticate(Request $request): Passport
48    {
49        $idToken = (string)$request->request->get('credential');
50
51        if ($idToken === '' || $idToken === '0') {
52            throw new AuthenticationException('Missing credentials :(');
53        }
54
55        $payload = new Client([
56            'client_id' => $this->oauthGoogleId,
57        ])
58            ->verifyIdToken($idToken);
59
60        if (!$payload) {
61            throw new AuthenticationException('Invalid ID token :(');
62        }
63
64        $user = $this->getUser(new GoogleUser($payload));
65
66        return new SelfValidatingPassport(
67            new UserBadge($user->getUserIdentifier()),
68            [new RememberMeBadge()],
69        );
70    }
71
72    private function getUser(GoogleUser $googleUser): User
73    {
74        $user = $this->userRepository->findOneBy(
75            [
76                'googleId' => $googleUser->getId(),
77            ]
78        );
79
80        if ($user) {
81            return $user;
82        }
83
84        // @todo remove: Fetch user by email
85        $user = $this->userRepository->findOneBy(
86            [
87                'email' => $googleUser->getEmail(),
88            ]
89        );
90
91        if ($user) {
92            // @todo remove: Update existing users google id
93            $user->setGoogleId($googleUser->getId());
94            $this->entityManager->persist($user);
95            $this->entityManager->flush();
96
97            return $user;
98        }
99
100        throw new UserNotFoundException('You are not allowed to login. Please contact an administrator. - '.$googleUser->getEmail());
101    }
102
103    #[Override]
104    public function onAuthenticationSuccess(
105        Request $request,
106        TokenInterface $token,
107        string $firewallName
108    ): RedirectResponse
109    {
110        if ($targetPath = $this->getTargetPath(
111            $request->getSession(),
112            $firewallName
113        )
114        ) {
115            return new RedirectResponse($targetPath);
116        }
117
118        return new RedirectResponse($this->urlGenerator->generate('welcome'));
119    }
120
121    #[Override]
122    public function onAuthenticationFailure(
123        Request $request,
124        AuthenticationException $exception
125    ): RedirectResponse
126    {
127        /**
128         * @var Session $session
129         */
130        $session = $request->getSession();
131        $session->getFlashBag()->add('danger', $exception->getMessage());
132
133        return new RedirectResponse($this->urlGenerator->generate('login'));
134    }
135}