|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Palladium\Service; |
|
4
|
|
|
|
|
5
|
|
|
/** |
|
6
|
|
|
* Retrieval and handling of identities for registered users |
|
7
|
|
|
*/ |
|
8
|
|
|
|
|
9
|
|
|
use RuntimeException; |
|
10
|
|
|
|
|
11
|
|
|
use Palladium\Component\MapperFactory; |
|
12
|
|
|
use Palladium\Mapper as Mapper; |
|
13
|
|
|
use Palladium\Entity as Entity; |
|
14
|
|
|
|
|
15
|
|
|
use Palladium\Exception\IdentityDuplicated; |
|
16
|
|
|
use Palladium\Exception\IdentityNotFound; |
|
17
|
|
|
use Palladium\Exception\EmailNotFound; |
|
18
|
|
|
use Palladium\Exception\PasswordNotMatch; |
|
19
|
|
|
use Palladium\Exception\CompromisedCookie; |
|
20
|
|
|
use Palladium\Exception\DenialOfServiceAttempt; |
|
21
|
|
|
use Palladium\Exception\IdentityExpired; |
|
22
|
|
|
use Palladium\Exception\Community\UserNotFound; |
|
23
|
|
|
|
|
24
|
|
|
use Palladium\Contract\CanCreateMapper; |
|
25
|
|
|
use Psr\Log\LoggerInterface; |
|
26
|
|
|
|
|
27
|
|
|
class Identification |
|
28
|
|
|
{ |
|
29
|
|
|
|
|
30
|
|
|
private $currentCookie; |
|
31
|
|
|
|
|
32
|
|
|
private $mapperFactory; |
|
33
|
|
|
private $logger; |
|
34
|
|
|
|
|
35
|
|
|
|
|
36
|
1 |
|
public function __construct(CanCreateMapper $mapperFactory, LoggerInterface $logger) |
|
37
|
|
|
{ |
|
38
|
1 |
|
$this->mapperFactory = $mapperFactory; |
|
39
|
1 |
|
$this->logger = $logger; |
|
40
|
1 |
|
} |
|
41
|
|
|
|
|
42
|
1 |
|
public function loginWithPassword(Entity\EmailIdentity $identity, $password) |
|
43
|
|
|
{ |
|
44
|
1 |
View Code Duplication |
if ($identity->matchKey($password) === false) { |
|
|
|
|
|
|
45
|
|
|
$this->logger->warning('wrong password', [ |
|
46
|
|
|
'input' => [ |
|
47
|
|
|
'identifier' => $identity->getIdentifier(), |
|
48
|
|
|
'key' => md5($password), |
|
49
|
|
|
], |
|
50
|
|
|
'account' => [ |
|
51
|
|
|
'user' => $identity->getUserId(), |
|
52
|
|
|
'identity' => $identity->getId(), |
|
53
|
|
|
], |
|
54
|
|
|
]); |
|
55
|
|
|
|
|
56
|
|
|
throw new PasswordNotMatch; |
|
57
|
|
|
} |
|
58
|
|
|
|
|
59
|
1 |
|
$this->registerUsageOfIdentity($identity); |
|
60
|
1 |
|
$cookie = $this->createCookieIdentity($identity); |
|
61
|
|
|
|
|
62
|
1 |
|
$this->logger->info('login successful', [ |
|
63
|
|
|
'input' => [ |
|
64
|
1 |
|
'identifier' => $identity->getIdentifier(), |
|
65
|
|
|
], |
|
66
|
|
|
'account' => [ |
|
67
|
1 |
|
'user' => $identity->getUserId(), |
|
68
|
1 |
|
'identity' => $identity->getId(), |
|
69
|
|
|
], |
|
70
|
|
|
]); |
|
71
|
|
|
|
|
72
|
1 |
|
$this->currentCookie = $cookie; |
|
73
|
|
|
|
|
74
|
1 |
|
return $cookie; |
|
75
|
|
|
} |
|
76
|
|
|
|
|
77
|
|
|
|
|
78
|
1 |
|
private function registerUsageOfIdentity(Entity\Identity $identity) |
|
79
|
|
|
{ |
|
80
|
1 |
|
$identity->setLastUsed(time()); |
|
81
|
|
|
|
|
82
|
1 |
|
$mapper = $this->mapperFactory->create(Mapper\Identity::class); |
|
83
|
1 |
|
$mapper->store($identity); |
|
84
|
1 |
|
} |
|
85
|
|
|
|
|
86
|
|
|
|
|
87
|
1 |
|
private function createCookieIdentity(Entity\EmailIdentity $identity) |
|
88
|
|
|
{ |
|
89
|
1 |
|
$cookie = new Entity\CookieIdentity; |
|
90
|
1 |
|
$mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class); |
|
91
|
|
|
|
|
92
|
1 |
|
$cookie->setUserId($identity->getUserId()); |
|
93
|
1 |
|
$cookie->generateNewSeries(); |
|
94
|
|
|
|
|
95
|
1 |
|
while ($mapper->exists($cookie)) { |
|
96
|
|
|
// just a failsafe, to prevent violation of constraint |
|
97
|
|
|
$cookie->generateNewSeries(); |
|
98
|
|
|
} |
|
99
|
|
|
|
|
100
|
1 |
|
$cookie->generateNewKey(); |
|
101
|
1 |
|
$cookie->setStatus(Entity\Identity::STATUS_ACTIVE); |
|
102
|
1 |
|
$cookie->setExpiresOn(time() + Entity\Identity::COOKIE_LIFESPAN); |
|
103
|
|
|
|
|
104
|
1 |
|
$mapper->store($cookie); |
|
105
|
|
|
|
|
106
|
1 |
|
return $cookie; |
|
107
|
|
|
} |
|
108
|
|
|
|
|
109
|
|
|
|
|
110
|
|
|
public function authenticateWithCookie($userId, $series, $key) |
|
111
|
|
|
{ |
|
112
|
|
|
$identity = $this->retrieveIdenityByCookie($userId, $series, Entity\Identity::STATUS_ACTIVE); |
|
113
|
|
|
|
|
114
|
|
View Code Duplication |
if ($identity->getId() === null) { |
|
|
|
|
|
|
115
|
|
|
$this->logger->error('denial of service', [ |
|
116
|
|
|
'input' => [ |
|
117
|
|
|
'user' => $userId, |
|
118
|
|
|
'series' => $series, |
|
119
|
|
|
'key' => $key, |
|
120
|
|
|
], |
|
121
|
|
|
'account' => [ |
|
122
|
|
|
'user' => $identity->getUserId(), |
|
123
|
|
|
'identity' => $identity->getId(), |
|
124
|
|
|
], |
|
125
|
|
|
]); |
|
126
|
|
|
|
|
127
|
|
|
throw new DenialOfServiceAttempt; |
|
128
|
|
|
} |
|
129
|
|
|
|
|
130
|
|
|
$mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class); |
|
131
|
|
|
|
|
132
|
|
View Code Duplication |
if ($identity->getExpiresOn() < time()) { |
|
|
|
|
|
|
133
|
|
|
$identity->setStatus(Entity\Identity::STATUS_EXPIRED); |
|
134
|
|
|
$mapper->store($identity); |
|
135
|
|
|
$this->logger->info('cookie expired', [ |
|
136
|
|
|
'input' => [ |
|
137
|
|
|
'user' => $userId, |
|
138
|
|
|
'series' => $series, |
|
139
|
|
|
'key' => $key, |
|
140
|
|
|
], |
|
141
|
|
|
'account' => [ |
|
142
|
|
|
'user' => $identity->getUserId(), |
|
143
|
|
|
'identity' => $identity->getId(), |
|
144
|
|
|
], |
|
145
|
|
|
]); |
|
146
|
|
|
|
|
147
|
|
|
throw new IdentityExpired; |
|
148
|
|
|
} |
|
149
|
|
|
|
|
150
|
|
View Code Duplication |
if ($identity->matchKey($key) === false) { |
|
|
|
|
|
|
151
|
|
|
$identity->setStatus(Entity\Identity::STATUS_BLOCKED); |
|
152
|
|
|
$mapper->store($identity); |
|
153
|
|
|
|
|
154
|
|
|
$this->logger->error('compromised cookie', [ |
|
155
|
|
|
'input' => [ |
|
156
|
|
|
'user' => $userId, |
|
157
|
|
|
'series' => $series, |
|
158
|
|
|
'key' => $key, |
|
159
|
|
|
], |
|
160
|
|
|
'account' => [ |
|
161
|
|
|
'user' => $identity->getUserId(), |
|
162
|
|
|
'identity' => $identity->getId(), |
|
163
|
|
|
], |
|
164
|
|
|
]); |
|
165
|
|
|
|
|
166
|
|
|
throw new CompromisedCookie; |
|
167
|
|
|
} |
|
168
|
|
|
|
|
169
|
|
|
$identity->generateNewKey(); |
|
170
|
|
|
$identity->setLastUsed(time()); |
|
171
|
|
|
$identity->setExpiresOn(time() + Entity\Identity::COOKIE_LIFESPAN); |
|
172
|
|
|
|
|
173
|
|
|
$mapper->store($identity); |
|
174
|
|
|
|
|
175
|
|
|
$this->logger->info('cookie updated', [ |
|
176
|
|
|
'account' => [ |
|
177
|
|
|
'user' => $identity->getUserId(), |
|
178
|
|
|
'identity' => $identity->getId(), |
|
179
|
|
|
], |
|
180
|
|
|
]); |
|
181
|
|
|
|
|
182
|
|
|
$this->currentCookie = $identity; |
|
183
|
|
|
} |
|
184
|
|
|
|
|
185
|
|
|
|
|
186
|
|
|
private function retrieveIdenityByCookie($userId, $series, $status = Entity\Identity::STATUS_ANY) |
|
187
|
|
|
{ |
|
188
|
|
|
$cookie = new Entity\CookieIdentity; |
|
189
|
|
|
$mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class); |
|
190
|
|
|
|
|
191
|
|
|
$cookie->setUserId($userId); |
|
192
|
|
|
$cookie->setSeries($series); |
|
193
|
|
|
$cookie->setStatus($status); |
|
194
|
|
|
|
|
195
|
|
|
$mapper->fetch($cookie); |
|
196
|
|
|
|
|
197
|
|
|
return $cookie; |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
|
|
|
|
201
|
|
|
public function discardCookie($userId, $series, $key) |
|
202
|
|
|
{ |
|
203
|
|
|
$identity = $this->retrieveIdenityByCookie($userId, $series, Entity\Identity::STATUS_ACTIVE); |
|
204
|
|
|
|
|
205
|
|
View Code Duplication |
if ($identity->getId() === null) { |
|
|
|
|
|
|
206
|
|
|
$this->logger->error('denial of service', [ |
|
207
|
|
|
'input' => [ |
|
208
|
|
|
'user' => $userId, |
|
209
|
|
|
'series' => $series, |
|
210
|
|
|
'key' => $key, |
|
211
|
|
|
], |
|
212
|
|
|
'account' => [ |
|
213
|
|
|
'user' => $identity->getUserId(), |
|
214
|
|
|
'identity' => $identity->getId(), |
|
215
|
|
|
], |
|
216
|
|
|
]); |
|
217
|
|
|
|
|
218
|
|
|
throw new DenialOfServiceAttempt; |
|
219
|
|
|
} |
|
220
|
|
|
|
|
221
|
|
|
$mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class); |
|
222
|
|
|
|
|
223
|
|
View Code Duplication |
if ($identity->matchKey($key) === false) { |
|
|
|
|
|
|
224
|
|
|
$identity->setStatus(Entity\Identity::STATUS_BLOCKED); |
|
225
|
|
|
$mapper->store($identity); |
|
226
|
|
|
|
|
227
|
|
|
$this->logger->error('compromised cookie', [ |
|
228
|
|
|
'input' => [ |
|
229
|
|
|
'user' => $userId, |
|
230
|
|
|
'series' => $series, |
|
231
|
|
|
'key' => $key, |
|
232
|
|
|
], |
|
233
|
|
|
'account' => [ |
|
234
|
|
|
'user' => $identity->getUserId(), |
|
235
|
|
|
'identity' => $identity->getId(), |
|
236
|
|
|
], |
|
237
|
|
|
]); |
|
238
|
|
|
|
|
239
|
|
|
throw new CompromisedCookie; |
|
240
|
|
|
} |
|
241
|
|
|
|
|
242
|
|
|
$identity->setStatus(Entity\Identity::STATUS_DISCARDED); |
|
243
|
|
|
$mapper->store($identity); |
|
244
|
|
|
|
|
245
|
|
|
$this->logger->info('logout successful', [ |
|
246
|
|
|
'account' => [ |
|
247
|
|
|
'user' => $identity->getUserId(), |
|
248
|
|
|
'identity' => $identity->getId(), |
|
249
|
|
|
], |
|
250
|
|
|
]); |
|
251
|
|
|
|
|
252
|
|
|
} |
|
253
|
|
|
|
|
254
|
|
|
|
|
255
|
|
|
public function changeUserPassword($userId, $oldKey, $newKey) |
|
256
|
|
|
{ |
|
257
|
|
|
$list = $this->retrieveIdenitiesByUserId($userId, Entity\Identity::TYPE_PASSWORD); |
|
|
|
|
|
|
258
|
|
|
|
|
259
|
|
|
if (count($list) !== 1) { |
|
260
|
|
|
$this->logger->warning('acount not found', [ |
|
261
|
|
|
'input' => [ |
|
262
|
|
|
'user' => $userId, |
|
263
|
|
|
'old-key' => md5($oldKey), |
|
264
|
|
|
'new-key' => md5($newKey), |
|
265
|
|
|
], |
|
266
|
|
|
]); |
|
267
|
|
|
|
|
268
|
|
|
throw new IdentityNotFound; |
|
269
|
|
|
} |
|
270
|
|
|
|
|
271
|
|
|
$identity = $list->getLastEntity(); |
|
272
|
|
|
|
|
273
|
|
|
$mapper = $this->mapperFactory->create(Mapper\EmailIdentity::class); |
|
274
|
|
|
|
|
275
|
|
View Code Duplication |
if ($identity->matchKey($oldKey) === false) { |
|
|
|
|
|
|
276
|
|
|
$this->logger->warning('wrong password', [ |
|
277
|
|
|
'input' => [ |
|
278
|
|
|
'user' => $userId, |
|
279
|
|
|
'old-key' => md5($oldKey), |
|
280
|
|
|
'new-key' => md5($newKey), |
|
281
|
|
|
], |
|
282
|
|
|
'account' => [ |
|
283
|
|
|
'user' => $identity->getUserId(), |
|
284
|
|
|
'identity' => $identity->getId(), |
|
285
|
|
|
], |
|
286
|
|
|
]); |
|
287
|
|
|
|
|
288
|
|
|
throw new PasswordNotMatch; |
|
289
|
|
|
} |
|
290
|
|
|
|
|
291
|
|
|
$identity->setKey($newKey); |
|
292
|
|
|
$mapper->store($identity); |
|
293
|
|
|
|
|
294
|
|
|
$this->discardAllUserCookies($identity->getUserId()); |
|
|
|
|
|
|
295
|
|
|
|
|
296
|
|
|
$this->logger->info('password changed', [ |
|
297
|
|
|
'account' => [ |
|
298
|
|
|
'user' => $identity->getUserId(), |
|
299
|
|
|
'identity' => $identity->getId(), |
|
300
|
|
|
], |
|
301
|
|
|
]); |
|
302
|
|
|
} |
|
303
|
|
|
|
|
304
|
|
|
|
|
305
|
|
|
public function getCurrentCookie() |
|
306
|
|
|
{ |
|
307
|
|
|
if (null === $this->currentCookie) { |
|
308
|
|
|
return new Entity\CookieIdentity; |
|
309
|
|
|
} |
|
310
|
|
|
|
|
311
|
|
|
return $this->currentCookie; |
|
312
|
|
|
} |
|
313
|
|
|
|
|
314
|
|
|
|
|
315
|
|
|
public function discardCurrentCookie() |
|
316
|
|
|
{ |
|
317
|
|
|
$cookie = new Entity\CookieIdentity; |
|
318
|
|
|
$cookie->setExpiresOn(time()); |
|
319
|
|
|
|
|
320
|
|
|
$this->currentCookie = $cookie; |
|
321
|
|
|
} |
|
322
|
|
|
} |
|
323
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.