Completed
Push — master ( 7348eb...8d7aac )
by Mārtiņš
02:10
created

Identification::discardCookie()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 31
Code Lines 17

Duplication

Lines 8
Ratio 25.81 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 8
loc 31
rs 8.8571
c 0
b 0
f 0
ccs 0
cts 17
cp 0
cc 3
eloc 17
nc 3
nop 3
crap 12
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 $currentCookie;
26
27
    private $mapperFactory;
28
    private $logger;
29
30
31 1
    public function __construct(CanCreateMapper $mapperFactory, LoggerInterface $logger)
32
    {
33 1
        $this->mapperFactory = $mapperFactory;
34 1
        $this->logger = $logger;
35 1
    }
36
37 1
    public function loginWithPassword(Entity\EmailIdentity $identity, $password)
38
    {
39 1 View Code Duplication
        if ($identity->matchKey($password) === 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...
40
            $this->logger->warning('wrong password', [
41
                'input' => [
42
                    'identifier' => $identity->getIdentifier(),
43
                    'key' => md5($password),
44
                ],
45
                'account' => [
46
                    'user' => $identity->getUserId(),
47
                    'identity' => $identity->getId(),
48
                ],
49
            ]);
50
51
            throw new PasswordNotMatch;
52
        }
53
54 1
        $this->registerUsageOfIdentity($identity);
55 1
        $cookie = $this->createCookieIdentity($identity);
56
57 1
        $this->logger->info('login successful', [
58
            'input' => [
59 1
                'identifier' => $identity->getIdentifier(),
60
            ],
61
            'account' => [
62 1
                'user' => $identity->getUserId(),
63 1
                'identity' => $identity->getId(),
64
            ],
65
        ]);
66
67 1
        $this->currentCookie = $cookie;
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->setUserId($identity->getUserId());
88 1
        $cookie->generateNewSeries();
89
90 1
        while ($mapper->exists($cookie)) {
91
            // just a failsafe, to prevent violation of constraint
92
            $cookie->generateNewSeries();
93
        }
94
95 1
        $cookie->generateNewKey();
96 1
        $cookie->setStatus(Entity\Identity::STATUS_ACTIVE);
97 1
        $cookie->setExpiresOn(time() + Entity\Identity::COOKIE_LIFESPAN);
98
99 1
        $mapper->store($cookie);
100
101 1
        return $cookie;
102
    }
103
104
105
    public function authenticateWithCookie($userId, $series, $key)
106
    {
107
        $identity = $this->retrieveIdenityByCookie($userId, $series, Entity\Identity::STATUS_ACTIVE);
108
109
        if ($identity->getId() === null) {
110
            $this->logCookieError($identity, 'denial of service');
111
            throw new DenialOfServiceAttempt;
112
        }
113
114
        $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
115
116
        if ($identity->getExpiresOn() < time()) {
117
            $identity->setStatus(Entity\Identity::STATUS_EXPIRED);
118
            $mapper->store($identity);
119
            $this->logger->info('cookie expired', [
120
                'input' => [
121
                    'user' => $identity->getUserId(),
122
                    'series' => $identity->getSeries(),
123
                    'key' => $identity->getKey(),
124
                ],
125
                'account' => [
126
                    'user' => $identity->getUserId(),
127
                    'identity' => $identity->getId(),
128
                ],
129
            ]);
130
131
            throw new IdentityExpired;
132
        }
133
134 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...
135
            $identity->setStatus(Entity\Identity::STATUS_BLOCKED);
136
            $mapper->store($identity);
137
138
            $this->logCookieError($identity, 'compromised cookie');
139
140
            throw new CompromisedCookie;
141
        }
142
143
        $identity->generateNewKey();
144
        $identity->setLastUsed(time());
145
        $identity->setExpiresOn(time() + Entity\Identity::COOKIE_LIFESPAN);
146
147
        $mapper->store($identity);
148
149
        $this->logger->info('cookie updated', [
150
            'account' => [
151
                'user' => $identity->getUserId(),
152
                'identity' => $identity->getId(),
153
            ],
154
        ]);
155
156
        $this->currentCookie = $identity;
157
    }
158
159
160
    private function retrieveIdenityByCookie($userId, $series, $status = Entity\Identity::STATUS_ANY)
161
    {
162
        $cookie = new Entity\CookieIdentity;
163
        $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
164
165
        $cookie->setUserId($userId);
166
        $cookie->setSeries($series);
167
        $cookie->setStatus($status);
168
169
        $mapper->fetch($cookie);
170
171
        return $cookie;
172
    }
173
174
175
    public function discardCookie($userId, $series, $key)
176
    {
177
        $identity = $this->retrieveIdenityByCookie($userId, $series, Entity\Identity::STATUS_ACTIVE);
178
179
        if ($identity->getId() === null) {
180
            $this->logCookieError($identity, 'denial of service');
181
            throw new DenialOfServiceAttempt;
182
        }
183
184
        $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
185
186 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...
187
            $identity->setStatus(Entity\Identity::STATUS_BLOCKED);
188
            $mapper->store($identity);
189
190
            $this->logCookieError($identity, 'compromised cookie');
191
192
            throw new CompromisedCookie;
193
        }
194
195
        $identity->setStatus(Entity\Identity::STATUS_DISCARDED);
196
        $mapper->store($identity);
197
198
        $this->logger->info('logout successful', [
199
            'account' => [
200
                'user' => $identity->getUserId(),
201
                'identity' => $identity->getId(),
202
            ],
203
        ]);
204
205
    }
206
207
208
    private function logCookieError(Entity\CookieIdentity $identity, $message)
209
    {
210
        $this->logger->error($message, [
211
            'input' => [
212
                'user' => $identity->getUserId(),
213
                'series' => $identity->getSeries(),
214
                'key' => $identity->getKey(),
215
            ],
216
            'account' => [
217
                'user' => $identity->getUserId(),
218
                'identity' => $identity->getId(),
219
            ],
220
        ]);
221
    }
222
223
224
    public function changeUserPassword(Entity\EmailIdentity $identity, $oldKey, $newKey)
225
    {
226
        $mapper = $this->mapperFactory->create(Mapper\EmailIdentity::class);
227
228 View Code Duplication
        if ($identity->matchKey($oldKey) === 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...
229
            $this->logger->warning('wrong password', [
230
                'input' => [
231
                    'user' => $identity->getUserId(),
232
                    'old-key' => md5($oldKey),
233
                    'new-key' => md5($newKey),
234
                ],
235
                'account' => [
236
                    'user' => $identity->getUserId(),
237
                    'identity' => $identity->getId(),
238
                ],
239
            ]);
240
241
            throw new PasswordNotMatch;
242
        }
243
244
        $identity->setPassword($newKey);
245
        $mapper->store($identity);
246
247
        $this->logger->info('password changed', [
248
            'account' => [
249
                'user' => $identity->getUserId(),
250
                'identity' => $identity->getId(),
251
            ],
252
        ]);
253
    }
254
255
256
    public function getCurrentCookie()
257
    {
258
        if (null === $this->currentCookie) {
259
            return new Entity\CookieIdentity;
260
        }
261
262
        return $this->currentCookie;
263
    }
264
265
266
    public function discardCurrentCookie()
267
    {
268
        $cookie = new Entity\CookieIdentity;
269
        $cookie->setExpiresOn(time());
270
271
        $this->currentCookie = $cookie;
272
    }
273
}
274