AccountsSingleFile   B
last analyzed

Complexity

Total Complexity 51

Size/Duplication

Total Lines 347
Duplicated Lines 0 %

Test Coverage

Coverage 98.75%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 185
c 1
b 0
f 0
dl 0
loc 347
ccs 158
cts 160
cp 0.9875
rs 7.92
wmc 51

12 Methods

Rating   Name   Duplication   Size   Complexity  
B deleteAccount() 0 38 6
A getDataOnly() 0 22 5
A savePassword() 0 3 1
B createAccount() 0 54 10
A __construct() 0 17 1
A openPassword() 0 3 1
A getUserClass() 0 14 1
D updateAccount() 0 36 9
A noDirectoryDelimiterSet() 0 3 1
A readAccounts() 0 15 3
B authenticate() 0 30 8
A updatePassword() 0 32 5

How to fix   Complexity   

Complex Class

Complex classes like AccountsSingleFile often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AccountsSingleFile, and based on these observations, apply Extract Interface, too.

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\FileUser;
8
use kalanis\kw_accounts\Interfaces as acc_interfaces;
9
use kalanis\kw_auth_sources\AuthSourcesException;
10
use kalanis\kw_auth_sources\Interfaces;
11
use kalanis\kw_auth_sources\Traits;
12
use kalanis\kw_locks\Interfaces\ILock;
13
use kalanis\kw_locks\LockException;
14
15
16
/**
17
 * Class AccountsSingleFile
18
 * @package kalanis\kw_auth_sources\Sources\Files
19
 * Authenticate via single file
20
 */
