Completed
Push — master ( 6cce77...4639e9 )
by Mārtiņš
02:13
created

Identification::createCookieIdentity()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 14
cts 14
cp 1
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 14
nc 2
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\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
     * @return Palladium\Entity\CookieIdentity
36
     */
37 2
    public function loginWithPassword(Entity\EmailIdentity $identity, $password)
38
    {
39 2
        if ($identity->matchPassword($password) === false) {
40 1
            $this->logWrongPasswordWarning($identity, [
41 1
                'identifier' => $identity->getIdentifier(),
42 1
                'key' => md5($password),
43
            ]);
44
45 1
            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
            'user' => [
56 1
                'account' => $identity->getAccountId(),
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->setAccountId($identity->getAccountId());
80 1
        $cookie->generateNewSeries();
81
82 1
        $cookie->generateNewKey();
83 1
        $cookie->setStatus(Entity\Identity::STATUS_ACTIVE);
84 1
        $cookie->setExpiresOn(time() + Entity\Identity::COOKIE_LIFESPAN);
85
86
87 1
        $parentId = $identity->getParentId();
88
89 1
        if (null === $parentId) {
90 1
            $parentId = $identity->getId();
91
        }
92
93 1
        $cookie->setParentId($parentId);
94
95 1
        $mapper->store($cookie);
96
97 1
        return $cookie;
98
    }
99
100
101
    /**
102
     * @param string @key
103
     *
104
     * @return Palladium\Entity\CookieIdentity
105
     */
106 4
    public function loginWithCookie(Entity\CookieIdentity $identity, $key)
107
    {
108 4
        if ($identity->getId() === null) {
109 1
            $this->logCookieError($identity, 'denial of service');
110 1
            throw new DenialOfServiceAttempt;
111
        }
112
113 3
        $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
114
115 3
        if ($identity->getExpiresOn() < time()) {
116 1
            $identity->setStatus(Entity\Identity::STATUS_EXPIRED);
117 1
            $mapper->store($identity);
118 1
            $this->logger->info('cookie expired', [
119
                'input' => [
120 1
                    'account' => $identity->getAccountId(),
121 1
                    'series' => $identity->getSeries(),
122 1
                    'key' => $identity->getKey(),
123
                ],
124
                'user' => [
125 1
                    'account' => $identity->getAccountId(),
126 1
                    'identity' => $identity->getId(),
127
                ],
128
            ]);
129
130 1
            throw new IdentityExpired;
131
        }
132
133 2
        $this->checkCookieKey($identity, $key);
134
135 1
        $identity->generateNewKey();
136 1
        $identity->setLastUsed(time());
137 1
        $identity->setExpiresOn(time() + Entity\Identity::COOKIE_LIFESPAN);
138
139 1
        $mapper->store($identity);
140 1
        $this->logExpectedBehaviour($identity, 'cookie updated');
141
142 1
        return $identity;
143
    }
144
145
146
    /**
147
     * @param string $key
148
     */
149 2
    public function logout(Entity\CookieIdentity $identity, $key)
150
    {
151 2
        if ($identity->getId() === null) {
152 1
            $this->logCookieError($identity, 'denial of service');
153 1
            throw new DenialOfServiceAttempt;
154
        }
155
156 1
        $this->checkCookieKey($identity, $key);
157 1
        $identity->setStatus(Entity\Identity::STATUS_DISCARDED);
158
159 1
        $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
160 1
        $mapper->store($identity);
161
162 1
        $this->logExpectedBehaviour($identity, 'logout successful');
163 1
    }
164
165
166
    /**
167
     * Verify that the cookie based identity matches the key and,
168
     * if verification is failed, disable this given identity
169
     *
170
     * @param string $key
171
     * @throws \Palladium\Exception\CompromisedCookie if key does not match
172
     */
173 3
    private function checkCookieKey(Entity\CookieIdentity $identity, $key)
174
    {
175 3
        if ($identity->matchKey($key) === true) {
176 2
            return;
177
        }
178
179 1
        $identity->setStatus(Entity\Identity::STATUS_BLOCKED);
180
181 1
        $mapper = $this->mapperFactory->create(Mapper\CookieIdentity::class);
182 1
        $mapper->store($identity);
183
184 1
        $this->logCookieError($identity, 'compromised cookie');
185
186 1
        throw new CompromisedCookie;
187
    }
188
189
190 1
    public function discardIdentities(Entity\IdentityCollection $list)
191
    {
192 1
        foreach ($list as $identity) {
193 1
            $identity->setStatus(Entity\Identity::STATUS_DISCARDED);
194
        }
195
196 1
        $mapper = $this->mapperFactory->create(Mapper\IdentityCollection::class);
197 1
        $mapper->store($list);
198 1
    }
199
200
201
    /**
202
     * @param string $oldPassword
203
     * @param string $newPassword
204
     */
205 2
    public function changePassword(Entity\EmailIdentity $identity, $oldPassword, $newPassword)
206
    {
207 2
        $mapper = $this->mapperFactory->create(Mapper\EmailIdentity::class);
208
209 2
        if ($identity->matchPassword($oldPassword) === false) {
210 1
            $this->logWrongPasswordWarning($identity, [
211 1
                'account' => $identity->getAccountId(),
212 1
                'old-key' => md5($oldPassword),
213 1
                'new-key' => md5($newPassword),
214
            ]);
215
216 1
            throw new PasswordNotMatch;
217
        }
218
219 1
        $identity->setPassword($newPassword);
220 1
        $mapper->store($identity);
221
222 1
        $this->logExpectedBehaviour($identity, 'password changed');
223 1
    }
224
225
226
    /**
227
     * @param string $message
228
     */
229 3
    private function logCookieError(Entity\CookieIdentity $identity, $message)
230
    {
231 3
        $this->logger->error($message, [
232
            'input' => [
233 3
                'account' => $identity->getAccountId(),
234 3
                'series' => $identity->getSeries(),
235 3
                'key' => $identity->getKey(),
236
            ],
237
            'user' => [
238 3
                'account' => $identity->getAccountId(),
239 3
                'identity' => $identity->getId(),
240
            ],
241
        ]);
242 3
    }
243
244
245
    /**
246
     * @param array $input
247
     */
248 2
    private function logWrongPasswordWarning(Entity\EmailIdentity $identity, $input)
249
    {
250 2
        $this->logger->warning('wrong password', [
251 2
            'input' => $input,
252
            'user' => [
253 2
                'account' => $identity->getAccountId(),
254 2
                'identity' => $identity->getId(),
255
            ],
256
        ]);
257 2
    }
258
259
260 3
    private function logExpectedBehaviour(Entity\Identity $identity, $message)
261
    {
262 3
        $this->logger->info($message, [
263
            'user' => [
264 3
                'account' => $identity->getAccountId(),
265 3
                'identity' => $identity->getId(),
266
            ],
267
        ]);
268 3
    }
269
}
270