Passed
Push — master ( 141183...2b56eb )
by Petr
08:03
created

AFiles::getCertData()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 30
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 7

Importance

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