21
class AccountsSingleFile implements acc_interfaces\IAuth, acc_interfaces\IProcessAccounts
22
{
23
    use Traits\TAuthLock;
24
    use Traits\TLines;
25
    use Traits\TStatusTransform;
26
27
    protected const PW_ID = 0;
28
    protected const PW_NAME = 1;
29
    protected const PW_PASS = 2;
30
    protected const PW_GROUP = 3;
31
    protected const PW_CLASS = 4;
32
    protected const PW_STATUS = 5;
33
    protected const PW_DISPLAY = 6;
34
    protected const PW_DIR = 7;
35
    protected const PW_EXTRA = 8;
36
    protected const PW_FEED = 9;
37
38
    protected Storages\AStorage $storage;
39
    protected Interfaces\IHashes $mode;
40
    protected Interfaces\IStatus $status;
41
    protected Interfaces\IExtraParser $extraParser;
42
    /** @var string[] */
43
    protected array $path = [];
44
45
    /**
46
     * @param Storages\AStorage $storage where is it stored and how to access there
47
     * @param Interfaces\IHashes $mode hashing mode
48
     * @param Interfaces\IStatus $status which status is necessary to use that feature
49
     * @param Interfaces\IExtraParser $parser parsing extra arguments from and to string
50
     * @param ILock $lock file lock
51
     * @param string[] $path use full path with file name
52
     * @param Interfaces\IKAusTranslations|null $lang
53
     */
54 27
    public function __construct(
55
        Storages\AStorage $storage,
56
        Interfaces\IHashes $mode,
57
        Interfaces\IStatus $status,
58
        Interfaces\IExtraParser $parser,
59
        ILock $lock,
60
        array $path,
61
        ?Interfaces\IKAusTranslations $lang = null
62
    )
63
    {
64 27
        $this->setAusLang($lang);
65 27
        $this->initAuthLock($lock);
66 27
        $this->storage = $storage;
67 27
        $this->mode = $mode;
68 27
        $this->status = $status;
69 27
        $this->path = $path;
70 27
        $this->extraParser = $parser;
71 27
    }
72
73 5
    public function authenticate(string $userName, array $params = []): ?acc_interfaces\IUser
74
    {
75 5
        if (empty($params['password'])) {
76 1
            throw new AccountsException($this->getAusLang()->kauPassMustBeSet());
77
        }
78 4
        $name = $this->stripChars($userName);
79 4
        $pass = strval($params['password']);
80
81
        try {
82 4
            $this->checkLock();
83
            try {
84 3
                $passLines = $this->openPassword();
85 1
            } catch (AuthSourcesException $ex) {
86
                // silence the problems on storage
87 1
                return null;
88
            }
89 2
            foreach ($passLines as &$line) {
90 2
                if ($line[static::PW_NAME] == $name) {
91
                    if (
92 2
                        $this->mode->checkHash($pass, strval($line[static::PW_PASS]))
93 2
                        && $this->status->allowLogin($this->transformFromStringToInt(strval($line[static::PW_STATUS])))
94
                    ) {
95 2
                        return $this->getUserClass($line);
96
                    }
97
                }
98
            }
99 2
            return null;
100
101 1
        } catch (AuthSourcesException | LockException $ex) {
102 1
            throw new AccountsException($ex->getMessage(), $ex->getCode(), $ex);
103
        }
104
    }
105
106 6
    public function getDataOnly(string $userName): ?acc_interfaces\IUser
107
    {
108 6
        $name = $this->stripChars($userName);
109
110
        // load from password
111
        try {
112 6
            $this->checkLock();
113
            try {
114 5
                $passwordLines = $this->openPassword();
115 2
            } catch (AuthSourcesException $ex) {
116
                // silence the problems on storage
117 2
                return null;
118
            }
119 3
            foreach ($passwordLines as &$line) {
120 3
                if ($line[static::PW_NAME] == $name) {
121 3
                    return $this->getUserClass($line);
122
                }
123
            }
124 2
            return null;
125
126 1
        } catch (AuthSourcesException | LockException $ex) {
127 1
            throw new AccountsException($ex->getMessage(), $ex->getCode(), $ex);
128
        }
129
    }
130
131
    /**
132
     * @param array<int, string|int|float> $line
133
     * @throws AuthSourcesException
134
     * @return acc_interfaces\IUser
135
     */
136 5
    protected function getUserClass(array &$line): acc_interfaces\IUser
137
    {
138 5
        $user = new FileUser();
139 5
        $user->setUserData(
140 5
            strval($line[static::PW_ID]),
141 5
            strval($line[static::PW_NAME]),
142 5
            strval($line[static::PW_GROUP]),
143 5
            intval($line[static::PW_CLASS]),
144 5
            $this->transformFromStringToInt(strval($line[static::PW_STATUS])),
145 5
            strval($line[static::PW_DISPLAY]),
146 5
            strval($line[static::PW_DIR]),
147 5
            $this->extraParser->expand(strval($line[static::PW_EXTRA]))
148
        );
149 5
        return $user;
150
    }
151
152 4
    public function createAccount(acc_interfaces\IUser $user, string $password): bool
153
    {
154 4
        $userName = $this->stripChars($user->getAuthName());
155 4
        $directory = $this->stripChars($user->getDir());
156 4
        $displayName = $this->stripChars($user->getDisplayName());
157
158
        // not everything necessary is set
159 4
        if (empty($userName) || empty($directory) || empty($password)) {
160 1
            throw new AccountsException($this->getAusLang()->kauPassMissParam());
161
        }
162
163
        try {
164 3
            $this->checkLock();
165
166 2
            $uid = acc_interfaces\IUser::LOWEST_USER_ID;
167 2
            $this->getLock()->create();
168
169
            // read password
170
            try {
171 2
                $passLines = $this->openPassword();
172 1
            } catch (AuthSourcesException $ex) {
173
                // silence the problems on storage
174 1
                $passLines = [];
175
            }
176 2
            foreach ($passLines as &$line) {
177 1
                $uid = max($uid, intval($line[static::PW_ID]));
178
            }
179 2
            $uid++;
180
181
            $newUserPass = [
182 2
                static::PW_ID => strval($uid),
183 2
                static::PW_NAME => $userName,
184 2
                static::PW_PASS => $this->mode->createHash($password),
185 2
                static::PW_GROUP => empty($user->getGroup()) ? $uid : $user->getGroup() ,
186 2
                static::PW_CLASS => empty($user->getClass()) ? acc_interfaces\IProcessClasses::CLASS_USER : strval($user->getClass()) ,
187 2
                static::PW_STATUS => $this->transformFromIntToString($user->getStatus()),
188 2
                static::PW_DISPLAY => empty($displayName) ? $userName : $displayName,
189 2
                static::PW_DIR => $directory,
190 2
                static::PW_EXTRA => $this->extraParser->compact($user->getExtra()),
191 2
                static::PW_FEED => '',
192
            ];
193 2
            ksort($newUserPass);
194 2
            $passLines[] = $newUserPass;
195
196
            // now save it
197
            try {
198 2
                $result = $this->savePassword($passLines);
199 2
            } finally {
200 2
                $this->getLock()->delete();
201
            }
202 2
            return $result;
203
204 1
        } catch (AuthSourcesException | LockException $ex) {
205 1
            throw new AccountsException($ex->getMessage(), $ex->getCode(), $ex);
206
        }
207
    }
208
209 2
    public function readAccounts(): array
210
    {
211
        try {
212 2
            $this->checkLock();
213
214 1
            $passLines = $this->openPassword();
215 1
            $result = [];
216 1
            foreach ($passLines as &$line) {
217 1
                $result[] = $this->getUserClass($line);
218
            }
219
220 1
            return $result;
221
222 1
        } catch (AuthSourcesException | LockException $ex) {
223 1
            throw new AccountsException($ex->getMessage(), $ex->getCode(), $ex);
224
        }
225
    }
226
227 3
    public function updateAccount(acc_interfaces\IUser $user): bool
228
    {
229 3
        $userName = $this->stripChars($user->getAuthName());
230 3
        $directory = $this->stripChars($user->getDir());
231 3
        $displayName = $this->stripChars($user->getDisplayName());
232
233
        try {
234 3
            $this->checkLock();
235
236 2
            $this->getLock()->create();
237
            try {
238 2
                $passwordLines = $this->openPassword();
239 1
            } finally {
240 2
                $this->getLock()->delete();
241
            }
242 1
            foreach ($passwordLines as &$line) {
243 1
                if ($line[static::PW_NAME] == $userName) {
244
                    // REFILL
245 1
                    $line[static::PW_GROUP] = !empty($user->getGroup()) ? $user->getGroup() : $line[static::PW_GROUP] ;
246 1
                    $line[static::PW_CLASS] = !empty($user->getClass()) ? strval($user->getClass()) : $line[static::PW_CLASS] ;
247 1
                    $line[static::PW_STATUS] = $this->transformFromIntToString($user->getStatus());
248 1
                    $line[static::PW_DISPLAY] = !empty($displayName) ? $displayName : $line[static::PW_DISPLAY] ;
249 1
                    $line[static::PW_DIR] = !empty($directory) ? $directory : $line[static::PW_DIR] ;
250 1
                    $line[static::PW_EXTRA] = !empty($user->getExtra()) ? $this->extraParser->compact($user->getExtra()) : $line[static::PW_EXTRA] ;
251
                }
252
            }
253
254
            try {
255 1
                $result = $this->savePassword($passwordLines);
256 1
            } finally {
257 1
                $this->getLock()->delete();
258
            }
259 1
            return $result;
260
261 2
        } catch (AuthSourcesException | LockException $ex) {
262 2
            throw new AccountsException($ex->getMessage(), $ex->getCode(), $ex);
263
        }
264
    }
265
266 3
    public function updatePassword(string $userName, string $passWord): bool
267
    {
268 3
        $name = $this->stripChars($userName);
269
        // load from shadow
270
        try {
271 3
            $this->checkLock();
272
273 2
            $changed = false;
274 2
            $this->getLock()->create();
275
276
            try {
277 2
                $lines = $this->openPassword();
278 1
            } finally {
279 2
                $this->getLock()->delete();
280
            }
281 1
            foreach ($lines as &$line) {
282 1
                if ($line[static::PW_NAME] == $name) {
283 1
                    $changed = true;
284 1
                    $line[static::PW_PASS] = $this->mode->createHash($passWord);
285
                }
286
            }
287
            try {
288 1
                if ($changed) {
289 1
                    $this->savePassword($lines);
290
                }
291 1
            } finally {
292 1
                $this->getLock()->delete();
293
            }
294 1
            return true;
295
296 2
        } catch (AuthSourcesException | LockException $ex) {
297 2
            throw new AccountsException($ex->getMessage(), $ex->getCode(), $ex);
298
        }
299
    }
300
301 3
    public function deleteAccount(string $userName): bool
302
    {
303 3
        $name = $this->stripChars($userName);
304
305
        try {
306 3
            $this->checkLock();
307
308 2
            $changed = false;
309 2
            $this->getLock()->create();
310
311
            // update password
312
            try {
313 2
                $passLines = $this->openPassword();
314 1
            } catch (AuthSourcesException $ex) {
315
                // removal on non-existent file is not possible and not necessary
316 1
                $this->getLock()->delete();
317 1
                return true;
318
            }
319
320 1
            foreach ($passLines as $index => &$line) {
321 1
                if ($line[static::PW_NAME] == $name) {
322 1
                    unset($passLines[$index]);
323 1
                    $changed = true;
324
                }
325
            }
326
327
            // now save it all
328
            try {
329 1
                if ($changed) {
330 1
                    $this->savePassword($passLines);
331
                }
332 1
            } finally {
333 1
                $this->getLock()->delete();
334
            }
335 1
            return true;
336
337 1
        } catch (AuthSourcesException | LockException $ex) {
338 1
            throw new AccountsException($ex->getMessage(), $ex->getCode(), $ex);
339
        }
340
    }
341
342
    /**
343
     * @throws AuthSourcesException
344
     * @return array<int, array<int, string|int>>
345
     */
346 9
    protected function openPassword(): array
347
    {
348 9
        return $this->storage->read($this->path);
349
    }
350
351
    /**
352
     * @param array<int, array<int, string|int>> $lines
353
     * @throws AuthSourcesException
354
     * @return bool
355
     */
356 2
    protected function savePassword(array $lines): bool
357
    {
358 2
        return $this->storage->write($this->path, $lines);
359
    }
360
361
    /**
362
     * @return string
363
     * @codeCoverageIgnore translation
364
     */
365
    protected function noDirectoryDelimiterSet(): string
366
    {
367
        return $this->getAusLang()->kauNoDelimiterSet();
368
    }
369
}
370