1
|
|
|
<?php |
2
|
|
|
declare(strict_types=1); |
3
|
|
|
|
4
|
|
|
namespace SlayerBirden\DataFlowServer\Authentication\Service; |
5
|
|
|
|
6
|
|
|
use Doctrine\Common\Collections\ArrayCollection; |
7
|
|
|
use Doctrine\Common\Collections\Criteria; |
8
|
|
|
use Doctrine\ORM\EntityManager; |
9
|
|
|
use Doctrine\ORM\ORMException; |
10
|
|
|
use Psr\Log\LoggerInterface; |
11
|
|
|
use SlayerBirden\DataFlowServer\Authentication\Entities\Grant; |
12
|
|
|
use SlayerBirden\DataFlowServer\Authentication\Entities\Token; |
13
|
|
|
use SlayerBirden\DataFlowServer\Authentication\Exception\InvalidCredentialsException; |
14
|
|
|
use SlayerBirden\DataFlowServer\Authentication\Exception\PermissionDeniedException; |
15
|
|
|
use SlayerBirden\DataFlowServer\Authentication\PasswordManagerInterface; |
16
|
|
|
use SlayerBirden\DataFlowServer\Authentication\TokenManagerInterface; |
17
|
|
|
use SlayerBirden\DataFlowServer\Authorization\PermissionManagerInterface; |
18
|
|
|
use SlayerBirden\DataFlowServer\Domain\Entities\User; |
19
|
|
|
|
20
|
|
|
class TokenManager implements TokenManagerInterface |
21
|
|
|
{ |
22
|
|
|
/** |
23
|
|
|
* @var PasswordManagerInterface |
24
|
|
|
*/ |
25
|
|
|
private $passwordManager; |
26
|
|
|
/** |
27
|
|
|
* @var EntityManager |
28
|
|
|
*/ |
29
|
|
|
private $entityManager; |
30
|
|
|
/** |
31
|
|
|
* @var LoggerInterface |
32
|
|
|
*/ |
33
|
|
|
private $logger; |
34
|
|
|
/** |
35
|
|
|
* @var PermissionManagerInterface |
36
|
|
|
*/ |
37
|
|
|
private $permissionManager; |
38
|
|
|
|
39
|
12 |
|
public function __construct( |
40
|
|
|
PasswordManagerInterface $passwordManager, |
41
|
|
|
EntityManager $entityManager, |
|
|
|
|
42
|
|
|
LoggerInterface $logger, |
43
|
|
|
PermissionManagerInterface $permissionManager |
44
|
|
|
) { |
45
|
12 |
|
$this->passwordManager = $passwordManager; |
46
|
12 |
|
$this->entityManager = $entityManager; |
47
|
12 |
|
$this->logger = $logger; |
48
|
12 |
|
$this->permissionManager = $permissionManager; |
49
|
12 |
|
} |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* @inheritdoc |
53
|
|
|
* @throws \Exception |
54
|
|
|
*/ |
55
|
3 |
|
public function getToken(string $user, string $password, array $resources): Token |
56
|
|
|
{ |
57
|
|
|
try { |
58
|
3 |
|
$user = $this->getByUsername($user); |
59
|
3 |
|
if ($this->passwordManager->isValidForUser($password, $user)) { |
60
|
2 |
|
$token = new Token(); |
61
|
2 |
|
$now = new \DateTime(); |
62
|
|
|
// default token for 1 month |
63
|
2 |
|
$due = (new \DateTime())->add(new \DateInterval('P1M')); |
64
|
|
|
|
65
|
2 |
|
$token->setActive(true); |
66
|
2 |
|
$token->setCreatedAt($now); |
67
|
2 |
|
$token->setOwner($user); |
68
|
2 |
|
$token->setDue($due); |
69
|
2 |
|
$token->setGrants($this->getGrants($token, $resources, $user)); |
|
|
|
|
70
|
1 |
|
$token->setToken($this->generateToken()); |
71
|
|
|
|
72
|
1 |
|
$this->entityManager->persist($token); |
73
|
1 |
|
$this->entityManager->flush(); |
74
|
|
|
|
75
|
1 |
|
return $token; |
76
|
|
|
} |
77
|
1 |
|
} catch (ORMException $exception) { |
78
|
|
|
$this->logger->error((string)$exception); |
79
|
|
|
throw new InvalidCredentialsException('Unknown ORM error.', $exception); |
80
|
|
|
} |
81
|
|
|
|
82
|
1 |
|
throw new InvalidCredentialsException('Could not validate credentials.'); |
83
|
|
|
} |
84
|
|
|
|
85
|
3 |
|
private function getByUsername(string $user): User |
86
|
|
|
{ |
87
|
3 |
|
$criteria = Criteria::create(); |
88
|
3 |
|
$criteria->where(Criteria::expr()->eq('email', $user)); |
89
|
3 |
|
$users = $this->entityManager->getRepository(User::class)->matching($criteria); |
90
|
|
|
|
91
|
3 |
|
if ($users->count()) { |
92
|
3 |
|
return $users->first(); |
93
|
|
|
} else { |
94
|
|
|
throw new InvalidCredentialsException('Could not find user by provided email.'); |
95
|
|
|
} |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* UUIDv4 |
100
|
|
|
* https://stackoverflow.com/a/15875555/927404 |
101
|
|
|
* |
102
|
|
|
* @return string |
103
|
|
|
*/ |
104
|
6 |
|
private function generateToken(): string |
105
|
|
|
{ |
106
|
6 |
|
$data = random_bytes(16); |
107
|
|
|
|
108
|
6 |
|
$data[6] = chr(ord($data[6]) & 0x0f | 0x40); |
109
|
6 |
|
$data[8] = chr(ord($data[8]) & 0x3f | 0x80); |
110
|
|
|
|
111
|
6 |
|
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* @param Token $token |
116
|
|
|
* @param array $resources |
117
|
|
|
* @param User $user |
118
|
|
|
* @return ArrayCollection|Grant[] |
119
|
|
|
* @throws PermissionDeniedException |
120
|
|
|
* @throws ORMException |
121
|
|
|
*/ |
122
|
9 |
|
private function getGrants(Token $token, array $resources, User $user): ArrayCollection |
123
|
|
|
{ |
124
|
9 |
|
$grants = []; |
125
|
9 |
|
foreach ($resources as $resource) { |
126
|
9 |
|
if (!$this->permissionManager->isAllowed($resource, $user)) { |
127
|
3 |
|
throw new PermissionDeniedException(sprintf('%s is not allowed for provided user.', $resource)); |
128
|
|
|
} |
129
|
6 |
|
$grant = new Grant(); |
130
|
6 |
|
$grant->setToken($token); |
131
|
6 |
|
$grant->setResource($resource); |
132
|
6 |
|
$this->entityManager->persist($grant); |
133
|
6 |
|
$grants[] = $grant; |
134
|
|
|
} |
135
|
|
|
|
136
|
6 |
|
return new ArrayCollection($grants); |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* @param User $user |
141
|
|
|
* @param array $resources |
142
|
|
|
* @return Token |
143
|
|
|
* @throws ORMException |
144
|
|
|
* @throws \Doctrine\ORM\OptimisticLockException |
145
|
|
|
* @throws \Exception |
146
|
|
|
*/ |
147
|
7 |
|
public function getTmpToken(User $user, array $resources): Token |
148
|
|
|
{ |
149
|
7 |
|
$token = new Token(); |
150
|
7 |
|
$now = new \DateTime(); |
151
|
|
|
// tmp token for 1 hour |
152
|
7 |
|
$due = (new \DateTime())->add(new \DateInterval('PT1H')); |
153
|
|
|
|
154
|
7 |
|
$token->setActive(true); |
155
|
7 |
|
$token->setCreatedAt($now); |
156
|
7 |
|
$token->setOwner($user); |
157
|
7 |
|
$token->setDue($due); |
158
|
7 |
|
$token->setGrants($this->getGrants($token, $resources, $user)); |
|
|
|
|
159
|
5 |
|
$token->setToken($this->generateToken()); |
160
|
|
|
|
161
|
5 |
|
$this->entityManager->persist($token); |
162
|
5 |
|
$this->entityManager->flush(); |
163
|
|
|
|
164
|
5 |
|
return $token; |
165
|
|
|
} |
166
|
|
|
} |
167
|
|
|
|
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.