|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Palladium\Service; |
|
4
|
|
|
|
|
5
|
|
|
/** |
|
6
|
|
|
* Retrieval and handling of identities for registered users |
|
7
|
|
|
*/ |
|
8
|
|
|
|
|
9
|
|
|
use Palladium\Mapper as Mapper; |
|
10
|
|
|
use Palladium\Entity as Entity; |
|
11
|
|
|
use Palladium\Exception\PasswordNotMatch; |
|
12
|
|
|
use Palladium\Exception\CompromisedCookie; |
|
13
|
|
|
use Palladium\Exception\DenialOfServiceAttempt; |
|
14
|
|
|
use Palladium\Exception\IdentityExpired; |
|
15
|
|
|
use Palladium\Contract\CanCreateMapper; |
|
16
|
|
|
use Psr\Log\LoggerInterface; |
|
17
|
|
|
|
|
18
|
|
|
class Identification |
|
19
|
|
|
{ |
|
20
|
|
|
|
|
21
|
|
|
private $mapperFactory; |
|
22
|
|
|
private $logger; |
|
23
|
|
|
|
|
24
|
|
|
|
|
25
|
1 |
|
public function __construct(CanCreateMapper $mapperFactory, LoggerInterface $logger) |
|
26
|
|
|
{ |
|
27
|
1 |
|
$this->mapperFactory = $mapperFactory; |
|
28
|
1 |
|
$this->logger = $logger; |
|
29
|
1 |
|
} |
|
30
|
|
|
|
|
31
|
1 |
|
public function loginWithPassword(Entity\EmailIdentity $identity, $password) |
|
32
|
|
|
{ |
|
33
|
1 |
View Code Duplication |
if ($identity->matchPassword($password) === false) { |
|
|
|
|
|
|
34
|
|
|
$this->logger->warning('wrong password', [ |
|
35
|
|
|
'input' => [ |
|
36
|
|
|
'identifier' => $identity->getIdentifier(), |
|
37
|
|
|
'key' => md5($password), |
|
38
|
|
|
], |
|
39
|
|
|
'account' => [ |
|
40
|
|
|
'user' => $identity->getUserId(), |
|
41
|
|
|
'identity' => $identity->getId(), |
|
42
|
|
|
], |
|
43
|
|
|
]); |
|
44
|
|
|
|
|
45
|
|
|
throw new PasswordNotMatch; |
|
46
|
|
|
} |
|
47
|
|
|
|
|
48
|
1 |
|
$this->registerUsageOfIdentity($identity); |
|
49
|
1 |
|
$cookie = $this->createCookieIdentity($identity); |
|
50
|
|
|
|
|
51
|
1 |
|
$this->logger->info('login successful', [ |
|
52
|
|
|
'input' => [ |
|
53
|
1 |
|
'identifier' => $identity->getIdentifier(), |
|
54
|
|
|
], |
|
55
|
|
|
'account' => [ |
|
56
|
1 |
|
'user' => $identity->getUserId(), |
|
57
|
1 |
|
'identity' => $identity->getId(), |
|
58
|
|
|
], |
|
59
|
|
|
]); |
|
60
|
|
|
|
|
61
|
1 |
|
return $cookie; |
|
62
|
|
|
} |
|
63
|
|
|
|
|
64
|
|
|
|
|
65
|
1 |
|
private function registerUsageOfIdentity(Entity\Identity $identity) |
|
66
|
|
|
{ |
|
67
|
1 |
|
$identity->setLastUsed(time()); |
|
68
|
|
|
|
|
69
|
1 |
|
$mapper = $this->mapperFactory->create(Mapper\Identity::class); |
|
70
|
1 |
|
$mapper->store($identity); |
|
71
|
1 |
|
} |
|
72
|
|
|
|
|
73
|
|
|
|
|
74
|
1 |
|
private function createCookieIdentity(Entity\EmailIdentity $identity) |
|
75
|
|
|
{ |
|
76
|
1 |
|
$cookie = new Entity\CookieIdentity; |
|
77
|
1 |
|
$mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class); |
|
78
|
|
|
|
|
79
|
1 |
|
$cookie->setUserId($identity->getUserId()); |
|
80
|
1 |
|
$cookie->generateNewSeries(); |
|
81
|
|
|
|
|
82
|
1 |
|
while ($mapper->exists($cookie)) { |
|
83
|
|
|
// just a failsafe, to prevent violation of constraint |
|
84
|
|
|
$cookie->generateNewSeries(); |
|
85
|
|
|
} |
|
86
|
|
|
|
|
87
|
1 |
|
$cookie->generateNewKey(); |
|
88
|
1 |
|
$cookie->setStatus(Entity\Identity::STATUS_ACTIVE); |
|
89
|
1 |
|
$cookie->setExpiresOn(time() + Entity\Identity::COOKIE_LIFESPAN); |
|
90
|
|
|
|
|
91
|
1 |
|
$mapper->store($cookie); |
|
92
|
|
|
|
|
93
|
1 |
|
return $cookie; |
|
94
|
|
|
} |
|
95
|
|
|
|
|
96
|
|
|
|
|
97
|
|
|
public function loginWithCookie(Entity\CookieIdentity $identity, $key) |
|
98
|
|
|
{ |
|
99
|
|
|
if ($identity->getId() === null) { |
|
100
|
|
|
$this->logCookieError($identity, 'denial of service'); |
|
101
|
|
|
throw new DenialOfServiceAttempt; |
|
102
|
|
|
} |
|
103
|
|
|
|
|
104
|
|
|
$mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class); |
|
105
|
|
|
|
|
106
|
|
|
if ($identity->getExpiresOn() < time()) { |
|
107
|
|
|
$identity->setStatus(Entity\Identity::STATUS_EXPIRED); |
|
108
|
|
|
$mapper->store($identity); |
|
109
|
|
|
$this->logger->info('cookie expired', [ |
|
110
|
|
|
'input' => [ |
|
111
|
|
|
'user' => $identity->getUserId(), |
|
112
|
|
|
'series' => $identity->getSeries(), |
|
113
|
|
|
'key' => $identity->getKey(), |
|
114
|
|
|
], |
|
115
|
|
|
'account' => [ |
|
116
|
|
|
'user' => $identity->getUserId(), |
|
117
|
|
|
'identity' => $identity->getId(), |
|
118
|
|
|
], |
|
119
|
|
|
]); |
|
120
|
|
|
|
|
121
|
|
|
throw new IdentityExpired; |
|
122
|
|
|
} |
|
123
|
|
|
|
|
124
|
|
View Code Duplication |
if ($identity->matchKey($key) === false) { |
|
|
|
|
|
|
125
|
|
|
$identity->setStatus(Entity\Identity::STATUS_BLOCKED); |
|
126
|
|
|
$mapper->store($identity); |
|
127
|
|
|
|
|
128
|
|
|
$this->logCookieError($identity, 'compromised cookie'); |
|
129
|
|
|
|
|
130
|
|
|
throw new CompromisedCookie; |
|
131
|
|
|
} |
|
132
|
|
|
|
|
133
|
|
|
$identity->generateNewKey(); |
|
134
|
|
|
$identity->setLastUsed(time()); |
|
135
|
|
|
$identity->setExpiresOn(time() + Entity\Identity::COOKIE_LIFESPAN); |
|
136
|
|
|
|
|
137
|
|
|
$mapper->store($identity); |
|
138
|
|
|
|
|
139
|
|
|
$this->logger->info('cookie updated', [ |
|
140
|
|
|
'account' => [ |
|
141
|
|
|
'user' => $identity->getUserId(), |
|
142
|
|
|
'identity' => $identity->getId(), |
|
143
|
|
|
], |
|
144
|
|
|
]); |
|
145
|
|
|
|
|
146
|
|
|
return $identity; |
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
|
|
150
|
|
|
public function logout(Entity\CookieIdentity $identity, $key) |
|
151
|
|
|
{ |
|
152
|
|
|
if ($identity->getId() === null) { |
|
153
|
|
|
$this->logCookieError($identity, 'denial of service'); |
|
154
|
|
|
throw new DenialOfServiceAttempt; |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
|
|
$mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class); |
|
158
|
|
|
|
|
159
|
|
View Code Duplication |
if ($identity->matchKey($key) === false) { |
|
|
|
|
|
|
160
|
|
|
$identity->setStatus(Entity\Identity::STATUS_BLOCKED); |
|
161
|
|
|
$mapper->store($identity); |
|
162
|
|
|
|
|
163
|
|
|
$this->logCookieError($identity, 'compromised cookie'); |
|
164
|
|
|
|
|
165
|
|
|
throw new CompromisedCookie; |
|
166
|
|
|
} |
|
167
|
|
|
|
|
168
|
|
|
$identity->setStatus(Entity\Identity::STATUS_DISCARDED); |
|
169
|
|
|
$mapper->store($identity); |
|
170
|
|
|
|
|
171
|
|
|
$this->logger->info('logout successful', [ |
|
172
|
|
|
'account' => [ |
|
173
|
|
|
'user' => $identity->getUserId(), |
|
174
|
|
|
'identity' => $identity->getId(), |
|
175
|
|
|
], |
|
176
|
|
|
]); |
|
177
|
|
|
|
|
178
|
|
|
} |
|
179
|
|
|
|
|
180
|
|
|
|
|
181
|
|
|
public function discardRelatedCookies(Entity\Identity $identity) |
|
182
|
|
|
{ |
|
183
|
|
|
/** |
|
184
|
|
|
* @NOTE: this operation might require transaction |
|
185
|
|
|
* or a change in how store() is implemnted in IdentityCollection mapper |
|
186
|
|
|
*/ |
|
187
|
|
|
$list = $this->retrieveIdenitiesByUserId($identity->getUserId(), Entity\Identity::TYPE_COOKIE); |
|
188
|
|
|
|
|
189
|
|
|
foreach ($list as $identity) { |
|
190
|
|
|
$identity->setStatus(Entity\Identity::STATUS_DISCARDED); |
|
191
|
|
|
} |
|
192
|
|
|
|
|
193
|
|
|
$mapper = $this->mapperFactory->create(Mapper\IdentityCollection::class); |
|
194
|
|
|
$mapper->store($list); |
|
195
|
|
|
} |
|
196
|
|
|
|
|
197
|
|
|
|
|
198
|
|
|
private function retrieveIdenitiesByUserId($userId, $type = Entity\Identity::TYPE_ANY, $status = Entity\Identity::STATUS_ACTIVE) |
|
199
|
|
|
{ |
|
200
|
|
|
$collection = new Entity\IdentityCollection; |
|
201
|
|
|
$collection->forUserId($userId); |
|
202
|
|
|
$collection->forType($type); |
|
203
|
|
|
$collection->forStatus($status); |
|
204
|
|
|
|
|
205
|
|
|
$mapper = $this->mapperFactory->create(Mapper\IdentityCollection::class); |
|
206
|
|
|
$mapper->fetch($collection); |
|
207
|
|
|
|
|
208
|
|
|
return $collection; |
|
209
|
|
|
} |
|
210
|
|
|
|
|
211
|
|
|
|
|
212
|
|
|
/** |
|
213
|
|
|
* @param string $message |
|
214
|
|
|
*/ |
|
215
|
|
|
private function logCookieError(Entity\CookieIdentity $identity, $message) |
|
216
|
|
|
{ |
|
217
|
|
|
$this->logger->error($message, [ |
|
218
|
|
|
'input' => [ |
|
219
|
|
|
'user' => $identity->getUserId(), |
|
220
|
|
|
'series' => $identity->getSeries(), |
|
221
|
|
|
'key' => $identity->getKey(), |
|
222
|
|
|
], |
|
223
|
|
|
'account' => [ |
|
224
|
|
|
'user' => $identity->getUserId(), |
|
225
|
|
|
'identity' => $identity->getId(), |
|
226
|
|
|
], |
|
227
|
|
|
]); |
|
228
|
|
|
} |
|
229
|
|
|
|
|
230
|
|
|
|
|
231
|
|
|
public function changePassword(Entity\EmailIdentity $identity, $oldPassword, $newPassword) |
|
232
|
|
|
{ |
|
233
|
|
|
$mapper = $this->mapperFactory->create(Mapper\EmailIdentity::class); |
|
234
|
|
|
|
|
235
|
|
View Code Duplication |
if ($identity->matchPassword($oldPassword) === false) { |
|
|
|
|
|
|
236
|
|
|
$this->logger->warning('wrong password', [ |
|
237
|
|
|
'input' => [ |
|
238
|
|
|
'user' => $identity->getUserId(), |
|
239
|
|
|
'old-key' => md5($oldPassword), |
|
240
|
|
|
'new-key' => md5($newPassword), |
|
241
|
|
|
], |
|
242
|
|
|
'account' => [ |
|
243
|
|
|
'user' => $identity->getUserId(), |
|
244
|
|
|
'identity' => $identity->getId(), |
|
245
|
|
|
], |
|
246
|
|
|
]); |
|
247
|
|
|
|
|
248
|
|
|
throw new PasswordNotMatch; |
|
249
|
|
|
} |
|
250
|
|
|
|
|
251
|
|
|
$identity->setPassword($newPassword); |
|
252
|
|
|
$mapper->store($identity); |
|
253
|
|
|
|
|
254
|
|
|
$this->logger->info('password changed', [ |
|
255
|
|
|
'account' => [ |
|
256
|
|
|
'user' => $identity->getUserId(), |
|
257
|
|
|
'identity' => $identity->getId(), |
|
258
|
|
|
], |
|
259
|
|
|
]); |
|
260
|
|
|
} |
|
261
|
|
|
} |
|
262
|
|
|
|
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.