Completed
Push — master ( 7210d9...2b0eaf )
by Mārtiņš
02:13
created

Identification::deleteIdentity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 5
ccs 0
cts 0
cp 0
rs 9.4285
c 1
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
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 2
    public function loginWithPassword(Entity\EmailIdentity $identity, string $password): Entity\CookieIdentity
42
    {
43 2
        if ($identity->matchPassword($password) === false) {
44 1
            $this->logWrongPasswordNotice($identity, [
45 1
                'email' => $identity->getEmailAddress(),
46 1
                'key' => md5($password),
47
            ]);
48
49 1
            throw new PasswordMismatch;
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
                'email' => $identity->getEmailAddress(),
58
            ],
59
            'user' => [
60 1
                'account' => $identity->getAccountId(),
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 2
    private function createCookieIdentity(Entity\Identity $identity): Entity\CookieIdentity
79
    {
80 2
        $cookie = new Entity\CookieIdentity;
81 2
        $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
82
83 2
        $cookie->setAccountId($identity->getAccountId());
84 2
        $cookie->generateNewSeries();
85
86 2
        $cookie->generateNewKey();
87 2
        $cookie->setStatus(Entity\Identity::STATUS_ACTIVE);
88 2
        $cookie->setExpiresOn(time() + $this->cookieLifespan);
89
90
91 2
        $parentId = $identity->getParentId();
92
93 2
        if (null === $parentId) {
94 2
            $parentId = $identity->getId();
95
        }
96
97 2
        $cookie->setParentId($parentId);
98
99 2
        $mapper->store($cookie);
100
101 2
        return $cookie;
102
    }
103
104
105
    /**
106
     * @param string @key
107
     *
108
     * @throws \Palladium\Exception\CompromisedCookie if key does not match
109
     * @throws \Palladium\Exception\IdentityExpired if cookie is too old
110
     *
111
     * @return Palladium\Entity\CookieIdentity
112
     */
113 3
    public function loginWithCookie(Entity\CookieIdentity $identity, $key): Entity\CookieIdentity
114
    {
115 3
        $this->checkCookieExpireTime($identity);
116 2
        $this->checkCookieKey($identity, $key);
117
118 1
        $identity->generateNewKey();
119 1
        $identity->setLastUsed(time());
120 1
        $identity->setExpiresOn(time() + $this->cookieLifespan);
121
122 1
        $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
123 1
        $mapper->store($identity);
124
125 1
        $this->logExpectedBehaviour($identity, 'cookie updated');
126
127 1
        return $identity;
128
    }
129
130
131
    /**
132
     * @param string $key
133
     */
134 1
    public function logout(Entity\CookieIdentity $identity, $key)
135
    {
136 1
        $this->checkCookieExpireTime($identity);
137 1
        $this->checkCookieKey($identity, $key);
138
139 1
        $this->changeIdentityStatus($identity, Entity\Identity::STATUS_DISCARDED);
140 1
        $this->logExpectedBehaviour($identity, 'logout successful');
141 1
    }
142
143
144 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...
145
    {
146 4
        if ($identity->getExpiresOn() < time()) {
147 1
            $this->logger->info('cookie expired', $this->assembleCookieLogDetails($identity));
148
149 1
            $this->changeIdentityStatus($identity, Entity\Identity::STATUS_EXPIRED);
150
151 1
            throw new IdentityExpired;
152
        }
153 3
    }
154
155
156 4
    private function changeIdentityStatus(Entity\Identity $identity, int $status)
0 ignored issues
show
Unused Code introduced by
The parameter $status is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
157
    {
158 4
        $identity->setStatus(Entity\Identity::STATUS_EXPIRED);
159 4
        $mapper = $this->mapperFactory->create(Mapper\Identity::class);
160 4
        $mapper->store($identity);
161 4
    }
162
163
164
    /**
165
     * Verify that the cookie based identity matches the key and,
166
     * if verification is failed, disable this given identity
167
     *
168
     * @param string $key
169
     * @throws \Palladium\Exception\CompromisedCookie if key does not match
170
     */
171 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...
172
    {
173 3
        if ($identity->matchKey($key) === true) {
174 2
            return;
175
        }
176
177 1
        $this->changeIdentityStatus($identity, Entity\Identity::STATUS_BLOCKED);
178 1
        $this->logger->warning('compromised cookie', $this->assembleCookieLogDetails($identity));
179
180 1
        throw new CompromisedCookie;
181
    }
182
183
184 2
    private function assembleCookieLogDetails(Entity\CookieIdentity $identity): array
185
    {
186
        return [
187
            'input' => [
188 2
                'account' => $identity->getAccountId(),
189 2
                'series' => $identity->getSeries(),
190 2
                'key' => $identity->getKey(),
191
            ],
192
            'user' => [
193 2
                'account' => $identity->getAccountId(),
194 2
                'identity' => $identity->getId(),
195
            ],
196
        ];
197
    }
198
199
200 1
    public function discardIdentityCollection(Entity\IdentityCollection $list)
201
    {
202 1
        foreach ($list as $identity) {
203 1
            $identity->setStatus(Entity\Identity::STATUS_DISCARDED);
204
        }
205
206 1
        $mapper = $this->mapperFactory->create(Mapper\IdentityCollection::class);
207 1
        $mapper->store($list);
208 1
    }
209
210
211 1
    public function blockIdentity(Entity\Identity $identity)
212
    {
213 1
        $identity->setStatus(Entity\Identity::STATUS_BLOCKED);
214
215 1
        $mapper = $this->mapperFactory->create(Mapper\Identity::class);
216 1
        $mapper->store($identity);
217 1
    }
218
219
220
    /**
221
     * @codeCoverageIgnore
222
     */
223
    public function deleteIdentity(Entity\Identity $identity)
224
    {
225
        $mapper = $this->mapperFactory->create(Mapper\Identity::class);
226
        $mapper->remove($identity);
227
    }
228
229
230
    /**
231
     * @param string $oldPassword
232
     * @param string $newPassword
233
     */
234 2
    public function changePassword(Entity\EmailIdentity $identity, $oldPassword, $newPassword)
235
    {
236 2
        $mapper = $this->mapperFactory->create(Mapper\EmailIdentity::class);
237
238 2
        if ($identity->matchPassword($oldPassword) === false) {
239 1
            $this->logWrongPasswordNotice($identity, [
240 1
                'account' => $identity->getAccountId(),
241 1
                'old-key' => md5($oldPassword),
242 1
                'new-key' => md5($newPassword),
243
            ]);
244
245 1
            throw new PasswordMismatch;
246
        }
247
248 1
        $identity->setPassword($newPassword);
249 1
        $mapper->store($identity);
250
251 1
        $this->logExpectedBehaviour($identity, 'password changed');
252 1
    }
253
254
255
    /**
256
     * @param array $input
257
     */
258 2
    private function logWrongPasswordNotice(Entity\EmailIdentity $identity, $input)
259
    {
260 2
        $this->logger->notice('wrong password', [
261 2
            'input' => $input,
262
            'user' => [
263 2
                'account' => $identity->getAccountId(),
264 2
                'identity' => $identity->getId(),
265
            ],
266
        ]);
267 2
    }
268
269
270 4
    private function logExpectedBehaviour(Entity\Identity $identity, $message)
271
    {
272 4
        $this->logger->info($message, [
273
            'user' => [
274 4
                'account' => $identity->getAccountId(),
275 4
                'identity' => $identity->getId(),
276
            ],
277
        ]);
278 4
    }
279
280
281 2
    public function useOneTimeIdentity(Entity\OneTimeIdentity $identity, $key): Entity\CookieIdentity
282
    {
283 2
        if ($identity->matchKey($key) === false) {
284 1
            $this->logger->notice('wrong key', [
285
                'input' => [
286 1
                    'key' => md5($key),
287
                ],
288
                'user' => [
289 1
                    'account' => $identity->getAccountId(),
290 1
                    'identity' => $identity->getId(),
291
                ],
292
            ]);
293
294 1
            throw new KeyMismatch;
295
        }
296
297 1
        $this->changeIdentityStatus($identity, Entity\Identity::STATUS_DISCARDED);
298
299 1
        $this->logExpectedBehaviour($identity, 'one-time identity used');
300
301 1
        return $this->createCookieIdentity($identity);
302
    }
303
}
304