Failed Conditions
Pull Request — master (#2127)
by Janko
10:05
created

Session::loadUser()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 7
nc 5
nop 1
dl 0
loc 14
ccs 0
cts 8
cp 0
crap 20
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Stu\Lib\Session;
4
5
use DateTime;
6
use Override;
0 ignored issues
show
Bug introduced by
The type Override was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use RuntimeException;
8
use Stu\Component\Game\TimeConstants;
0 ignored issues
show
Bug introduced by
The type Stu\Component\Game\TimeConstants was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use Stu\Exception\SessionInvalidException;
10
use Stu\Lib\LoginException;
11
use Stu\Lib\UserLockedException;
12
use Stu\Module\Control\StuHashInterface;
13
use Stu\Module\Logging\LoggerUtilFactoryInterface;
14
use Stu\Module\Logging\LoggerUtilInterface;
15
use Stu\Module\PlayerSetting\Lib\UserEnum;
0 ignored issues
show
Bug introduced by
The type Stu\Module\PlayerSetting\Lib\UserEnum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
use Stu\Orm\Entity\UserInterface;
17
use Stu\Orm\Entity\UserLockInterface;
18
use Stu\Orm\Repository\SessionStringRepositoryInterface;
19
use Stu\Orm\Repository\UserIpTableRepositoryInterface;
20
use Stu\Orm\Repository\UserRepositoryInterface;
21
22
final class Session implements SessionInterface
23
{
24
    private LoggerUtilInterface $loggerUtil;
25
26
    private ?UserInterface $user = null;
27
28 2
    public function __construct(
29
        private UserIpTableRepositoryInterface $userIpTableRepository,
30
        private SessionStringRepositoryInterface $sessionStringRepository,
31
        private UserRepositoryInterface $userRepository,
32
        private StuHashInterface $stuHash,
33
        LoggerUtilFactoryInterface $loggerUtilFactory
34
    ) {
35 2
        $this->loggerUtil = $loggerUtilFactory->getLoggerUtil();
36
    }
37
38
    #[Override]
39
    public function createSession(bool $session_check = true): void
40
    {
41
        if (!$this->isLoggedIn() && $session_check) {
42
            throw new SessionInvalidException('Session abgelaufen');
43
        }
44
        if ($session_check && (!$_SESSION['uid'] || !$_SESSION['login'])) {
45
            $this->logout();
46
            return;
47
        }
48
        if ($this->isLoggedIn() && $session_check) {
49
            $this->chklogin();
50
        }
51
    }
52
53
    /**
54
     * @api
55
     */
56
    #[Override]
57
    public function checkLoginCookie(): void
58
    {
59
        $sstr = $_COOKIE['sstr'] ?? '';
60
        $uid = (int) ($_SESSION['uid'] ?? 0);
61
        if ($uid > 0) {
62
            $this->performCookieLogin($uid, $sstr);
63
        }
64
    }
65
66
    private function isLoggedIn(): bool
67
    {
68
        return array_key_exists('uid', $_SESSION)
69
            && array_key_exists('login', $_SESSION)
70
            && $_SESSION['login'] == 1;
71
    }
72
73
    /**
74
     * @api
75
     */
76
    #[Override]
77
    public function getUser(): ?UserInterface
78
    {
79
        return $this->user;
80
    }
81
82
    private function getUserMandatory(): UserInterface
83
    {
84
        if ($this->user === null) {
85
            throw new SessionInvalidException("No user logged in");
86
        }
87
88
        return $this->user;
89
    }
90
91
    #[Override]
92
    public function login(string $login, string $password): bool
93
    {
94
        $this->destroyLoginCookies();
95
96
        $user = $this->loadUser($login);
97
98
        $this->checkPassword($user, $password);
99
        $this->checkUser($user);
100
        $this->updateUser($user);
101
        $this->setUser($user);
102
103
        $this->sessionStringRepository->truncate($user);
104
105
        if (!$user->isSaveLogin()) {
106
            $cookieString = $this->buildCookieString($user);
107
            $this->loggerUtil->log(sprintf('noSaveLogin, set cookieString: %s', $cookieString));
108
            setcookie('sstr', $cookieString, ['expires' => time() + TimeConstants::TWO_DAYS_IN_SECONDS]);
109
        }
110
111
        // register login
112
        $this->addIpTableEntry($user);
113
114
        return true;
115
    }
116
117
    private function loadUser(string $login): UserInterface
118
    {
119
        $user = $this->userRepository->getByLogin(mb_strtolower($login));
120
        if ($user === null) {
121
            if (is_numeric($login)) {
122
                $user = $this->userRepository->find((int)$login);
123
            }
124
125
            if ($user === null) {
126
                throw new LoginException(_('Login oder Passwort inkorrekt'));
127
            }
128
        }
129
130
        return $user;
131
    }
132
133
    private function checkPassword(UserInterface $user, string $password): void
134
    {
135
        $password_hash = $user->getPassword();
136
137
        if (!password_verify($password, $password_hash)) {
138
            throw new LoginException(_('Login oder Passwort inkorrekt'));
139
        }
140
141
        if (password_needs_rehash($password_hash, PASSWORD_DEFAULT)) {
142
            $user->setPassword(password_hash($password, PASSWORD_DEFAULT));
143
144
            $this->userRepository->save($user);
145
        }
146
    }
147
148
    private function checkUser(UserInterface $user): void
149
    {
150
        if ($user->isLocked()) {
151
            /** @var UserLockInterface $userLock */
152
            $userLock = $user->getUserLock();
153
154
            throw new UserLockedException(
155
                _('Dein Spieleraccount wurde gesperrt'),
156
                sprintf(_('Dein Spieleraccount ist noch für %d Ticks gesperrt. Begründung: %s'), $userLock->getRemainingTicks(), $userLock->getReason())
157
            );
158
        }
159
        if ($user->getDeletionMark() === UserEnum::DELETION_CONFIRMED) {
160
            throw new LoginException(_('Dein Spieleraccount ist zur Löschung vorgesehen'));
161
        }
162
    }
163
164
    private function updateUser(UserInterface $user): void
165
    {
166
        if ($user->getState() === UserEnum::USER_STATE_NEW) {
167
            $user->setState(UserEnum::USER_STATE_UNCOLONIZED);
168
169
            $this->userRepository->save($user);
170
        }
171
172
        if ($user->isVacationMode()) {
173
            $user->setVacationMode(false);
174
        }
175
176
        $this->userRepository->save($user);
177
    }
178
179
    private function setUser(UserInterface $user): void
180
    {
181
        $_SESSION['uid'] = $user->getId();
182
        $_SESSION['login'] = 1;
183
184
        $this->user = $user;
185
    }
186
187
    private function addIpTableEntry(UserInterface $user): void
188
    {
189
        $ipTableEntry = $this->userIpTableRepository->prototype();
190
        $ipTableEntry->setUser($user);
191
        $ipTableEntry->setIp((string) getenv('REMOTE_ADDR'));
192
        $ipTableEntry->setSessionId((string) session_id());
193
        $ipTableEntry->setUserAgent((string) getenv('HTTP_USER_AGENT'));
194
        $ipTableEntry->setStartDate(new DateTime());
195
196
        $this->userIpTableRepository->save($ipTableEntry);
197
    }
198
199
    private function buildCookieString(UserInterface $user): string
200
    {
201
        return $this->stuHash->hash(($user->getId() . $user->getEMail() . $user->getCreationDate()));
202
    }
203
204
    private function destroySession(?UserInterface $user = null): void
205
    {
206
        $userToTruncate = $user ?? $this->user;
207
        if ($userToTruncate !== null) {
208
            $this->sessionStringRepository->truncate($userToTruncate);
209
        }
210
211
        if ($user === null) {
212
            $this->destroyLoginCookies();
213
            $sessionName = session_name();
214
            if ($sessionName) {
215
                setcookie($sessionName, '', ['expires' => time() - 42000]);
216
            }
217
            if (@session_destroy() === false) {
218
                throw new RuntimeException('The session could not be destroyed');
219
            }
220
221
            $this->user = null;
222
        }
223
    }
224
225
    private function destroyLoginCookies(): void
226
    {
227
        setcookie('sstr');
228
    }
229
230
    #[Override]
231
    public function logout(?UserInterface $user = null): void
232
    {
233
        $this->destroySession($user);
234
    }
235
236
    private function performCookieLogin(int $uid, string $sstr): void
237
    {
238
        if (strlen($sstr) != 40) {
239
            $this->destroySession();
240
            return;
241
        }
242
        $user = $this->userRepository->find($uid);
243
        if ($user === null) {
244
            $this->destroySession();
245
            return;
246
        }
247
        if ($this->buildCookieString($user) !== $sstr) {
248
            $this->destroySession();
249
            return;
250
        }
251
        if ($user->getState() == UserEnum::USER_STATE_NEW) {
252
            throw new SessionInvalidException("Aktivierung");
253
        }
254
        if ($user->isLocked()) {
255
            throw new SessionInvalidException("Gesperrt");
256
        }
257
        if ($user->getDeletionMark() === UserEnum::DELETION_CONFIRMED) {
258
            throw new SessionInvalidException("Löschung");
259
        }
260
        if ($user->isVacationMode() === true) {
261
            $user->setVacationMode(false);
262
        }
263
        $this->userRepository->save($user);
264
265
        $this->setUser($user);
266
267
        $this->sessionStringRepository->truncate($user);
268
269
        //start session if not already active
270
        if (session_id() == '') {
271
            session_start();
272
        }
273
274
        // register login
275
        $this->addIpTableEntry($user);
276
    }
277
278
    private function chklogin(): void
279
    {
280
        if (!$this->isLoggedIn()) {
281
            throw new SessionInvalidException("Not logged in");
282
        }
283
284
        $userId = (int) $_SESSION['uid'];
285
286
        $user = $this->userRepository->find($userId);
287
288
        if ($user === null) {
289
            $this->logout();
290
            return;
291
        }
292
        $user->setLastaction(time());
293
294
        $this->userRepository->save($user);
295
296
        $sessionId = session_id();
297
        if (!$sessionId) {
298
            throw new SessionInvalidException("Session Id not set");
299
        }
300
301
        $ipTableEntry = $this->userIpTableRepository->findBySessionId($sessionId);
302
        if ($ipTableEntry !== null) {
303
            $ipTableEntry->setEndDate(new DateTime());
304
305
            $this->userIpTableRepository->save($ipTableEntry);
306
        }
307
308
        $_SESSION['login'] = 1;
309
310
        $this->user = $user;
311
    }
312
313
    /**
314
     * @api
315
     */
316
    #[Override]
317
    public function storeSessionData(string|int $key, mixed $value, bool $isSingleValue = false): void
318
    {
319
        $stored = false;
320
        $user = $this->getUserMandatory();
321
322
        $data = $user->getSessionDataUnserialized();
323
        if (!array_key_exists($key, $data)) {
324
            if ($isSingleValue) {
325
                $data[$key] = $value;
326
                $stored = true;
327
            } else {
328
                $data[$key] = [];
329
            }
330
        }
331
        if (!$isSingleValue && !array_key_exists($value, $data[$key])) {
332
            $data[$key][$value] = 1;
333
            $stored = true;
334
        }
335
336
        if ($stored) {
337
            $user->setSessionData(serialize($data));
338
            $this->userRepository->save($user);
339
        }
340
    }
341
342
    /**
343
     * @api
344
     */
345
    #[Override]
346
    public function deleteSessionData(string $key, mixed $value = null): void
347
    {
348
        $user = $this->getUserMandatory();
349
350
        $data = $user->getSessionDataUnserialized();
351
        if (!array_key_exists($key, $data)) {
352
            return;
353
        }
354
        if ($value === null) {
355
            unset($data[$key]);
356
        } else {
357
            if (!array_key_exists($value, $data[$key])) {
358
                return;
359
            }
360
            unset($data[$key][$value]);
361
        }
362
        $user->setSessionData(serialize($data));
363
        $this->userRepository->save($user);
364
    }
365
366
    /**
367
     * @api
368
     */
369
    #[Override]
370
    public function hasSessionValue(string $key, mixed $value): bool
371
    {
372
        $data = $this->getUserMandatory()->getSessionDataUnserialized();
373
        if (!array_key_exists($key, $data)) {
374
            return false;
375
        }
376
        return array_key_exists($value, $data[$key]);
377
    }
378
379
    /**
380
     * @api
381
     */
382
    #[Override]
383
    public function getSessionValue(string $key): mixed
384
    {
385
        $data = $this->getUserMandatory()->getSessionDataUnserialized();
386
        if (!array_key_exists($key, $data)) {
387
            return false;
388
        }
389
        return $data[$key];
390
    }
391
}
392