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