Completed
Push — master ( 33aec4...b9a5c4 )
by Mārtiņš
02:06
created

Identification::changePassword()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 30
Code Lines 18

Duplication

Lines 15
Ratio 50 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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