Passed
Push — master ( 2f5fd4...141183 )
by Petr
02:34
created

AFiles::updateCertKeys()   A

Complexity

Conditions 6
Paths 24

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 6

Importance

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