Passed
Push — master ( d309df...7ec489 )
by Petr
02:37
created

AFiles::updateAccount()   F

Complexity

Conditions 15
Paths 410

Size

Total Lines 48
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 15

Importance

Changes 0
Metric Value
cc 15
eloc 33
c 0
b 0
f 0
nc 410
nop 1
dl 0
loc 48
ccs 32
cts 32
cp 1
crap 15
rs 2.5694

How to fix   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\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 1
        } catch (AuthException $ex) {
165 1
            $this->getLock()->delete();
166 1
            throw $ex;
167
        }
168 2
        foreach ($lines as &$line) {
169 2
            if ($line[static::SH_NAME] == $name) {
170 2
                $changed = true;
171 2
                $line[static::SH_PASS] = $this->mode->hash($passWord);
172 2
                $line[static::SH_CHANGE_NEXT] = $this->whenItExpire();
173
            }
174
        }
175 2
        if ($changed) {
176 2
            $this->saveShadow($lines);
177
        }
178 2
        $this->getLock()->delete();
179
    }
180
181 3
    public function updateCertKeys(string $userName, ?string $certKey, ?string $certSalt): void
182
    {
183 3
        $name = $this->stripChars($userName);
184
        // load from shadow
185 3
        $this->checkLock();
186
187 3
        $changed = false;
188 3
        $this->getLock()->create();
189
        try {
190 3
            $lines = $this->openShadow();
191 1
        } catch (AuthException $ex) {
192 1
            $this->getLock()->delete();
193 1
            throw $ex;
194
        }
195 2
        foreach ($lines as &$line) {
196 2
            if ($line[static::SH_NAME] == $name) {
197 2
                $changed = true;
198 2
                $line[static::SH_CERT_KEY] = $certKey ? base64_encode($certKey) : $line[static::SH_CERT_KEY];
199 2
                $line[static::SH_CERT_SALT] = $certSalt ?: $line[static::SH_CERT_SALT];
200
            }
201
        }
202 2
        if ($changed) {
203 2
            $this->saveShadow($lines);
204
        }
205 2
        $this->getLock()->delete();
206
    }
207
208 5
    public function createAccount(Interfaces\IUser $user, string $password): void
209
    {
210 5
        $userName = $this->stripChars($user->getAuthName());
211 5
        $displayName = $this->stripChars($user->getDisplayName());
212 5
        $directory = $this->stripChars($user->getDir());
213 5
        $certSalt = '';
214 5
        $certKey = '';
215
216 5
        if ($user instanceof Interfaces\IUserCert) {
217 5
            $certSalt = $this->stripChars($user->getPubSalt());
218 5
            $certKey = $user->getPubKey();
219
        }
220
221
        // not everything necessary is set
222 5
        if (empty($userName) || empty($directory) || empty($password)) {
223 2
            throw new AuthException($this->getLang()->kauPassMissParam());
224
        }
225 3
        $this->checkLock();
226
227 3
        $uid = Interfaces\IUser::LOWEST_USER_ID;
228 3
        $this->getLock()->create();
229
230
        // read password
231
        try {
232 3
            $passLines = $this->openPassword();
233 1
        } catch (AuthException $ex) {
234 1
            $passLines = [];
235
        }
236 3
        foreach ($passLines as &$line) {
237 2
            $uid = max($uid, $line[static::PW_ID]);
238
        }
239 3
        $uid++;
240
241
        $newUserPass = [
242
            static::PW_NAME => $userName,
243
            static::PW_ID => $uid,
244 3
            static::PW_GROUP => empty($user->getGroup()) ? $uid : $user->getGroup() ,
245 3
            static::PW_CLASS => empty($user->getClass()) ? Interfaces\IAccessClasses::CLASS_USER : $user->getClass() ,
246 3
            static::PW_DISPLAY => empty($displayName) ? $userName : $displayName,
247
            static::PW_DIR => $directory,
248
            static::PW_FEED => '',
249
        ];
250 3
        ksort($newUserPass);
251 3
        $passLines[] = $newUserPass;
252
253
        // now read shadow
254
        try {
255 3
            $shadeLines = $this->openShadow();
256 1
        } catch (AuthException $ex) {
257 1
            $shadeLines = [];
258
        }
259
260
        $newUserShade = [
261
            static::SH_NAME => $userName,
262 3
            static::SH_PASS => $this->mode->hash($password),
263 3
            static::SH_CHANGE_LAST => time(),
264 3
            static::SH_CHANGE_NEXT => $this->whenItExpire(),
265
            static::SH_CERT_SALT => $certSalt,
266 3
            static::SH_CERT_KEY => $certKey ? base64_encode($certKey) : '',
267
            static::SH_FEED => '',
268
        ];
269 3
        ksort($newUserShade);
270 3
        $shadeLines[] = $newUserShade;
271
272
        // now save it all
273
        try {
274 3
            $this->savePassword($passLines);
275 3
            $this->saveShadow($shadeLines);
276 3
        } finally {
277 3
            $this->getLock()->delete();
278
        }
279
    }
