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