Passed
Push — master ( f1fb72...be1df6 )
by Petr
08:10
created

AccountsMultiFile::noDirectoryDelimiterSet()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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