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