Passed
Push — master ( ead99e...4691ae )
by Bukashk0zzz
02:06
created

JWTAuthenticator::getCredentials()   B

Complexity

Conditions 9
Paths 15

Size

Total Lines 30
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 19
nc 15
nop 1
dl 0
loc 30
rs 8.0555
c 0
b 0
f 0
1
<?php declare(strict_types = 1);
2
3
namespace AtlassianConnectBundle\Security;
4
5
use AtlassianConnectBundle\Entity\TenantInterface;
6
use AtlassianConnectBundle\Service\QSHGenerator;
7
use Doctrine\Common\Persistence\ManagerRegistry;
8
use Doctrine\ORM\EntityManager;
9
use Firebase\JWT\JWT;
10
use Symfony\Component\HttpFoundation\Request;
11
use Symfony\Component\HttpFoundation\Response;
12
use Symfony\Component\HttpKernel\KernelInterface;
13
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
14
use Symfony\Component\Security\Core\Exception\AuthenticationException;
15
use Symfony\Component\Security\Core\User\UserInterface;
16
use Symfony\Component\Security\Core\User\UserProviderInterface;
17
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
18
19
/**
20
 * Class JWTAuthenticator
21
 */
22
class JWTAuthenticator extends AbstractGuardAuthenticator
23
{
24
    /**
25
     * @var JWTUserProvider
26
     */
27
    protected $userProvider;
28
29
    /**
30
     * @var KernelInterface
31
     */
32
    protected $kernel;
33
34
    /**
35
     * @var EntityManager
36
     */
37
    protected $em;
38
39
    /**
40
     * @var string
41
     */
42
    protected $tenantEntityClass;
43
44
    /**
45
     * @var int
46
     */
47
    protected $devTenant;
48
49
    /**
50
     * JWTAuthenticator constructor.
51
     *
52
     * @param JWTUserProvider $userProvider
53
     * @param KernelInterface $kernel
54
     * @param ManagerRegistry $registry
55
     * @param string          $tenantEntityClass
56
     * @param int             $devTenant
57
     */
58
    public function __construct(JWTUserProvider $userProvider, KernelInterface $kernel, ManagerRegistry $registry, string $tenantEntityClass, int $devTenant)
59
    {
60
        $this->userProvider = $userProvider;
61
        $this->kernel = $kernel;
62
        $this->em = $registry->getManager();
63
        $this->tenantEntityClass = $tenantEntityClass;
64
        $this->devTenant = $devTenant;
65
    }
66
67
    /**
68
     * @param Request                      $request
69
     * @param AuthenticationException|null $authException
70
     *
71
     * @return Response
72
     */
73
    public function start(Request $request, ?AuthenticationException $authException = null): Response
74
    {
75
        return new Response('Authentication header required', 401);
76
    }
77
78
    /**
79
     * @param Request $request
80
     *
81
     * @return bool
82
     */
83
    public function supports(Request $request): bool
84
    {
85
        return $request->query->has('jwt') ||
86
            $request->headers->has('authorization') ||
87
            ($this->devTenant && ($this->kernel->getEnvironment() === 'dev'));
88
    }
89
90
    /**
91
     * @param Request $request
92
     *
93
     * @return mixed Any non-null value
94
     */
95
    public function getCredentials(Request $request)
96
    {
97
        $jwt = $request->query->get('jwt');
98
        if (!$jwt && $request->headers->has('authorization')) {
99
            $authorizationHeaderArray = \explode(' ', $request->headers->get('authorization'));
100
            if (\count($authorizationHeaderArray) > 1) {
101
                $jwt = $authorizationHeaderArray[1];
102
            }
103
        }
104
105
        if (!$jwt && $this->devTenant && ($this->kernel->getEnvironment() === 'dev')) {
106
            $tenant = $this->em->getRepository($this->tenantEntityClass)->find($this->devTenant);
107
            if ($tenant === null) {
108
                throw new \RuntimeException(\sprintf('Cant find tenant with id %s - please set atlassian_connect.dev_tenant to false to disable dedicated dev tenant OR add valid id', $this->devTenant));
109
            }
110
111
            $jwt = JWT::encode([
112
                'iss' => $tenant->getClientKey(),
113
                'iat' => \time(),
114
                'exp' => \strtotime('+1 day'),
115
                'qsh' => QSHGenerator::generate($request->getRequestUri(), 'GET'),
116
                'sub' => 'admin',
117
            ], $tenant->getSharedSecret());
118
        }
119
120
        if (!$jwt) {
121
            return null;
122
        }
123
124
        return ['jwt' => $jwt];
125
    }
126
127
    /**
128
     * @param mixed                 $credentials
129
     * @param UserProviderInterface $userProvider
130
     *
131
     * @return UserInterface|null
132
     */
133
    public function getUser($credentials, UserProviderInterface $userProvider): ?UserInterface
134
    {
135
        $token = $this->userProvider->getDecodedToken($credentials['jwt']);
136
        $clientKey = $token->iss;
137
138
        if (!$clientKey) {
139
            throw new AuthenticationException(
140
                \sprintf('API Key "%s" does not exist.', $credentials['jwt'])
141
            );
142
        }
143
144
        /** @var TenantInterface|UserInterface $user */
145
        $user = $this->userProvider->loadUserByUsername($clientKey);
146
        if (\property_exists($token, 'sub')) {
147
            // for some reasons, when webhooks are called - field sub is undefined
148
            $user->setUsername($token->sub);
149
        }
150
151
        return $user;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $user returns the type AtlassianConnectBundle\Entity\TenantInterface which is incompatible with the type-hinted return Symfony\Component\Securi...User\UserInterface|null.
Loading history...
152
    }
153
154
    /**
155
     * @param mixed         $credentials
156
     * @param UserInterface $user
157
     *
158
     * @return bool
159
     */
160
    public function checkCredentials($credentials, UserInterface $user): bool
161
    {
162
        return true;
163
    }
164
165
    /**
166
     * @param Request                 $request
167
     * @param AuthenticationException $exception
168
     *
169
     * @return Response|null
170
     */
171
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
172
    {
173
        return new Response('Authentication Failed: '.$exception->getMessage(), 403);
174
    }
175
176
    /**
177
     * @param Request        $request
178
     * @param TokenInterface $token
179
     * @param mixed|string   $providerKey The provider (i.e. firewall) key
180
     *
181
     * @return Response|null
182
     */
183
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): ?Response
184
    {
185
        return null;
186
    }
187
188
    /**
189
     * @return bool
190
     */
191
    public function supportsRememberMe(): bool
192
    {
193
        return false;
194
    }
195
}
196