1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Overwatch\UserBundle\Security; |
4
|
|
|
|
5
|
|
|
use Doctrine\ORM\EntityManager; |
6
|
|
|
use Symfony\Component\HttpFoundation\JsonResponse; |
7
|
|
|
use Symfony\Component\HttpFoundation\Request; |
8
|
|
|
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken; |
9
|
|
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; |
10
|
|
|
use Symfony\Component\Security\Core\Exception\AuthenticationException; |
11
|
|
|
use Symfony\Component\Security\Core\Exception\BadCredentialsException; |
12
|
|
|
use Symfony\Component\Security\Core\User\UserProviderInterface; |
13
|
|
|
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; |
14
|
|
|
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* ApiAuthenticator |
18
|
|
|
* |
19
|
|
|
* @author Zac Sturgess <[email protected]> |
20
|
|
|
*/ |
21
|
|
|
class ApiAuthenticator implements SimplePreAuthenticatorInterface, AuthenticationFailureHandlerInterface |
22
|
|
|
{ |
23
|
|
|
const USER_ID = 'x-api-user'; |
24
|
|
|
const TIMESTAMP = 'x-api-timestamp'; |
25
|
|
|
const TOKEN = 'x-api-token'; |
26
|
|
|
|
27
|
|
|
private $em; |
28
|
|
|
|
29
|
148 |
|
public function __construct(EntityManager $em) |
|
|
|
|
30
|
|
|
{ |
31
|
148 |
|
$this->em = $em; |
32
|
148 |
|
} |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @param string $providerKey |
36
|
|
|
*/ |
37
|
5 |
|
public function createToken(Request $request, $providerKey) |
38
|
|
|
{ |
39
|
|
|
if ( |
40
|
5 |
|
!$request->headers->has(self::USER_ID) || |
41
|
3 |
|
!$request->headers->has(self::TIMESTAMP) || |
42
|
3 |
|
!$request->headers->has(self::TOKEN) |
43
|
5 |
|
) { |
44
|
2 |
|
throw new BadCredentialsException('API credentials invalid. The user ID, timestamp and token should given.'); |
45
|
|
|
} |
46
|
|
|
|
47
|
3 |
|
if (!is_numeric($request->headers->get(self::USER_ID))) { |
48
|
1 |
|
throw new BadCredentialsException('API credentials invalid. The user ID should be an integer.'); |
49
|
|
|
} |
50
|
|
|
|
51
|
2 |
|
if (!is_numeric($request->headers->get(self::TIMESTAMP))) { |
52
|
1 |
|
throw new BadCredentialsException('API credentials invalid. The timestamp should be an integer.'); |
53
|
|
|
} |
54
|
|
|
|
55
|
1 |
|
return new PreAuthenticatedToken( |
56
|
1 |
|
'anon.', |
57
|
|
|
[ |
58
|
1 |
|
self::USER_ID => $request->headers->get(self::USER_ID), |
59
|
1 |
|
self::TIMESTAMP => $request->headers->get(self::TIMESTAMP), |
60
|
1 |
|
self::TOKEN => $request->headers->get(self::TOKEN), |
61
|
1 |
|
], |
62
|
|
|
$providerKey |
63
|
1 |
|
); |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* @param string $providerKey |
68
|
|
|
*/ |
69
|
6 |
|
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey) |
70
|
|
|
{ |
71
|
6 |
|
$credentials = $token->getCredentials(); |
72
|
|
|
|
73
|
6 |
|
if (abs(time() - (int) $credentials[self::TIMESTAMP]) > 60) { |
74
|
2 |
|
throw new AuthenticationException('API credentials invalid. The timestamp is more than 60 seconds old.'); |
75
|
|
|
} |
76
|
|
|
|
77
|
4 |
|
$user = $this->em->find('OverwatchUserBundle:User', $credentials[self::USER_ID]); |
78
|
|
|
|
79
|
4 |
|
if ($user === null || $user->isLocked()) { |
80
|
2 |
|
throw new AuthenticationException('API credentials invalid. User not found.'); |
81
|
|
|
} |
82
|
|
|
|
83
|
2 |
|
$apiToken = hash_hmac( |
84
|
2 |
|
'sha256', |
85
|
2 |
|
'timestamp=' . $credentials[self::TIMESTAMP], |
86
|
2 |
|
$user->getApiKey() |
87
|
2 |
|
); |
88
|
|
|
|
89
|
2 |
|
if ($apiToken !== $credentials[self::TOKEN]) { |
90
|
1 |
|
throw new AuthenticationException('API credentials invalid. Token verification failed.'); |
91
|
|
|
} |
92
|
|
|
|
93
|
1 |
|
return new PreAuthenticatedToken( |
94
|
1 |
|
$user, |
95
|
1 |
|
$token->getCredentials(), |
96
|
1 |
|
$providerKey, |
97
|
1 |
|
$user->getRoles() |
98
|
1 |
|
); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* @param string $providerKey |
103
|
|
|
*/ |
104
|
3 |
|
public function supportsToken(TokenInterface $token, $providerKey) |
105
|
|
|
{ |
106
|
3 |
|
return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey; |
107
|
|
|
} |
108
|
|
|
|
109
|
2 |
|
public function onAuthenticationFailure(Request $request, AuthenticationException $exception) |
110
|
|
|
{ |
111
|
2 |
|
return new JsonResponse($exception->getMessage(), JsonResponse::HTTP_UNAUTHORIZED); |
112
|
|
|
} |
113
|
|
|
} |
114
|
|
|
|
The
EntityManager
might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:If that code throws an exception and the
EntityManager
is closed. Any other code which depends on the same instance of theEntityManager
during this request will fail.On the other hand, if you instead inject the
ManagerRegistry
, thegetManager()
method guarantees that you will always get a usable manager instance.