Completed
Push — master ( 93c3bf...f3a59b )
by Mārtiņš
02:16
created

Identification::loginWithPassword()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 26
ccs 13
cts 13
cp 1
rs 8.8571
c 1
b 0
f 0
cc 2
eloc 15
nc 2
nop 2
crap 2
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\PasswordMismatch;
12
use Palladium\Exception\KeyMismatch;
13
use Palladium\Exception\CompromisedCookie;
14
use Palladium\Exception\IdentityExpired;
15
use Palladium\Contract\CanCreateMapper;
16
use Psr\Log\LoggerInterface;
17
18
class Identification
19
{
20
21
    const DEFAULT_COOKIE_LIFESPAN = 14400; // 4 hours
22
23
    private $mapperFactory;
24
    private $logger;
25
26
    private $cookieLifespan;
27
28
    /**
29
     * @param Palladium\Contract\CanCreateMapper $mapperFactory Factory for creating persistence layer structures
30
     * @param Psr\Log\LoggerInterface $logger PSR-3 compatible logger
31
     * @param int $cookieLifespan Lifespan of the authentication cookie in seconds
32
     */
33 12
    public function __construct(CanCreateMapper $mapperFactory, LoggerInterface $logger, $cookieLifespan = self::DEFAULT_COOKIE_LIFESPAN)
34
    {
35 12
        $this->mapperFactory = $mapperFactory;
36 12
        $this->logger = $logger;
37 12
        $this->cookieLifespan = $cookieLifespan;
38 12
    }
39
40
41
    /**
42
     * @param string $password
43
     *
44
     * @return Palladium\Entity\CookieIdentity
45
     */
46 2
    public function loginWithPassword(Entity\EmailIdentity $identity, $password)
47
    {
48 2
        if ($identity->matchPassword($password) === false) {
49 1
            $this->logWrongPasswordNotice($identity, [
50 1
                'email' => $identity->getEmailAddress(),
51 1
                'key' => md5($password),
52
            ]);
53
54 1
            throw new PasswordMismatch;
55
        }
56
57 1
        $this->registerUsageOfIdentity($identity);
58 1
        $cookie = $this->createCookieIdentity($identity);
59
60 1
        $this->logger->info('login successful', [
61
            'input' => [
62 1
                'email' => $identity->getEmailAddress(),
63
            ],
64
            'user' => [
65 1
                'account' => $identity->getAccountId(),
66 1
                'identity' => $identity->getId(),
67
            ],
68
        ]);
69
70 1
        return $cookie;
71
    }
72
73
74 1
    private function registerUsageOfIdentity(Entity\Identity $identity)
75
    {
76 1
        $identity->setLastUsed(time());
77
78 1
        $mapper = $this->mapperFactory->create(Mapper\Identity::class);
79 1
        $mapper->store($identity);
80 1
    }
81
82
83 2
    private function createCookieIdentity(Entity\Identity $identity)
84
    {
85 2
        $cookie = new Entity\CookieIdentity;
86 2
        $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
87
88 2
        $cookie->setAccountId($identity->getAccountId());
89 2
        $cookie->generateNewSeries();
90
91 2
        $cookie->generateNewKey();
92 2
        $cookie->setStatus(Entity\Identity::STATUS_ACTIVE);
93 2
        $cookie->setExpiresOn(time() + $this->cookieLifespan);
94
95
96 2
        $parentId = $identity->getParentId();
97
98 2
        if (null === $parentId) {
99 2
            $parentId = $identity->getId();
100
        }
101
102 2
        $cookie->setParentId($parentId);
103
104 2
        $mapper->store($cookie);
105
106 2
        return $cookie;
107
    }
108
109
110
    /**
111
     * @param string @key
112
     *
113
     * @throws \Palladium\Exception\CompromisedCookie if key does not match
114
     * @throws \Palladium\Exception\IdentityExpired if cookie is too old
115
     *
116
     * @return Palladium\Entity\CookieIdentity
117
     */
118 3
    public function loginWithCookie(Entity\CookieIdentity $identity, $key)
119
    {
120 3
        $this->checkCookieExpireTime($identity);
121 2
        $this->checkCookieKey($identity, $key);
122
123 1
        $identity->generateNewKey();
124 1
        $identity->setLastUsed(time());
125 1
        $identity->setExpiresOn(time() + $this->cookieLifespan);
126
127 1
        $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
128 1
        $mapper->store($identity);
129
130 1
        $this->logExpectedBehaviour($identity, 'cookie updated');
131
132 1
        return $identity;
133
    }
134
135
136
    /**
137
     * @param string $key
138
     */
139 1
    public function logout(Entity\CookieIdentity $identity, $key)
140
    {
141 1
        $this->checkCookieExpireTime($identity);
142 1
        $this->checkCookieKey($identity, $key);
143
144 1
        $identity->setStatus(Entity\Identity::STATUS_DISCARDED);
145
146 1
        $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
147 1
        $mapper->store($identity);
148
149 1
        $this->logExpectedBehaviour($identity, 'logout successful');
150 1
    }
151
152
153 4 View Code Duplication
    private function checkCookieExpireTime(Entity\CookieIdentity $identity)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
154
    {
155 4
        if ($identity->getExpiresOn() < time()) {
156 1
            $identity->setStatus(Entity\Identity::STATUS_EXPIRED);
157
158 1
            $this->logger->info('cookie expired', $this->assembleCookieLogDetails($identity));
159
160 1
            $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
161 1
            $mapper->store($identity);
162
163 1
            throw new IdentityExpired;
164
        }
165 3
    }
166
167
168
    /**
169
     * Verify that the cookie based identity matches the key and,
170
     * if verification is failed, disable this given identity
171
     *
172
     * @param string $key
173
     * @throws \Palladium\Exception\CompromisedCookie if key does not match
174
     */
175 3 View Code Duplication
    private function checkCookieKey(Entity\CookieIdentity $identity, $key)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
176
    {
177 3
        if ($identity->matchKey($key) === true) {
178 2
            return;
179
        }
180
181 1
        $identity->setStatus(Entity\Identity::STATUS_BLOCKED);
182
183 1
        $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
184 1
        $mapper->store($identity);
185
186 1
        $this->logger->warning('compromised cookie', $this->assembleCookieLogDetails($identity));
187
188 1
        throw new CompromisedCookie;
189
    }
190
191
192
    /**
193
     * @return array
194
     */
195 2
    private function assembleCookieLogDetails(Entity\CookieIdentity $identity)
196
    {
197
        return [
198
            'input' => [
199 2
                'account' => $identity->getAccountId(),
200 2
                'series' => $identity->getSeries(),
201 2
                'key' => $identity->getKey(),
202
            ],
203
            'user' => [
204 2
                'account' => $identity->getAccountId(),
205 2
                'identity' => $identity->getId(),
206
            ],
207
        ];
208
    }
209
210
211 1
    public function discardIdentityCollection(Entity\IdentityCollection $list)
212
    {
213 1
        foreach ($list as $identity) {
214 1
            $identity->setStatus(Entity\Identity::STATUS_DISCARDED);
215
        }
216
217 1
        $mapper = $this->mapperFactory->create(Mapper\IdentityCollection::class);
218 1
        $mapper->store($list);
219 1
    }
220
221
222 1
    public function blockIdentity(Entity\Identity $identity)
223
    {
224 1
        $identity->setStatus(Entity\Identity::STATUS_BLOCKED);
225
226 1
        $mapper = $this->mapperFactory->create(Mapper\Identity::class);
227 1
        $mapper->store($identity);
228 1
    }
229
230
231
    /**
232
     * @codeCoverageIgnore
233
     */
234
    public function deleteIdentity(Entity\Identity $identity)
235
    {
236
        $mapper = $this->mapperFactory->create(Mapper\Identity::class);
237
        $mapper->remove($identity);
238
    }
239
240
241
    /**
242
     * @param string $oldPassword
243
     * @param string $newPassword
244
     */
245 2
    public function changePassword(Entity\EmailIdentity $identity, $oldPassword, $newPassword)
246
    {
247 2
        $mapper = $this->mapperFactory->create(Mapper\EmailIdentity::class);
248
249 2
        if ($identity->matchPassword($oldPassword) === false) {
250 1
            $this->logWrongPasswordNotice($identity, [
251 1
                'account' => $identity->getAccountId(),
252 1
                'old-key' => md5($oldPassword),
253 1
                'new-key' => md5($newPassword),
254
            ]);
255
256 1
            throw new PasswordMismatch;
257
        }
258
259 1
        $identity->setPassword($newPassword);
260 1
        $mapper->store($identity);
261
262 1
        $this->logExpectedBehaviour($identity, 'password changed');
263 1
    }
264
265
266
    /**
267
     * @param array $input
268
     */
269 2
    private function logWrongPasswordNotice(Entity\EmailIdentity $identity, $input)
270
    {
271 2
        $this->logger->notice('wrong password', [
272 2
            'input' => $input,
273
            'user' => [
274 2
                'account' => $identity->getAccountId(),
275 2
                'identity' => $identity->getId(),
276
            ],
277
        ]);
278 2
    }
279
280
281 3
    private function logExpectedBehaviour(Entity\Identity $identity, $message)
282
    {
283 3
        $this->logger->info($message, [
284
            'user' => [
285 3
                'account' => $identity->getAccountId(),
286 3
                'identity' => $identity->getId(),
287
            ],
288
        ]);
289 3
    }
290
291
292 2
    public function useOneTimeIdentity(Entity\OneTimeIdentity $identity, $key)
293
    {
294 2
        if ($identity->matchKey($key) === false) {
295 1
            $this->logger->notice('wrong key', [
296
                'input' => [
297 1
                    'key' => md5($key),
298
                ],
299
                'user' => [
300 1
                    'account' => $identity->getAccountId(),
301 1
                    'identity' => $identity->getId(),
302
                ],
303
            ]);
304
305 1
            throw new KeyMismatch;
306
        }
307
308 1
        $mapper = $this->mapperFactory->create(Mapper\OneTimeIdentity::class);
309 1
        $mapper->store($identity);
310
311 1
        $this->logger->info('one-time identity used', [
312
            'user' => [
313 1
                'account' => $identity->getAccountId(),
314 1
                'identity' => $identity->getId(),
315
            ],
316
        ]);
317
318 1
        return $this->createCookieIdentity($identity);
319
    }
320
}
321