Completed
Push — master ( b9a5c4...4587ca )
by Mārtiņš
02:02
created

Identification::checkCookieKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 13
ccs 0
cts 7
cp 0
rs 9.4285
cc 2
eloc 7
nc 2
nop 2
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
        if ($identity->matchPassword($password) === false) {
34
            $this->logWrongPasswordWarning($identity, [
35
                'identifier' => $identity->getIdentifier(),
36
                'key' => md5($password),
37
            ]);
38
39
            throw new PasswordNotMatch;
40
        }
41
42 1
        $this->registerUsageOfIdentity($identity);
43 1
        $cookie = $this->createCookieIdentity($identity);
44
45 1
        $this->logger->info('login successful', [
46
            'input' => [
47 1
                'identifier' => $identity->getIdentifier(),
48
            ],
49
            'account' => [
50 1
                'user' => $identity->getUserId(),
51 1
                'identity' => $identity->getId(),
52
            ],
53
        ]);
54
55 1
        return $cookie;
56
    }
57
58
59 1
    private function registerUsageOfIdentity(Entity\Identity $identity)
60
    {
61 1
        $identity->setLastUsed(time());
62
63 1
        $mapper = $this->mapperFactory->create(Mapper\Identity::class);
64 1
        $mapper->store($identity);
65 1
    }
66
67
68 1
    private function createCookieIdentity(Entity\EmailIdentity $identity)
69
    {
70 1
        $cookie = new Entity\CookieIdentity;
71 1
        $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
72
73 1
        $cookie->setUserId($identity->getUserId());
74 1
        $cookie->generateNewSeries();
75
76 1
        while ($mapper->exists($cookie)) {
77
            // just a failsafe, to prevent violation of constraint
78
            $cookie->generateNewSeries();
79
        }
80
81 1
        $cookie->generateNewKey();
82 1
        $cookie->setStatus(Entity\Identity::STATUS_ACTIVE);
83 1
        $cookie->setExpiresOn(time() + Entity\Identity::COOKIE_LIFESPAN);
84
85 1
        $mapper->store($cookie);
86
87 1
        return $cookie;
88
    }
89
90
91
    public function loginWithCookie(Entity\CookieIdentity $identity, $key)
92
    {
93
        if ($identity->getId() === null) {
94
            $this->logCookieError($identity, 'denial of service');
95
            throw new DenialOfServiceAttempt;
96
        }
97
98
        $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
99
100
        if ($identity->getExpiresOn() < time()) {
101
            $identity->setStatus(Entity\Identity::STATUS_EXPIRED);
102
            $mapper->store($identity);
103
            $this->logger->info('cookie expired', [
104
                'input' => [
105
                    'user' => $identity->getUserId(),
106
                    'series' => $identity->getSeries(),
107
                    'key' => $identity->getKey(),
108
                ],
109
                'account' => [
110
                    'user' => $identity->getUserId(),
111
                    'identity' => $identity->getId(),
112
                ],
113
            ]);
114
115
            throw new IdentityExpired;
116
        }
117
118
        $this->checkCookieKey($identity, $key);
119
120
        $identity->generateNewKey();
121
        $identity->setLastUsed(time());
122
        $identity->setExpiresOn(time() + Entity\Identity::COOKIE_LIFESPAN);
123
124
        $mapper->store($identity);
125
126
        $this->logger->info('cookie updated', [
127
            'account' => [
128
                'user' => $identity->getUserId(),
129
                'identity' => $identity->getId(),
130
            ],
131
        ]);
132
133
        return $identity;
134
    }
135
136
137
    public function logout(Entity\CookieIdentity $identity, $key)
138
    {
139
        if ($identity->getId() === null) {
140
            $this->logCookieError($identity, 'denial of service');
141
            throw new DenialOfServiceAttempt;
142
        }
143
144
        $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
145
146
        $this->checkCookieKey($identity, $key);
147
148
        $identity->setStatus(Entity\Identity::STATUS_DISCARDED);
149
        $mapper->store($identity);
150
151
        $this->logger->info('logout successful', [
152
            'account' => [
153
                'user' => $identity->getUserId(),
154
                'identity' => $identity->getId(),
155
            ],
156
        ]);
157
158
    }
