Passed
Push — master ( 5d2f4a...4ba7a2 )
by Petr
07:39
created

AccountsMultiFile   F

Complexity

Total Complexity 86

Size/Duplication

Total Lines 526
Duplicated Lines 0 %

Test Coverage

Coverage 99.23%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 298
c 1
b 0
f 0
dl 0
loc 526
ccs 258
cts 260
cp 0.9923
rs 2
wmc 86

16 Methods

Rating   Name   Duplication   Size   Complexity  
A getDataOnly() 0 33 5
A getUserClass() 0 3 1
A savePassword() 0 3 1
A noDirectoryDelimiterSet() 0 3 1
A __construct() 0 17 1
A readAccounts() 0 26 3
A saveShadow() 0 3 1
F createAccount() 0 80 14
A openPassword() 0 3 1
B updateCertKeys() 0 35 7
C deleteAccount() 0 50 9
B authenticate() 0 37 10
B getCertData() 0 33 8
F updateAccount() 0 56 17
A openShadow() 0 3 1
B updatePassword() 0 35 6

How to fix   Complexity   

Complex Class

Complex classes like AccountsMultiFile often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AccountsMultiFile, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace kalanis\kw_auth_sources\Sources\Files;
4
5
6
use kalanis\kw_accounts\AccountsException;
7
use kalanis\kw_accounts\Data\FileCertUser;
8
use kalanis\kw_accounts\Interfaces as acc_interfaces;
9
use kalanis\kw_accounts\Traits as account_traits;
10
use kalanis\kw_auth_sources\AuthSourcesException;
11
use kalanis\kw_auth_sources\Interfaces;
12
use kalanis\kw_auth_sources\Traits;
13
use kalanis\kw_locks\Interfaces\ILock;
14
use kalanis\kw_locks\LockException;
15
16
17
/**
18
 * Class AccountsMultiFile
19
 * @package kalanis\kw_auth_sources\Sources\Files
20
 * Authenticate via multiple files
21
 */
