Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.49% covered (warning)
86.49%
160 / 185
76.67% covered (warning)
76.67%
23 / 30
CRAP
0.00% covered (danger)
0.00%
0 / 1
Useraccount
86.49% covered (warning)
86.49%
160 / 185
76.67% covered (warning)
76.67%
23 / 30
111.44
0.00% covered (danger)
0.00%
0 / 1
 getDefaults
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
1 / 1
1
 hasProperties
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 getDepartmentList
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 addDepartment
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getDepartment
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 hasDepartment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasScope
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setPermissions
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 hasPermissions
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 hasAnyPermission
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 hasExclusivePermission
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
10
 getRoles
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 hasRole
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 testPermissions
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 testAnyPermission
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 isOveraged
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 isSuperUser
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDepartmentById
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 getDepartmentByIds
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 testDepartmentById
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 setPassword
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
6
 withDepartmentList
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 withCleanedUpFormData
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
8.06
 setVerifiedHash
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 withVerifiedHash
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 isPasswordNeedingRehash
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getHash
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 withLessData
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 createFromOpenidData
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 getOidcProviderFromName
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace BO\Zmsentities;
4
5use BO\Zmsentities\Helper\Property;
6
7/**
8 * @SuppressWarnings(Complexity)
9 * @SuppressWarnings(PublicMethod)
10 *
11 */
12class Useraccount extends Schema\Entity
13{
14    public const PRIMARY = 'id';
15
16    public static $schema = "useraccount.json";
17
18    #[\Override]
19    public function getDefaults()
20    {
21        return [
22            'permissions' => [
23                "appointment" => false,
24                "availability" => false,
25                "calldisplay" => false,
26                "capacityreport" => false,
27                "cherrypick" => false,
28                "cluster" => false,
29                "config" => false,
30                "counter" => false,
31                "customersearch" => false,
32                "dayoff" => false,
33                "department" => false,
34                "emergency" => false,
35                "finishedqueue" => false,
36                "finishedqueuepast" => false,
37                "jurisdiction" => false,
38                "logs" => false,
39                "mailtemplates" => false,
40                "missedqueue" => false,
41                "openqueue" => false,
42                "organisation" => false,
43                "overviewcalendar" => false,
44                "parkedqueue" => false,
45                "restrictedscope" => false,
46                "scope" => false,
47                "source" => false,
48                "statistic" => false,
49                "ticketprinter" => false,
50                "useraccount" => false,
51                "waitingqueue" => false,
52                "superuser" => false
53            ],
54            'departments' => new Collection\DepartmentList(),
55        ];
56    }
57
58    public function hasProperties()
59    {
60        foreach (func_get_args() as $property) {
61            if (!$this->toProperty()->$property->get()) {
62                throw new Exception\UserAccountMissingProperties("Missing property " . htmlspecialchars($property));
63                return false;
64            }
65        }
66        return true;
67    }
68
69    public function getDepartmentList()
70    {
71        if (!$this->departments instanceof Collection\DepartmentList) {
72            $this->departments = new Collection\DepartmentList($this->departments);
73            foreach ($this->departments as $key => $department) {
74                $this->departments[$key] = new Department($department);
75            }
76        }
77        return $this->departments;
78    }
79
80    public function addDepartment($department)
81    {
82        $this->departments[] = $department;
83        return $this;
84    }
85
86    public function getDepartment($departmentId)
87    {
88        if (count($this->departments)) {
89            foreach ($this->getDepartmentList() as $department) {
90                if ($department['id'] == $departmentId) {
91                    return $department;
92                }
93            }
94        }
95        return new Department(['name' => 'Not existing']);
96    }
97
98    public function hasDepartment($departmentId)
99    {
100        return $this->getDepartment($departmentId)->hasId();
101    }
102
103    public function hasScope($scopeId)
104    {
105        return $this->getDepartmentList()->getUniqueScopeList()->hasEntity($scopeId);
106    }
107
108
109    public function setPermissions()
110    {
111        $givenPermissions = func_get_args();
112        foreach ($givenPermissions as $permission) {
113            if (Property::__keyExists($permission, $this->permissions)) {
114                $this->permissions[$permission] = true;
115            }
116        }
117        return $this;
118    }
119
120    /**
121     * Returns true when the user has all of the given permissions.
122     */
123    public function hasPermissions(array $requiredPermissions): bool
124    {
125        if ($this->isSuperUser()) {
126            return true;
127        }
128
129        $permissions = $this->toProperty()->permissions ?? null;
130
131        foreach ($requiredPermissions as $required) {
132            if ($required instanceof Useraccount\RightsInterface) {
133                if (! $required->validateUseraccount($this)) {
134                    return false;
135                }
136                continue;
137            }
138
139            if (! ($permissions?->$required?->get() ?? false)) {
140                return false;
141            }
142        }
143
144        return true;
145    }
146
147    /**
148     * Returns true when the user has any of the given permissions.
149     */
150    public function hasAnyPermission(array $requiredPermissions): bool
151    {
152        if ($this->isSuperUser()) {
153            return true;
154        }
155
156        $permissions = $this->toProperty()->permissions ?? null;
157
158        foreach ($requiredPermissions as $required) {
159            if ($required instanceof Useraccount\RightsInterface) {
160                if ($required->validateUseraccount($this)) {
161                    return true;
162                }
163                continue;
164            }
165
166            if ($permissions?->$required?->get() ?? false) {
167                return true;
168            }
169        }
170
171        return false;
172    }
173
174    /**
175     * Returns true when the user has only the given permission and no other permission.
176     */
177    public function hasExclusivePermission(string $permission): bool
178    {
179        if ($this->isSuperUser()) {
180            return false;
181        }
182
183        $permissions = $this['permissions'] ?? [];
184        $requiredPermission = $permissions[$permission] ?? false;
185        if (!is_array($permissions) || !$requiredPermission || '0' === $requiredPermission) {
186            return false;
187        }
188
189        foreach ($permissions as $name => $enabled) {
190            if ($permission === $name || 'superuser' === $name) {
191                continue;
192            }
193            if ($enabled && '0' !== $enabled) {
194                return false;
195            }
196        }
197
198        return true;
199    }
200    public function getRoles(): array
201    {
202        $roles = $this->roles ?? [];
203        if (is_array($roles)) {
204            return $roles;
205        }
206        if (is_string($roles)) {
207            return array_values(array_filter(array_map('trim', explode(',', $roles)), function ($role) {
208                return $role !== '';
209            }));
210        }
211        return [];
212    }
213
214    public function hasRole(string $role): bool
215    {
216        return in_array($role, $this->getRoles(), true);
217    }
218
219    public function testPermissions(array $requiredPermissions)
220    {
221        if (! $this->hasId()) {
222            throw new Exception\UserAccountMissingLogin();
223        }
224
225        if (! $this->hasPermissions($requiredPermissions)) {
226            throw new Exception\UserAccountMissingRights(
227                "Missing permissions " . htmlspecialchars(implode(',', $requiredPermissions))
228            );
229        }
230
231        return $this;
232    }
233
234    public function testAnyPermission(array $requiredPermissions)
235    {
236        if (! $this->hasId()) {
237            throw new Exception\UserAccountMissingLogin();
238        }
239
240        if (! $this->hasAnyPermission($requiredPermissions)) {
241            throw new Exception\UserAccountMissingRights(
242                "Missing any of permissions " . htmlspecialchars(implode(',', $requiredPermissions))
243            );
244        }
245
246        return $this;
247    }
248
249    public function isOveraged(\DateTimeInterface $dateTime)
250    {
251        if (Property::__keyExists('lastLogin', $this)) {
252            $lastLogin = (new \DateTimeImmutable())->setTimestamp($this['lastLogin'])->modify('23:59:59');
253            return ($lastLogin < $dateTime);
254        }
255        return false;
256    }
257
258    public function isSuperUser(): bool
259    {
260        return $this->toProperty()->permissions?->superuser?->get() ?? false;
261    }
262
263    public function getDepartmentById($departmentId)
264    {
265        foreach ($this->departments as $department) {
266            if ($departmentId == $department['id']) {
267                return new Department($department);
268            }
269        }
270        return new Department();
271    }
272
273    public function getDepartmentByIds(array $departmentIds)
274    {
275        foreach ($this->departments as $department) {
276            if (in_array($department['id'], $departmentIds)) {
277                return new Department($department);
278            }
279        }
280        return new Department();
281    }
282
283    public function testDepartmentById($departmentId)
284    {
285        $department = $this->getDepartmentById($departmentId);
286        if (!$department->hasId()) {
287            throw new Exception\UserAccountMissingDepartment(
288                "Missing department " . htmlspecialchars($departmentId)
289            );
290        }
291        return $department;
292    }
293
294    public function setPassword($input)
295    {
296        if (isset($input['password']) && '' != $input['password']) {
297            $this->password = $input['password'];
298        }
299        if (isset($input['changePassword']) && 0 < count(array_filter($input['changePassword']))) {
300            if (! isset($input['password'])) {
301                $this->password = $input['changePassword'][0];
302            }
303            $this->changePassword = $input['changePassword'];
304        }
305        return $this;
306    }
307
308    public function withDepartmentList()
309    {
310        $departmentList = new Collection\DepartmentList();
311        $entity = clone $this;
312        foreach ($this->departments as $department) {
313            if (! is_array($department) && ! $department instanceof Department) {
314                $department = new Department(array('id' => $department));
315            }
316            $departmentList->addEntity($department);
317        }
318        $entity->departments = $departmentList;
319        return $entity;
320    }
321
322    #[\Override]
323    public function withCleanedUpFormData($keepPassword = false)
324    {
325        unset($this['save']);
326        if (isset($this['password']) && '' == $this['password'] && false === $keepPassword) {
327            unset($this['password']);
328        }
329        if (
330            isset($this['changePassword']) &&
331            0 == count(array_filter($this['changePassword'])) &&
332            false === $keepPassword
333        ) {
334            unset($this['changePassword']);
335        }
336        if (isset($this['oidcProvider'])) {
337            unset($this['oidcProvider']);
338        }
339
340        return $this;
341    }
342
343    /**
344     * verify hashed password and create new if needs rehash
345     *
346     * @return array $useraccount
347    */
348    public function setVerifiedHash($password)
349    {
350        // Do you have old, turbo-legacy, non-crypt hashes?
351        if (strpos($this->password, '$') !== 0) {
352            $result = $this->password === md5($password);
353        } else {
354            $result = password_verify($password, $this->password);
355        }
356
357        // on passed validation check if the hash needs updating.
358        if ($result && $this->isPasswordNeedingRehash()) {
359            $this->password = $this->getHash($password);
360        }
361
362        return $this;
363    }
364
365    public function withVerifiedHash($password)
366    {
367        $useraccount = clone $this;
368        if ($useraccount->isPasswordNeedingRehash()) {
369            $useraccount->setVerifiedHash($password);
370        }
371        return $useraccount;
372    }
373
374    public function isPasswordNeedingRehash()
375    {
376        return password_needs_rehash($this->password, PASSWORD_DEFAULT);
377    }
378
379    /**
380     * set salted hash by string
381     *
382     * @return string $hash
383    */
384    public function getHash($string)
385    {
386        $hash = password_hash($string, PASSWORD_DEFAULT);
387        return $hash;
388    }
389
390    #[\Override]
391    public function withLessData()
392    {
393        unset($this->departments);
394
395        return $this;
396    }
397
398    /**
399     * create useraccount from open id input data with random password
400     *
401     * @return string $entity
402    */
403    public function createFromOpenidData($data)
404    {
405        $entity = new self();
406        $entity->id = $data['username'];
407        $department = new Department(['id' => 0]);
408        $entity->addDepartment($department);
409        $password = substr(str_shuffle($entity->id . uniqid()), 0, 8);
410        $entity->password = $this->getHash($password);
411        return $entity;
412    }
413
414    /**
415     * get oidc provider from $entity id if it exists
416     *
417     * @return string $entity
418    */
419    public function getOidcProviderFromName()
420    {
421        $providerName = '';
422        if (($pos = strpos($this->id, "@")) !== false) {
423            $providerName = substr($this->id, $pos + 1);
424        }
425        return ('' !== $providerName) ? $providerName : null;
426    }
427}