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