280
281
    /**
282
     * @throws AuthException
283
     * @throws LockException
284
     * @return Interfaces\IUser[]
285
     */
286 2
    public function readAccounts(): array
287
    {
288 2
        $this->checkLock();
289
290 2
        $passLines = $this->openPassword();
291 2
        $result = [];
292 2
        foreach ($passLines as &$line) {
293 2
            $record = $this->getUserClass();
294 2
            $record->setData(
295 2
                intval($line[static::PW_ID]),
296 2
                strval($line[static::PW_NAME]),
297 2
                intval($line[static::PW_GROUP]),
298 2
                intval($line[static::PW_CLASS]),
299 2
                strval($line[static::PW_DISPLAY]),
300 2
                strval($line[static::PW_DIR])
301
            );
302 2
            $result[] = $record;
303
        }
304
305 2
        return $result;
306
    }
307
308 5
    public function updateAccount(Interfaces\IUser $user): void
309
    {
310 5
        $userName = $this->stripChars($user->getAuthName());
311 5
        $directory = $this->stripChars($user->getDir());
312 5
        $displayName = $this->stripChars($user->getDisplayName());
313
314 5
        $this->checkLock();
315
316 5
        $this->getLock()->create();
317 5
        $oldName = null;
318
        try {
319 5
            $passwordLines = $this->openPassword();
320 1
        } catch (AuthException $ex) {
321 1
            $this->getLock()->delete();
322 1
            throw $ex;
323
        }
324 4
        foreach ($passwordLines as &$line) {
325 4
            if (($line[static::PW_NAME] == $userName) && ($line[static::PW_ID] != $user->getAuthId())) {
326 2
                $this->getLock()->delete();
327 2
                throw new AuthException($this->getLang()->kauPassLoginExists());
328
            }
329 4
            if ($line[static::PW_ID] == $user->getAuthId()) {
330
                // REFILL
331 2
                if (!empty($userName) && $userName != $line[static::PW_NAME]) {
332 2
                    $oldName = $line[static::PW_NAME];
333 2
                    $line[static::PW_NAME] = $userName;
334
                }
335 2
                $line[static::PW_GROUP] = !empty($user->getGroup()) ? $user->getGroup() : $line[static::PW_GROUP] ;
336 2
                $line[static::PW_CLASS] = !empty($user->getClass()) ? $user->getClass() : $line[static::PW_CLASS] ;
337 2
                $line[static::PW_DISPLAY] = !empty($displayName) ? $displayName : $line[static::PW_DISPLAY] ;
338 2
                $line[static::PW_DIR] = !empty($directory) ? $directory : $line[static::PW_DIR] ;
339
            }
340
        }
341
342
        try {
343 2
            $this->savePassword($passwordLines);
344
345 2
            if (!is_null($oldName)) {
346 2
                $lines = $this->openShadow();
347 2
                foreach ($lines as &$line) {
348 2
                    if ($line[static::SH_NAME] == $oldName) {
349 2
                        $line[static::SH_NAME] = $userName;
350
                    }
351
                }
352 2
                $this->saveShadow($lines);
353
            }
354 2
        } finally {
355 2
            $this->getLock()->delete();
356
        }
357
    }
