Completed
Push — master ( 26c17f...1133b5 )
by Mārtiņš
02:26
created

Identification::checkCookieKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 8

Duplication

Lines 15
Ratio 100 %

Code Coverage

Tests 8
CRAP Score 2

Importance

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