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

AFile::authenticate()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 26
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
eloc 16
c 0
b 0
f 0
nc 6
nop 2
dl 0
loc 26
ccs 15
cts 15
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\FileUser;
8
use kalanis\kw_auth\Interfaces;
9
use kalanis\kw_auth\Sources\TAuthLock;
10
use kalanis\kw_auth\Sources\TStatusTransform;
11
use kalanis\kw_locks\Interfaces\ILock;
12
use kalanis\kw_locks\LockException;
13
14
15
/**
16
 * Class AFile
17
 * @package kalanis\kw_auth\Sources\Files
18
 * Authenticate via single file
19
 */
20
abstract class AFile implements Interfaces\IAuth, Interfaces\IAccessAccounts
21
{
22
    use TAuthLock;
23
    use TLines;
24
    use TStatusTransform;
25
    use TStore;
26
27
    const PW_ID = 0;
28
    const PW_NAME = 1;
29
    const PW_PASS = 2;
30
    const PW_GROUP = 3;
31
    const PW_CLASS = 4;
32
    const PW_STATUS = 5;
33
    const PW_DISPLAY = 6;
34
    const PW_DIR = 7;
35
    const PW_FEED = 8;
36
37
    /** @var Interfaces\IMode */
38
    protected $mode = null;
39
    /** @var Interfaces\IStatus */
40
    protected $status = null;
41
    /** @var string */
42
    protected $path = '';
43
44
    /**
45
     * @param Interfaces\IMode $mode hashing mode
46
     * @param Interfaces\IStatus $status which status is necessary to use that feature
47
     * @param ILock $lock file lock
48
     * @param string $path use full path with file name
49
     * @param Interfaces\IKATranslations|null $lang
50
     */
51 24
    public function __construct(Interfaces\IMode $mode, Interfaces\IStatus $status, ILock $lock, string $path, ?Interfaces\IKATranslations $lang = null)
52
    {
53 24
        $this->setLang($lang);
54 24
        $this->mode = $mode;
55 24
        $this->status = $status;
56 24
        $this->path = $path;
57 24
        $this->initAuthLock($lock);
58 24
    }
59
60 7
    public function authenticate(string $userName, array $params = []): ?Interfaces\IUser
61
    {
62 7
        if (empty($params['password'])) {
63 2
            throw new AuthException($this->getLang()->kauPassMustBeSet());
64
        }
65 5
        $name = $this->stripChars($userName);
66 5
        $pass = strval($params['password']);
67
68 5
        $this->checkLock();
69
        try {
70 5
            $passLines = $this->openFile($this->path);
71 1
        } catch (AuthException $ex) {
72
            // silence the problems on storage
73 1
            return null;
74
        }
75 4
        foreach ($passLines as &$line) {
76 4
            if ($line[static::PW_NAME] == $name) {
77
                if (
78 4
                    $this->mode->check($pass, strval($line[static::PW_PASS]))
79 4
                    && $this->status->allowLogin($this->transformFromStringToInt(strval($line[static::PW_STATUS])))
80
                ) {
81 4
                    return $this->getUserClass($line);
82
                }
83
            }
84
        }
85 4
        return null;
86
    }
87
88 7
    public function getDataOnly(string $userName): ?Interfaces\IUser
89
    {
90 7
        $name = $this->stripChars($userName);
91
92
        // load from password
93 7
        $this->checkLock();
94
        try {
95 7
            $passwordLines = $this->openFile($this->path);
96 2
        } catch (AuthException $ex) {
97
            // silence the problems on storage
98 2
            return null;
99
        }
100 5
        foreach ($passwordLines as &$line) {
101 5
            if ($line[static::PW_NAME] == $name) {
102 5
                return $this->getUserClass($line);
103
            }
104
        }
105 4
        return null;
106
    }
107
108
    /**
109
     * @param array<int, string|int|float> $line
110
     * @return Interfaces\IUser
111
     */
112 9
    protected function getUserClass(array &$line): Interfaces\IUser
113
    {
114 9
        $user = new FileUser();
115 9
        $user->setData(
116 9
            intval($line[static::PW_ID]),
117 9
            strval($line[static::PW_NAME]),
118 9
            intval($line[static::PW_GROUP]),
119 9
            intval($line[static::PW_CLASS]),
120 9
            $this->transformFromStringToInt(strval($line[static::PW_STATUS])),
121 9
            strval($line[static::PW_DISPLAY]),
122 9
            strval($line[static::PW_DIR])
123
        );
124 9
        return $user;
125
    }
126
127 7
    public function createAccount(Interfaces\IUser $user, string $password): void
128
    {
129 7
        $userName = $this->stripChars($user->getAuthName());
130 7
        $directory = $this->stripChars($user->getDir());
131 7
        $displayName = $this->stripChars($user->getDisplayName());
132
133
        // not everything necessary is set
134 7
        if (empty($userName) || empty($directory) || empty($password)) {
135 2
            throw new AuthException($this->getLang()->kauPassMissParam());
136
        }
137 5
        $this->checkLock();
138
139 5
        $uid = Interfaces\IUser::LOWEST_USER_ID;
140 5
        $this->getLock()->create();
141
142
        // read password
143
        try {
144 5
            $passLines = $this->openFile($this->path);
145 2
        } catch (AuthException $ex) {
146
            // silence the problems on storage
147 2
            $passLines = [];
148
        }
149 5
        foreach ($passLines as &$line) {
150 2
            $uid = max($uid, intval($line[static::PW_ID]));
151
        }
152 5
        $uid++;
153
154
        $newUserPass = [
155 5
            static::PW_ID => $uid,
156 5
            static::PW_NAME => $userName,
157 5
            static::PW_PASS => $this->mode->hash($password),
158 5
            static::PW_GROUP => empty($user->getGroup()) ? $uid : $user->getClass() ,
159 5
            static::PW_CLASS => empty($user->getClass()) ? Interfaces\IAccessClasses::CLASS_USER : $user->getClass() ,
160 5
            static::PW_STATUS => $this->transformFromIntToString($user->getStatus()),
161 5
            static::PW_DISPLAY => empty($displayName) ? $userName : $displayName,
162 5
            static::PW_DIR => $directory,
163 5
            static::PW_FEED => '',
164
        ];
165 5
        ksort($newUserPass);
166 5
        $passLines[] = $newUserPass;
167
168
        // now save it
169
        try {
170 5
            $this->saveFile($this->path, $passLines);
171 3
        } finally {
172 5
            $this->getLock()->delete();
173
        }
174 3
    }
175
176
    /**
177
     * @throws AuthException
178
     * @throws LockException
179
     * @return Interfaces\IUser[]
180
     */
181 3
    public function readAccounts(): array
182
    {
183 3
        $this->checkLock();
184
185 3
        $passLines = $this->openFile($this->path);
186 2
        $result = [];
187 2
        foreach ($passLines as &$line) {
188 2
            $result[] = $this->getUserClass($line);
189
        }
190
191 2
        return $result;
192
    }
193
194 5
    public function updateAccount(Interfaces\IUser $user): bool
195
    {
196 5
        $userName = $this->stripChars($user->getAuthName());
197 5
        $directory = $this->stripChars($user->getDir());
198 5
        $displayName = $this->stripChars($user->getDisplayName());
199
200 5
        $this->checkLock();
201
202 5
        $this->getLock()->create();
203
        try {
204 5
            $passwordLines = $this->openFile($this->path);
205 3
        } finally {
206 5
            $this->getLock()->delete();
207
        }
208 3
        foreach ($passwordLines as &$line) {
209 2
            if ($line[static::PW_NAME] == $userName) {
210
                // REFILL
211 2
                $line[static::PW_GROUP] = !empty($user->getGroup()) ? $user->getGroup() : $line[static::PW_GROUP] ;
212 2
                $line[static::PW_CLASS] = !empty($user->getClass()) ? $user->getClass() : $line[static::PW_CLASS] ;
213 2
                $line[static::PW_STATUS] = $this->transformFromIntToString($user->getStatus());
214 2
                $line[static::PW_DISPLAY] = !empty($displayName) ? $displayName : $line[static::PW_DISPLAY] ;
215 2
                $line[static::PW_DIR] = !empty($directory) ? $directory : $line[static::PW_DIR] ;
216
            }
217
        }
218
219
        try {
220 3
            $this->saveFile($this->path, $passwordLines);
221 2
        } finally {
222 3
            $this->getLock()->delete();
223
        }
224 2
        return true;
225
    }
226
227 3
    public function updatePassword(string $userName, string $passWord): bool
228
    {
229 3
        $name = $this->stripChars($userName);
230
        // load from shadow
231 3
        $this->checkLock();
232
233 3
        $changed = false;
234 3
        $this->getLock()->create();
235
236
        try {
237 3
            $lines = $this->openFile($this->path);
238 2
        } finally {
239 3
            $this->getLock()->delete();
240
        }
241 2
        foreach ($lines as &$line) {
242 2
            if ($line[static::PW_NAME] == $name) {
243 2
                $changed = true;
244 2
                $line[static::PW_PASS] = $this->mode->hash($passWord);
245
            }
246
        }
247
        try {
248 2
            if ($changed) {
249 2
                $this->saveFile($this->path, $lines);
250
            }
251 2
        } finally {
252 2
            $this->getLock()->delete();
253
        }
254 2
        return true;
255
    }
256
257 5
    public function deleteAccount(string $userName): bool
258
    {
259 5
        $name = $this->stripChars($userName);
260 5
        $this->checkLock();
261
262 5
        $changed = false;
263 5
        $this->getLock()->create();
264
265
        // update password
266
        try {
267 5
            $passLines = $this->openFile($this->path);
268 2
        } catch (AuthException $ex) {
269
            // removal on non-existent file is not possible and not necessary
270 2
            $this->getLock()->delete();
271 2
            return true;
272
        }
273
274 3
        foreach ($passLines as $index => &$line) {
275 3
            if ($line[static::PW_NAME] == $name) {
276 3
                unset($passLines[$index]);
277 3
                $changed = true;
278
            }
279
        }
280
281
        // now save it all
282
        try {
283 3
            if ($changed) {
284 3
                $this->saveFile($this->path, $passLines);
285
            }
286 2
        } finally {
287 3
            $this->getLock()->delete();
288
        }
289 2
        return true;
290
    }
291
}
292