358
359 4
    public function deleteAccount(string $userName): void
360
    {
361 4
        $name = $this->stripChars($userName);
362 4
        $this->checkLock();
363
364 4
        $changed = false;
365 4
        $this->getLock()->create();
366
367
        // update password
368
        try {
369 4
            $passLines = $this->openPassword();
370 1
        } catch (AuthException $ex) {
371 1
            $this->getLock()->delete();
372 1
            throw $ex;
373
        }
374 3
        foreach ($passLines as $index => &$line) {
375 3
            if ($line[static::PW_NAME] == $name) {
376 2
                unset($passLines[$index]);
377 2
                $changed = true;
378
            }
379
        }
380
381
        // now update shadow
382
        try {
383 3
            $shadeLines = $this->openShadow();
384 1
        } catch (AuthException $ex) {
385 1
            $this->getLock()->delete();
386 1
            throw $ex;
387
        }
388 2
        foreach ($shadeLines as $index => &$line) {
389 2
            if ($line[static::SH_NAME] == $name) {
390 2
                unset($shadeLines[$index]);
391 2
                $changed = true;
392
            }
393
        }
394
395
        // now save it all
396
        try {
397 2
            if ($changed) {
398 2
                $this->savePassword($passLines);
399 2
                $this->saveShadow($shadeLines);
400
            }
401 2
        } finally {
402 2
            $this->getLock()->delete();
403
        }
404
    }
405
406 4
    protected function checkRest(int $groupId): void
407
    {
408 4
        $passLines = $this->openPassword();
409 4
        foreach ($passLines as &$line) {
410 4
            if ($line[static::PW_GROUP] == $groupId) {
411 2
                throw new AuthException($this->getLang()->kauGroupHasMembers());
412
            }
413
        }
414
    }
415
416
    /**
417
     * @throws AuthException
418
     * @return array<int, array<int, string|int>>
419
     */
420 19
    protected function openPassword(): array
421
    {
422 19
        return $this->openFile($this->path . DIRECTORY_SEPARATOR . Interfaces\IFile::PASS_FILE);
423
    }
424
425
    /**
426
     * @param array<int, array<int, string|int>> $lines
427
     * @throws AuthException
428
     */
429 3
    protected function savePassword(array $lines): void
430
    {
431 3
        $this->saveFile($this->path . DIRECTORY_SEPARATOR . Interfaces\IFile::PASS_FILE, $lines);
432
    }
433
434
    /**
435
     * @throws AuthException
436
     * @return array<int, array<int, string|int>>
437
     */
438 10
    protected function openShadow(): array
439
    {
440 10
        return $this->openFile($this->path . DIRECTORY_SEPARATOR . Interfaces\IFile::SHADE_FILE);
441
    }
442
443
    /**
444
     * @param array<int, array<int, string|int>> $lines
445
     * @throws AuthException
446
     */
447 3
    protected function saveShadow(array $lines): void
448
    {
449 3
        $this->saveFile($this->path . DIRECTORY_SEPARATOR . Interfaces\IFile::SHADE_FILE, $lines);
450
    }
451
452
    /**
453
     * @throws AuthException
454
     * @return array<int, array<int, string|int>>
455
     */
456 4
    protected function openGroups(): array
457
    {
458 4
        return $this->openFile($this->path . DIRECTORY_SEPARATOR . Interfaces\IFile::GROUP_FILE);
459
    }
460
461
    /**
462
     * @param array<int, array<int, string|int>> $lines
463
     * @throws AuthException
464
     */
465 2
    protected function saveGroups(array $lines): void
466
    {
467 2
        $this->saveFile($this->path . DIRECTORY_SEPARATOR . Interfaces\IFile::GROUP_FILE, $lines);
468
    }
469
}
470