AccountsMultiFile::createAccount()   F
last analyzed

Complexity

Conditions 14
Paths 2980

Size

Total Lines 80
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 51
CRAP Score 14

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
eloc 56
c 1
b 0
f 0
nc 2980
nop 2
dl 0
loc 80
ccs 51
cts 51
cp 1
crap 14
rs 2.1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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