Completed
Push — master ( 801091...de4e4b )
by Mārtiņš
02:04
created

Identification::logWrongPasswordWarning()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

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