159
160
161
    /**
162
     * Verify that the cookie based identity matches the key and,
163
     * if verification is failed, disable this given identity
164
     *
165
     * @param string $key
166
     * @throws \Palladium\Exception\CompromisedCookie if key does not match
167
     */
168
    private function checkCookieKey(Entity\CookieIdentity $identity, $key)
169
    {
170
        if ($identity->matchKey($key) === true) {
171
            return;
172
        }
173
174
        $identity->setStatus(Entity\Identity::STATUS_BLOCKED);
175
        $mapper->store($identity);
0 ignored issues
show
Bug introduced by
The variable $mapper does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
176
177
        $this->logCookieError($identity, 'compromised cookie');
178
179
        throw new CompromisedCookie;
180
    }
181
182
183
    public function discardRelatedCookies(Entity\Identity $identity)
184
    {
185
        /**
186
         * @NOTE: this operation might require transaction
187
         * or a change in how store() is implemnted in IdentityCollection mapper
188
         */
189
        $list = $this->retrieveIdenitiesByUserId($identity->getUserId(), Entity\Identity::TYPE_COOKIE);
190
191
        foreach ($list as $identity) {
192
            $identity->setStatus(Entity\Identity::STATUS_DISCARDED);
193
        }
194
195
        $mapper = $this->mapperFactory->create(Mapper\IdentityCollection::class);
196
        $mapper->store($list);
197
    }
198
199
200
    private function retrieveIdenitiesByUserId($userId, $type = Entity\Identity::TYPE_ANY, $status = Entity\Identity::STATUS_ACTIVE)
201
    {
202
        $collection = new Entity\IdentityCollection;
203
        $collection->forUserId($userId);
204
        $collection->forType($type);
205
        $collection->forStatus($status);
206
207
        $mapper = $this->mapperFactory->create(Mapper\IdentityCollection::class);
208
        $mapper->fetch($collection);
209
210
        return $collection;
211
    }
212
213
214
    public function changePassword(Entity\EmailIdentity $identity, $oldPassword, $newPassword)
215
    {
216
        $mapper = $this->mapperFactory->create(Mapper\EmailIdentity::class);
217
218
        if ($identity->matchPassword($oldPassword) === false) {
219
            $this->logWrongPasswordWarning($identity, [
220
                'user' => $identity->getUserId(),
221
                'old-key' => md5($oldPassword),
222
                'new-key' => md5($newPassword),
223
            ]);
224
225
            throw new PasswordNotMatch;
226
        }
227
228
        $identity->setPassword($newPassword);
229
        $mapper->store($identity);
230
231
        $this->logger->info('password changed', [
232
            'account' => [
233
                'user' => $identity->getUserId(),
234
                'identity' => $identity->getId(),
235
            ],
236
        ]);
237
    }
238
239
240
    /**
241
     * @param string $message
242
     */
243
    private function logCookieError(Entity\CookieIdentity $identity, $message)
244
    {
245
        $this->logger->error($message, [
246
            'input' => [
247
                'user' => $identity->getUserId(),
248
                'series' => $identity->getSeries(),
249
                'key' => $identity->getKey(),
250
            ],
251
            'account' => [
252
                'user' => $identity->getUserId(),
253
                'identity' => $identity->getId(),
254
            ],
255
        ]);
256
    }
257
258
259
    /**
260
     * @param string $message
0 ignored issues
show
Bug introduced by
There is no parameter named $message. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
261
     */
262
    private function logWrongPasswordWarning(Entity\EmailIdentity $identity, $input)
263
    {
264
        $this->logger->warning('wrong password', [
265
            'input' => $input,
266
            'account' => [
267
                'user' => $identity->getUserId(),
268
                'identity' => $identity->getId(),
269
            ],
270
        ]);
271
    }
272
273
}
274