22
class AccountsMultiFile implements acc_interfaces\IAuthCert, acc_interfaces\IProcessAccounts
23
{
24
    use Traits\TAuthLock;
25
    use Traits\TLines;
26
    use Traits\TStatusTransform;
27
    use account_traits\TExpiration;
28
29
    const PW_NAME = 0;
30
    const PW_ID = 1;
31
    const PW_GROUP = 2;
32
    const PW_CLASS = 3;
33
    const PW_STATUS = 4;
34
    const PW_DISPLAY = 5;
35
    const PW_DIR = 6;
36
    const PW_EXTRA = 7;
37
    const PW_FEED = 8;
38
39
    const SH_NAME = 0;
40
    const SH_PASS = 1;
41
    const SH_CHANGE_LAST = 2;
42
    const SH_CHANGE_NEXT = 3;
43
    const SH_CERT_SALT = 4;
44
    const SH_CERT_KEY = 5;
45
    const SH_FEED = 6;
46
47
    /** @var Storages\AStorage */
48
    protected $storage = null;
49
    /** @var Interfaces\IHashes */
50
    protected $mode = null;
51
    /** @var Interfaces\IStatus */
52
    protected $status = null;
53
    /** @var Interfaces\IExtraParser */
54
    protected $extraParser = null;
55
    /** @var string[] */
56
    protected $path = [];
57
58
    /**
59
     * @param Storages\AStorage $storage
60
     * @param Interfaces\IHashes $mode
61
     * @param Interfaces\IStatus $status
62
     * @param Interfaces\IExtraParser $parser
63
     * @param ILock $lock
64
     * @param string[] $path
65
     * @param Interfaces\IKAusTranslations|null $lang
66
     */
67 50
    public function __construct(
68
        Storages\AStorage $storage,
69
        Interfaces\IHashes $mode,
70
        Interfaces\IStatus $status,
71
        Interfaces\IExtraParser $parser,
72
        ILock $lock,
73
        array $path,
74
        ?Interfaces\IKAusTranslations $lang = null
75
    )
76
    {
77 50
        $this->setAusLang($lang);
78 50
        $this->initAuthLock($lock);
79 50
        $this->storage = $storage;
80 50
        $this->path = $path;
81 50
        $this->mode = $mode;
82 50
        $this->status = $status;
83 50
        $this->extraParser = $parser;
84 50
    }
85
86 5
    public function authenticate(string $userName, array $params = []): ?acc_interfaces\IUser
87
    {
88 5
        if (empty($params['password'])) {
89 1
            throw new AccountsException($this->getAusLang()->kauPassMustBeSet());
90
        }
91 4
        $time = time();
92 4
        $name = $this->stripChars($userName);
93
94
        try {
95
            // load from shadow
96 4
            $this->checkLock();
97
98
            try {
99 3
                $shadowLines = $this->openShadow();
100 1
            } catch (AuthSourcesException $ex) {
101
                // silence the problems on storage
102 1
                return null;
103
            }
104 2
            foreach ($shadowLines as &$line) {
105
                if (
106 2
                    ($line[static::SH_NAME] == $name)
107 2
                    && $this->mode->checkHash(strval($params['password']), strval($line[static::SH_PASS]))
108 2
                    && ($time < $line[static::SH_CHANGE_NEXT])
109
                ) {
110 2
                    $class = $this->getDataOnly($userName);
111
                    if (
112 2
                        $class
113 2
                        && $this->status->allowLogin($class->getStatus())
114
                    ) {
115 2
                        $this->setExpirationNotice($class, intval($line[static::SH_CHANGE_NEXT]));
116 2
                        return $class;
117
                    }
118
                }
119
            }
120 2
            return null;
121 1
        } catch (AuthSourcesException | LockException $ex) {
122 1
            throw new AccountsException($ex->getMessage(), $ex->getCode(), $ex);
123
        }
124
    }
125
126 6
    public function getDataOnly(string $userName): ?acc_interfaces\IUser
127
    {
128 6
        $name = $this->stripChars($userName);
129
130
        try {
131
            // load from password
132 6
            $this->checkLock();
133
134
            try {
135 5
                $passwordLines = $this->openPassword();
136 1
            } catch (AuthSourcesException $ex) {
137
                // silence the problems on storage
138 1
                return null;
139
            }
140 4
            foreach ($passwordLines as &$line) {
141 4
                if ($line[static::PW_NAME] == $name) {
142 4
                    $user = $this->getUserClass();
143 4
                    $user->setUserData(
144 4
                        strval($line[static::PW_ID]),
145 4
                        strval($line[static::PW_NAME]),
146 4
                        strval($line[static::PW_GROUP]),
147 4
                        intval($line[static::PW_CLASS]),
148 4
                        $this->transformFromStringToInt(strval($line[static::PW_STATUS])),
149 4
                        strval($line[static::PW_DISPLAY]),
150 4
                        strval($line[static::PW_DIR]),
151 4
                        $this->extraParser->expand(strval($line[static::PW_EXTRA]))
152
                    );
153 4
                    return $user;
154
                }
155
            }
156 2
            return null;
157 1
        } catch (AuthSourcesException | LockException $ex) {
158 1
            throw new AccountsException($ex->getMessage(), $ex->getCode(), $ex);
159
        }
160
    }
161
162 8
    protected function getUserClass(): acc_interfaces\IUser
163
    {
164 8
        return new FileCertUser();
165
    }
166
167 4
    public function getCertData(string $userName): ?acc_interfaces\IUserCert
168
    {
169 4
        $name = $this->stripChars($userName);
170
171
        try {
172
            // load from shadow
173 4
            $this->checkLock();
174
175
            try {
176 3
                $shadowLines = $this->openShadow();
177 1
            } catch (AuthSourcesException $ex) {
178
                // silence the problems on storage
179 1
                return null;
180
            }
181 2
            foreach ($shadowLines as &$line) {
182 2
                if ($line[static::SH_NAME] == $name) {
183 1
                    $class = $this->getDataOnly($userName);
184
                    if (
185 1
                        $class
186 1
                        && ($class instanceof acc_interfaces\IUserCert)
187 1
                        && $this->status->allowCert($class->getStatus())
188
                    ) {
189 1
                        $class->addCertInfo(
190 1
                            strval(base64_decode(strval($line[static::SH_CERT_KEY]))),
191 1
                            strval($line[static::SH_CERT_SALT])
192
                        );
193 1
                        return $class;
194
                    }
195
                }
196
            }
197 1
            return null;
198 1
        } catch (AuthSourcesException | LockException $ex) {
199 1
            throw new AccountsException($ex->getMessage(), $ex->getCode(), $ex);
200
        }
201
    }
202
203 3
    public function updatePassword(string $userName, string $passWord): bool
204
    {
205 3
        $name = $this->stripChars($userName);
206
207
        try {
208
            // load from shadow
209 3
            $this->checkLock();
210
211 2
            $changed = false;
212 2
            $this->getLock()->create();
213
            try {
214 2
                $lines = $this->openShadow();
215 1
            } finally {
216 2
                $this->getLock()->delete();
217
            }
218 1
            foreach ($lines as &$line) {
219 1
                if ($line[static::SH_NAME] == $name) {
220 1
                    $changed = true;
221 1
                    $line[static::SH_PASS] = $this->mode->createHash($passWord);
222 1
                    $line[static::SH_CHANGE_NEXT] = $this->whenItExpire();
223
                }
224
            }
225
226 1
            $v2 = true;
227
            try {
228 1
                if ($changed) {
229 1
                    $v2 = $this->saveShadow($lines);
230
                }
231 1
            } finally {
232 1
                $this->getLock()->delete();
233
            }
234 1
            return $changed && $v2;
235
236 2
        } catch (AuthSourcesException | LockException $ex) {
237 2
            throw new AccountsException($ex->getMessage(), $ex->getCode(), $ex);
238
        }
239
    }
240
241 3
    public function updateCertKeys(string $userName, ?string $certKey, ?string $certSalt): bool
242
    {
243 3
        $name = $this->stripChars($userName);
244
245
        try {
246
            // load from shadow
247 3
            $this->checkLock();
248
249 2
            $changed = false;
250 2
            $this->getLock()->create();
251
            try {
252 2
                $lines = $this->openShadow();
253 1
            } finally {
254 2
                $this->getLock()->delete();
255
            }
256 1
            foreach ($lines as &$line) {
257 1
                if ($line[static::SH_NAME] == $name) {
258 1
                    $changed = true;
259 1
                    $line[static::SH_CERT_KEY] = $certKey ? base64_encode($certKey) : $line[static::SH_CERT_KEY];
260 1
                    $line[static::SH_CERT_SALT] = $certSalt ?? $line[static::SH_CERT_SALT];
261
                }
262
            }
263
264 1
            $v2 = true;
265
            try {
266 1
                if ($changed) {
267 1
                    $v2 = $this->saveShadow($lines);
268
                }
269 1
            } finally {
270 1
                $this->getLock()->delete();
271
            }
272 1
            return $changed && $v2;
273
274 2
        } catch (AuthSourcesException | LockException $ex) {
275 2
            throw new AccountsException($ex->getMessage(), $ex->getCode(), $ex);
276
        }
277
    }
278
279 4
    public function createAccount(acc_interfaces\IUser $user, string $password): bool
280
    {
281 4
        $userName = $this->stripChars($user->getAuthName());
282 4
        $displayName = $this->stripChars($user->getDisplayName());
283 4
        $directory = $this->stripChars($user->getDir());
284 4
        $certSalt = '';
285 4
        $certKey = '';
286
287 4
        if ($user instanceof acc_interfaces\IUserCert) {
288 4
            $certSalt = $this->stripChars($user->getPubSalt());
289 4
            $certKey = $user->getPubKey();
290
        }
291
292
        // not everything necessary is set
293 4
        if (empty($userName) || empty($directory) || empty($password)) {
294 1
            throw new AccountsException($this->getAusLang()->kauPassMissParam());
295
        }
296
297
        try {
298 3
            $this->checkLock();
299
300 2
            $uid = acc_interfaces\IUser::LOWEST_USER_ID;
301 2
            $this->getLock()->create();
302
303
            // read password
304
            try {
305 2
                $passLines = $this->openPassword();
306 1
            } catch (AuthSourcesException $ex) {
307 1
                $passLines = [];
308
            }
309 2
            foreach ($passLines as &$line) {
310 1
                $uid = max($uid, $line[static::PW_ID]);
311
            }
312 2
            $uid++;
313
314
            $newUserPass = [
315 2
                static::PW_NAME => $userName,
316 2
                static::PW_ID => $uid,
317 2
                static::PW_GROUP => empty($user->getGroup()) ? $uid : $user->getGroup() ,
318 2
                static::PW_CLASS => empty($user->getClass()) ? acc_interfaces\IProcessClasses::CLASS_USER : $user->getClass() ,
319 2
                static::PW_STATUS => $this->transformFromIntToString($user->getStatus()),
320 2
                static::PW_DISPLAY => empty($displayName) ? $userName : $displayName,
321 2
                static::PW_DIR => $directory,
322 2
                static::PW_EXTRA => $this->extraParser->compact($user->getExtra()),
323 2
                static::PW_FEED => '',
324
            ];
325 2
            ksort($newUserPass);
326 2
            $passLines[] = $newUserPass;
327
328
            // now read shadow
329
            try {
330 2
                $shadeLines = $this->openShadow();
331 1
            } catch (AuthSourcesException $ex) {
332 1
                $shadeLines = [];
333
            }
334
335
            $newUserShade = [
336 2
                static::SH_NAME => $userName,
337 2
                static::SH_PASS => $this->mode->createHash($password),
338 2
                static::SH_CHANGE_LAST => time(),
339 2
                static::SH_CHANGE_NEXT => $this->whenItExpire(),
340 2
                static::SH_CERT_SALT => $certSalt,
341 2
                static::SH_CERT_KEY => $certKey ? base64_encode($certKey) : '',
342 2
                static::SH_FEED => '',
343
            ];
344 2
            ksort($newUserShade);
345 2
            $shadeLines[] = $newUserShade;
346
347
            // now save it all
348 2
            $v1 = $v2 = true;
0 ignored issues
show
Unused Code introduced by
The assignment to $v1 is dead and can be removed.
Loading history...
349
            try {
350 2
                $v1 = $this->savePassword($passLines);
351 2
                $v2 = $this->saveShadow($shadeLines);
352 2
            } finally {
353 2
                $this->getLock()->delete();
354
            }
355 2
            return $v1 && $v2;
356
357 1
        } catch (AuthSourcesException | LockException $ex) {
358 1
            throw new AccountsException($ex->getMessage(), $ex->getCode(), $ex);
359
        }
360
    }
361
362 5
    public function readAccounts(): array
363
    {
364
        try {
365 5
            $this->checkLock();
366
367 4
            $passLines = $this->openPassword();
368 4
            $result = [];
369 4
            foreach ($passLines as &$line) {
370 4
                $record = $this->getUserClass();
371 4
                $record->setUserData(
372 4
                    strval($line[static::PW_ID]),
373 4
                    strval($line[static::PW_NAME]),
374 4
                    strval($line[static::PW_GROUP]),
375 4
                    intval($line[static::PW_CLASS]),
376 4
                    $this->transformFromStringToInt(strval($line[static::PW_STATUS])),
377 4
                    strval($line[static::PW_DISPLAY]),
378 4
                    strval($line[static::PW_DIR]),
379 4
                    $this->extraParser->expand(strval($line[static::PW_EXTRA]))
380
                );
381 4
                $result[] = $record;
382
            }
383
384 4
            return $result;
385
386 1
        } catch (AuthSourcesException | LockException $ex) {
387 1
            throw new AccountsException($ex->getMessage(), $ex->getCode(), $ex);
388
        }
389
    }
390
391 4
    public function updateAccount(acc_interfaces\IUser $user): bool
392
    {
393 4
        $userName = $this->stripChars($user->getAuthName());
394 4
        $directory = $this->stripChars($user->getDir());
395 4
        $displayName = $this->stripChars($user->getDisplayName());
396
397
        try {
398 4
            $this->checkLock();
399
400 3
            $this->getLock()->create();
401 3
            $oldName = null;
402
            try {
403 3
                $passwordLines = $this->openPassword();
404 2
            } finally {
405 3
                $this->getLock()->delete();
406
            }
407 2
            foreach ($passwordLines as &$line) {
408 2
                if (($line[static::PW_NAME] == $userName) && ($line[static::PW_ID] != $user->getAuthId())) {
409 1
                    $this->getLock()->delete();
410 1
                    throw new AuthSourcesException($this->getAusLang()->kauPassLoginExists());
411
                }
412 2
                if ($line[static::PW_ID] == $user->getAuthId()) {
413
                    // REFILL
414 1
                    if (!empty($userName) && $userName != $line[static::PW_NAME]) {
415 1
                        $oldName = $line[static::PW_NAME];
416 1
                        $line[static::PW_NAME] = $userName;
417
                    }
418 1
                    $line[static::PW_GROUP] = !empty($user->getGroup()) ? $user->getGroup() : $line[static::PW_GROUP] ;
419 1
                    $line[static::PW_CLASS] = !empty($user->getClass()) ? $user->getClass() : $line[static::PW_CLASS] ;
420 1
                    $line[static::PW_STATUS] = $this->transformFromIntToString($user->getStatus());
421 1
                    $line[static::PW_DISPLAY] = !empty($displayName) ? $displayName : $line[static::PW_DISPLAY] ;
422 1
                    $line[static::PW_DIR] = !empty($directory) ? $directory : $line[static::PW_DIR] ;
423 1
                    $line[static::PW_EXTRA] = !empty($user->getExtra()) ? $this->extraParser->compact($user->getExtra()) : $line[static::PW_EXTRA] ;
424
                }
425
            }
426
427 1
            $v2 = $v1 = true;
0 ignored issues
show
Unused Code introduced by
The assignment to $v1 is dead and can be removed.
Loading history...
428
            try {
429 1
                $v1 = $this->savePassword($passwordLines);
430
431 1
                if (!is_null($oldName)) {
432 1
                    $lines = $this->openShadow();
433 1
                    foreach ($lines as &$line) {
434 1
                        if ($line[static::SH_NAME] == $oldName) {
435 1
                            $line[static::SH_NAME] = $userName;
436
                        }
437
                    }
438 1
                    $v2 = $this->saveShadow($lines);
439
                }
440 1
            } finally {
441 1
                $this->getLock()->delete();
442
            }
443 1
            return $v1 && $v2;
444
445 3
        } catch (AuthSourcesException | LockException $ex) {
446 3
            throw new AccountsException($ex->getMessage(), $ex->getCode(), $ex);
447
        }
448
    }
449
450 4
    public function deleteAccount(string $userName): bool
451
    {
452 4
        $name = $this->stripChars($userName);
453
454
        try {
455 4
            $this->checkLock();
456
457 3
            $changed = false;
458 3
            $this->getLock()->create();
459
460
            // update password
461
            try {
462 3
                $passLines = $this->openPassword();
463 2
            } finally {
464 3
                $this->getLock()->delete();
465
            }
466 2
            foreach ($passLines as $index => &$line) {
467 2
                if ($line[static::PW_NAME] == $name) {
468 1
                    unset($passLines[$index]);
469 1
                    $changed = true;
470
                }
471
            }
472
473
            // now update shadow
474
            try {
475 2
                $shadeLines = $this->openShadow();
476 1
            } finally {
477 2
                $this->getLock()->delete();
478
            }
479 1
            foreach ($shadeLines as $index => &$line) {
480 1
                if ($line[static::SH_NAME] == $name) {
481 1
                    unset($shadeLines[$index]);
482 1
                    $changed = true;
483
                }
484
            }
485
486
            // now save it all
487 1
            $v1 = $v2 = true;
488
            try {
489 1
                if ($changed) {
490 1
                    $v1 = $this->savePassword($passLines);
491 1
                    $v2 = $this->saveShadow($shadeLines);
492
                }
493 1
            } finally {
494 1
                $this->getLock()->delete();
495
            }
496 1
            return $changed && $v1 && $v2;
497
498 3
        } catch (AuthSourcesException | LockException $ex) {
499 3
            throw new AccountsException($ex->getMessage(), $ex->getCode(), $ex);
500
        }
501
    }
502
503
    /**
504
     * @throws AuthSourcesException
505
     * @return array<int, array<int, string|int>>
506
     */
507 13
    protected function openPassword(): array
508
    {
509 13
        return $this->storage->read(array_merge($this->path, [Interfaces\IFile::PASS_FILE]));
510
    }
511
512
    /**
513
     * @param array<int, array<int, string|int>> $lines
514
     * @throws AuthSourcesException
515
     * @return bool
516
     */
517 2
    protected function savePassword(array $lines): bool
518
    {
519 2
        return $this->storage->write(array_merge($this->path, [Interfaces\IFile::PASS_FILE]), $lines);
520
    }
521
522
    /**
523
     * @throws AuthSourcesException
524
     * @return array<int, array<int, string|int>>
525
     */
526 8
    protected function openShadow(): array
527
    {
528 8
        return $this->storage->read(array_merge($this->path, [Interfaces\IFile::SHADE_FILE]));
529
    }
530
531
    /**
532
     * @param array<int, array<int, string|int>> $lines
533
     * @throws AuthSourcesException
534
     * @return bool
535
     */
536 2
    protected function saveShadow(array $lines): bool
537
    {
538 2
        return $this->storage->write(array_merge($this->path, [Interfaces\IFile::SHADE_FILE]), $lines);
539
    }
540
541
    /**
542
     * @return string
543
     * @codeCoverageIgnore translation
544
     */
545
    protected function noDirectoryDelimiterSet(): string
546
    {
547
        return $this->getAusLang()->kauNoDelimiterSet();
548
    }
549
}
550