Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
87.20% covered (warning)
87.20%
109 / 125
80.77% covered (warning)
80.77%
21 / 26
CRAP
0.00% covered (danger)
0.00%
0 / 1
Useraccount
87.20% covered (warning)
87.20%
109 / 125
80.77% covered (warning)
80.77%
21 / 26
78.98
0.00% covered (danger)
0.00%
0 / 1
 getDefaults
100.00% covered (success)
100.00%
15 / 15
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
 getRightsLevel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setRights
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 hasRights
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
6
 testRights
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    public function getDefaults()
19    {
20        return [
21            'rights' => [
22                "availability" => false,
23                "basic" => true,
24                "cluster" => false,
25                "department" => false,
26                "organisation" => false,
27                "scope" => false,
28                "sms" => false,
29                "superuser" => false,
30                "ticketprinter" => false,
31                "useraccount" => false,
32            ],
33            'departments' => new Collection\DepartmentList(),
34        ];
35    }
36
37    public function hasProperties()
38    {
39        foreach (func_get_args() as $property) {
40            if (!$this->toProperty()->$property->get()) {
41                throw new Exception\UserAccountMissingProperties("Missing property " . htmlspecialchars($property));
42                return false;
43            }
44        }
45        return true;
46    }
47
48    public function getDepartmentList()
49    {
50        if (!$this->departments instanceof Collection\DepartmentList) {
51            $this->departments = new Collection\DepartmentList($this->departments);
52            foreach ($this->departments as $key => $department) {
53                $this->departments[$key] = new Department($department);
54            }
55        }
56        return $this->departments;
57    }
58
59    public function addDepartment($department)
60    {
61        $this->departments[] = $department;
62        return $this;
63    }
64
65    public function getDepartment($departmentId)
66    {
67        if (count($this->departments)) {
68            foreach ($this->getDepartmentList() as $department) {
69                if ($department['id'] == $departmentId) {
70                    return $department;
71                }
72            }
73        }
74        return new Department(['name' => 'Not existing']);
75    }
76
77    public function hasDepartment($departmentId)
78    {
79        return $this->getDepartment($departmentId)->hasId();
80    }
81
82    public function hasScope($scopeId)
83    {
84        return $this->getDepartmentList()->getUniqueScopeList()->hasEntity($scopeId);
85    }
86
87    /**
88     * @todo Remove this function, keep no contraint on old DB schema in zmsentities
89     */
90    public function getRightsLevel()
91    {
92        return Helper\RightsLevelManager::getLevel($this->rights);
93    }
94
95    public function setRights()
96    {
97        $givenRights = func_get_args();
98        foreach ($givenRights as $right) {
99            if (Property::__keyExists($right, $this->rights)) {
100                $this->rights[$right] = true;
101            }
102        }
103        return $this;
104    }
105
106    public function hasRights(array $requiredRights)
107    {
108        if ($this->isSuperUser()) {
109            return true;
110        }
111        foreach ($requiredRights as $required) {
112            if ($required instanceof Useraccount\RightsInterface) {
113                if (!$required->validateUseraccount($this)) {
114                    return false;
115                }
116            } elseif (! $this->toProperty()->rights->$required->get()) {
117                return false;
118            }
119        }
120        return true;
121    }
122
123    public function testRights(array $requiredRights)
124    {
125        if ($this->hasId()) {
126            if (!$this->hasRights($requiredRights)) {
127                throw new Exception\UserAccountMissingRights(
128                    "Missing rights " . htmlspecialchars(implode(',', $requiredRights))
129                );
130            }
131        } else {
132            throw new Exception\UserAccountMissingLogin();
133        }
134        return $this;
135    }
136
137    public function isOveraged(\DateTimeInterface $dateTime)
138    {
139        if (Property::__keyExists('lastLogin', $this)) {
140            $lastLogin = (new \DateTimeImmutable())->setTimestamp($this['lastLogin'])->modify('23:59:59');
141            return ($lastLogin < $dateTime);
142        }
143        return false;
144    }
145
146    public function isSuperUser()
147    {
148        return $this->toProperty()->rights->superuser->get();
149    }
150
151    public function getDepartmentById($departmentId)
152    {
153        foreach ($this->departments as $department) {
154            if ($departmentId == $department['id']) {
155                return new Department($department);
156            }
157        }
158        return new Department();
159    }
160
161    public function getDepartmentByIds(array $departmentIds)
162    {
163        foreach ($this->departments as $department) {
164            if (in_array($department['id'], $departmentIds)) {
165                return new Department($department);
166            }
167        }
168        return new Department();
169    }
170
171    public function testDepartmentById($departmentId)
172    {
173        $department = $this->getDepartmentById($departmentId);
174        if (!$department->hasId()) {
175            throw new Exception\UserAccountMissingDepartment(
176                "Missing department " . htmlspecialchars($departmentId)
177            );
178        }
179        return $department;
180    }
181
182    public function setPassword($input)
183    {
184        if (isset($input['password']) && '' != $input['password']) {
185            $this->password = $input['password'];
186        }
187        if (isset($input['changePassword']) && 0 < count(array_filter($input['changePassword']))) {
188            if (! isset($input['password'])) {
189                $this->password = $input['changePassword'][0];
190            }
191            $this->changePassword = $input['changePassword'];
192        }
193        return $this;
194    }
195
196    public function withDepartmentList()
197    {
198        $departmentList = new Collection\DepartmentList();
199        $entity = clone $this;
200        foreach ($this->departments as $department) {
201            if (! is_array($department) && ! $department instanceof Department) {
202                $department = new Department(array('id' => $department));
203            }
204            $departmentList->addEntity($department);
205        }
206        $entity->departments = $departmentList;
207        return $entity;
208    }
209
210    public function withCleanedUpFormData($keepPassword = false)
211    {
212        unset($this['save']);
213        if (isset($this['password']) && '' == $this['password'] && false === $keepPassword) {
214            unset($this['password']);
215        }
216        if (
217            isset($this['changePassword']) &&
218            0 == count(array_filter($this['changePassword'])) &&
219            false === $keepPassword
220        ) {
221            unset($this['changePassword']);
222        }
223        if (isset($this['oidcProvider'])) {
224            unset($this['oidcProvider']);
225        }
226
227        return $this;
228    }
229
230    /**
231     * verify hashed password and create new if needs rehash
232     *
233     * @return array $useraccount
234    */
235    public function setVerifiedHash($password)
236    {
237        // Do you have old, turbo-legacy, non-crypt hashes?
238        if (strpos($this->password, '$') !== 0) {
239            //error_log(__METHOD__ . "::legacy_hash\n");
240            $result = $this->password === md5($password);
241        } else {
242            //error_log(__METHOD__ . "::password_verify\n");
243            $result = password_verify($password, $this->password);
244        }
245
246        // on passed validation check if the hash needs updating.
247        if ($result && $this->isPasswordNeedingRehash()) {
248            $this->password = $this->getHash($password);
249            //error_log(__METHOD__ . "::rehash\n");
250        }
251
252        return $this;
253    }
254
255    public function withVerifiedHash($password)
256    {
257        $useraccount = clone $this;
258        if ($useraccount->isPasswordNeedingRehash()) {
259            $useraccount->setVerifiedHash($password);
260        }
261        return $useraccount;
262    }
263
264    public function isPasswordNeedingRehash()
265    {
266        return password_needs_rehash($this->password, PASSWORD_DEFAULT);
267    }
268
269    /**
270     * set salted hash by string
271     *
272     * @return string $hash
273    */
274    public function getHash($string)
275    {
276        $hash = password_hash($string, PASSWORD_DEFAULT);
277        return $hash;
278    }
279
280    public function withLessData()
281    {
282        unset($this->departments);
283
284        return $this;
285    }
286
287    /**
288     * create useraccount from open id input data with random password
289     *
290     * @return string $entity
291    */
292    public function createFromOpenidData($data)
293    {
294        $entity = new self();
295        $entity->id = $data['username'];
296        $department = new Department(['id' => 0]);
297        $entity->addDepartment($department);
298        $password = substr(str_shuffle($entity->id . uniqid()), 0, 8);
299        $entity->password = $this->getHash($password);
300        return $entity;
301    }
302
303    /**
304     * get oidc provider from $entity id if it exists
305     *
306     * @return string $entity
307    */
308    public function getOidcProviderFromName()
309    {
310        $providerName = '';
311        if (($pos = strpos($this->id, "@")) !== false) {
312            $providerName = substr($this->id, $pos + 1);
313        }
314        return ('' !== $providerName) ? $providerName : null;
315    }
316}