Completed
Push — master ( 25598f...82bcfc )
by Mārtiņš
02:18
created

Identification::logout()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

Changes 3
Bugs 0 Features 0
Metric Value
dl 0
loc 12
ccs 8
cts 8
cp 1
rs 9.4285
c 3
b 0
f 0
cc 1
eloc 7
nc 1
nop 2
crap 1
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\CompromisedCookie;
13
use Palladium\Exception\IdentityExpired;
14
use Palladium\Contract\CanCreateMapper;
15
use Psr\Log\LoggerInterface;
16
17
class Identification
18
{
19
20
    const DEFAULT_COOKIE_LIFESPAN = 14400; // 4 hours
21
22
    private $mapperFactory;
23
    private $logger;
24
25
    private $cookieLifespan;
26
27
    /**
28
     * @param Palladium\Contract\CanCreateMapper $mapperFactory Factory for creating persistence layer structures
29
     * @param Psr\Log\LoggerInterface $logger PSR-3 compatible logger
30
     * @param int $cookieLifespan Lifespan of the authentication cookie in seconds
31
     */
32 10
    public function __construct(CanCreateMapper $mapperFactory, LoggerInterface $logger, $cookieLifespan = self::DEFAULT_COOKIE_LIFESPAN)
33
    {
34 10
        $this->mapperFactory = $mapperFactory;
35 10
        $this->logger = $logger;
36 10
        $this->cookieLifespan = $cookieLifespan;
37 10
    }
38
39
40
    /**
41
     * @param string $password
42
     *
43
     * @return Palladium\Entity\CookieIdentity
44
     */
45 2
    public function loginWithPassword(Entity\EmailIdentity $identity, $password)
46
    {
47 2
        if ($identity->matchPassword($password) === false) {
48 1
            $this->logWrongPasswordNotice($identity, [
49 1
                'email' => $identity->getEmailAddress(),
50 1
                'key' => md5($password),
51
            ]);
52
53 1
            throw new PasswordMismatch;
54
        }
55
56 1
        $this->registerUsageOfIdentity($identity);
57 1
        $cookie = $this->createCookieIdentity($identity);
58
59 1
        $this->logger->info('login successful', [
60
            'input' => [
61 1
                'email' => $identity->getEmailAddress(),
62
            ],
63
            'user' => [
64 1
                'account' => $identity->getAccountId(),
65 1
                'identity' => $identity->getId(),
66
            ],
67
        ]);
68
69 1
        return $cookie;
70
    }
71
72
73 1
    private function registerUsageOfIdentity(Entity\Identity $identity)
74
    {
75 1
        $identity->setLastUsed(time());
76
77 1
        $mapper = $this->mapperFactory->create(Mapper\Identity::class);
78 1
        $mapper->store($identity);
79 1
    }
80
81
82 1
    private function createCookieIdentity(Entity\EmailIdentity $identity)
83
    {
84 1
        $cookie = new Entity\CookieIdentity;
85 1
        $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
86
87 1
        $cookie->setAccountId($identity->getAccountId());
88 1
        $cookie->generateNewSeries();
89
90 1
        $cookie->generateNewKey();
91 1
        $cookie->setStatus(Entity\Identity::STATUS_ACTIVE);
92 1
        $cookie->setExpiresOn(time() + $this->cookieLifespan);
93
94
95 1
        $parentId = $identity->getParentId();
96
97 1
        if (null === $parentId) {
98 1
            $parentId = $identity->getId();
99
        }
100
101 1
        $cookie->setParentId($parentId);
102
103 1
        $mapper->store($cookie);
104
105 1
        return $cookie;
106
    }
107
108
109
    /**
110
     * @param string @key
111
     *
112
     * @throws \Palladium\Exception\CompromisedCookie if key does not match
113
     * @throws \Palladium\Exception\IdentityExpired if cookie is too old
114
     *
115
     * @return Palladium\Entity\CookieIdentity
116
     */
117 3
    public function loginWithCookie(Entity\CookieIdentity $identity, $key)
118
    {
119 3
        $this->checkCookieExpireTime($identity);
120 2
        $this->checkCookieKey($identity, $key);
121
122 1
        $identity->generateNewKey();
123 1
        $identity->setLastUsed(time());
124 1
        $identity->setExpiresOn(time() + $this->cookieLifespan);
125
126 1
        $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
127 1
        $mapper->store($identity);
128
129 1
        $this->logExpectedBehaviour($identity, 'cookie updated');
130
131 1
        return $identity;
132
    }
133
134
135
    /**
136
     * @param string $key
137
     */
138 1
    public function logout(Entity\CookieIdentity $identity, $key)
139
    {
140 1
        $this->checkCookieExpireTime($identity);
141 1
        $this->checkCookieKey($identity, $key);
142
143 1
        $identity->setStatus(Entity\Identity::STATUS_DISCARDED);
144
145 1
        $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
146 1
        $mapper->store($identity);
147
148 1
        $this->logExpectedBehaviour($identity, 'logout successful');
149 1
    }
150
151
152 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...
153
    {
154 4
        if ($identity->getExpiresOn() < time()) {
155 1
            $identity->setStatus(Entity\Identity::STATUS_EXPIRED);
156
157 1
            $this->logger->info('cookie expired', $this->assembleCookieLogDetails($identity));
158
159 1
            $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
160 1
            $mapper->store($identity);
161
162 1
            throw new IdentityExpired;
163
        }
164 3
    }
165
166
167
    /**
168
     * Verify that the cookie based identity matches the key and,
169
     * if verification is failed, disable this given identity
170
     *
171
     * @param string $key
172
     * @throws \Palladium\Exception\CompromisedCookie if key does not match
173
     */
174 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...
175
    {
176 3
        if ($identity->matchKey($key) === true) {
177 2
            return;
178
        }
179
180 1
        $identity->setStatus(Entity\Identity::STATUS_BLOCKED);
181
182 1
        $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
183 1
        $mapper->store($identity);
184
185 1
        $this->logger->warning('compromised cookie', $this->assembleCookieLogDetails($identity));
186
187 1
        throw new CompromisedCookie;
188
    }
189
190
191
    /**
192
     * @return array
193
     */
194 2
    private function assembleCookieLogDetails(Entity\CookieIdentity $identity)
195
    {
196
        return [
197
            'input' => [
198 2
                'account' => $identity->getAccountId(),
199 2
                'series' => $identity->getSeries(),
200 2
                'key' => $identity->getKey(),
201
            ],
202
            'user' => [
203 2
                'account' => $identity->getAccountId(),
204 2
                'identity' => $identity->getId(),
205
            ],
206
        ];
207
    }
208
209
210 1
    public function discardIdentityCollection(Entity\IdentityCollection $list)
211
    {
212 1
        foreach ($list as $identity) {
213 1
            $identity->setStatus(Entity\Identity::STATUS_DISCARDED);
214
        }
215
216 1
        $mapper = $this->mapperFactory->create(Mapper\IdentityCollection::class);
217 1
        $mapper->store($list);
218 1
    }
219
220
221 1
    public function blockIdentity(Entity\Identity $identity)
222
    {
223 1
        $identity->setStatus(Entity\Identity::STATUS_BLOCKED);
224
225 1
        $mapper = $this->mapperFactory->create(Mapper\Identity::class);
226 1
        $mapper->store($identity);
227 1
    }
228
229
230
    /**
231
     * @codeCoverageIgnore
232
     */
233
    public function deleteIdentity(Entity\Identity $identity)
234
    {
235
        $mapper = $this->mapperFactory->create(Mapper\Identity::class);
236
        $mapper->remove($identity);
237
    }
238
239
240
    /**
241
     * @param string $oldPassword
242
     * @param string $newPassword
243
     */
244 2
    public function changePassword(Entity\EmailIdentity $identity, $oldPassword, $newPassword)
245
    {
246 2
        $mapper = $this->mapperFactory->create(Mapper\EmailIdentity::class);
247
248 2
        if ($identity->matchPassword($oldPassword) === false) {
249 1
            $this->logWrongPasswordNotice($identity, [
250 1
                'account' => $identity->getAccountId(),
251 1
                'old-key' => md5($oldPassword),
252 1
                'new-key' => md5($newPassword),
253
            ]);
254
255 1
            throw new PasswordMismatch;
256
        }
257
258 1
        $identity->setPassword($newPassword);
259 1
        $mapper->store($identity);
260
261 1
        $this->logExpectedBehaviour($identity, 'password changed');
262 1
    }
263
264
265
    /**
266
     * @param array $input
267
     */
268 2
    private function logWrongPasswordNotice(Entity\EmailIdentity $identity, $input)
269
    {
270 2
        $this->logger->notice('wrong password', [
271 2
            'input' => $input,
272
            'user' => [
273 2
                'account' => $identity->getAccountId(),
274 2
                'identity' => $identity->getId(),
275
            ],
276
        ]);
277 2
    }
278
279
280 3
    private function logExpectedBehaviour(Entity\Identity $identity, $message)
281
    {
282 3
        $this->logger->info($message, [
283
            'user' => [
284 3
                'account' => $identity->getAccountId(),
285 3
                'identity' => $identity->getId(),
286
            ],
287
        ]);
288 3
    }
289
290
291
    public function useOneTimeIdentity(Entity\OneTimeIdentity $identity, $key)
292
    {
293 View Code Duplication
        if ($identity->matchKey($key) === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
294
            $this->logger->notice('wrong key', [
295
                'input' => [
296
                    'key' => md5($key),
297
                ],
298
                'user' => [
299
                    'account' => $identity->getAccountId(),
300
                    'identity' => $identity->getId(),
301
                ],
302
            ]);
303
        }
304
305
        $mapper = $this->mapperFactory->create(Mapper\OneTimeIdentity::class);
306
        $mapper->store($identity);
307
    }
308
}
309