Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
76.34% covered (warning)
76.34%
100 / 131
80.00% covered (warning)
80.00%
12 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
User
76.34% covered (warning)
76.34%
100 / 131
80.00% covered (warning)
80.00%
12 / 15
105.13
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 readWorkstation
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
8
 testWorkstationAssigend
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
72
 testWorkstationAccessRights
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
5
 testWorkstationAssignedRights
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 checkRights
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 checkPermissions
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 checkAnyPermission
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 checkDepartments
60.00% covered (warning)
60.00%
21 / 35
0.00% covered (danger)
0.00%
0 / 1
21.22
 checkDepartment
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
5
 hasRights
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 hasXApiKey
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 testWorkstationIsOveraged
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 testReadDepartmentByOrganisation
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 normalizeDepartmentIds
64.29% covered (warning)
64.29%
9 / 14
0.00% covered (danger)
0.00%
0 / 1
6.14
1<?php
2
3namespace BO\Zmsapi\Helper;
4
5use BO\Slim\Render;
6use BO\Zmsdb\Workstation;
7use BO\Zmsapi\Helper\UserAuth;
8use BO\Zmsentities\Collection\DepartmentList;
9
10/**
11 *
12 * @SuppressWarnings(CouplingBetweenObjects)
13 */
14class User
15{
16    public static $workstation = null;
17    public static $workstationResolved = null;
18
19    public static $assignedWorkstation = null;
20
21    public static $request = null;
22
23    public function __construct($request, $resolveReferences = 0)
24    {
25        static::$request = $request;
26        static::readWorkstation($resolveReferences);
27    }
28
29    public static function readWorkstation($resolveReferences = 0)
30    {
31        $request = (static::$request) ? static::$request : Render::$request;
32        if (! static::$workstation) {
33            $useraccount = UserAuth::getUseraccountByAuthMethod($request);
34            if ($useraccount && $useraccount->hasId()) {
35                static::$workstation = (new Workstation())->readEntity($useraccount->id, $resolveReferences);
36                if ($resolveReferences < 1) {
37                    static::$workstation->useraccount = $useraccount;
38                }
39                static::$workstationResolved = $resolveReferences;
40            } else {
41                static::$workstation = new \BO\Zmsentities\Workstation();
42            }
43        }
44        if ($resolveReferences > static::$workstationResolved && static::$workstation->hasId()) {
45            static::$workstation = (new Workstation())
46                ->readResolvedReferences(static::$workstation, $resolveReferences);
47        }
48        return static::$workstation;
49    }
50
51    /**
52     * @throws \BO\Zmsapi\Exception\Workstation\WorkstationAlreadyAssigned
53     *
54     */
55    public static function testWorkstationAssigend(\BO\Zmsentities\Workstation $entity, $resolveReferences = 0)
56    {
57        if (! static::$assignedWorkstation && $entity->name) {
58            static::$assignedWorkstation = (new Workstation())->readWorkstationByScopeAndName(
59                $entity->scope['id'],
60                $entity->name,
61                $resolveReferences
62            );
63        }
64        if (
65            static::$assignedWorkstation &&
66            static::$assignedWorkstation->id != $entity->id &&
67            static::$assignedWorkstation->name == $entity->name &&
68            static::$assignedWorkstation->scope['id'] == $entity->scope['id'] &&
69            ! static::$assignedWorkstation->getUseraccount()->isOveraged(\App::$now)
70        ) {
71            throw new \BO\Zmsapi\Exception\Workstation\WorkstationAlreadyAssigned();
72        }
73    }
74
75    /**
76     * @throws \BO\Zmsentities\Exception\UserAccountAccessRightsFailed
77     *
78     */
79    public static function testWorkstationAccessRights($useraccount)
80    {
81        if (
82            (
83                ! static::$workstation->getUseraccount()->isSuperUser() &&
84                ! static::$workstation->hasAccessToUseraccount($useraccount)
85            ) ||
86            (
87                ! static::$workstation->getUseraccount()->isSuperUser() &&
88                $useraccount->isSuperUser()
89            )
90        ) {
91            throw new \BO\Zmsentities\Exception\UserAccountAccessRightsFailed();
92        }
93    }
94
95    /**
96     * @throws \BO\Zmsentities\Exception\UserAccountMissingRights
97     * @throws \BO\Zmsentities\Exception\UserAccountMissingLogin
98     *
99     */
100    public static function testWorkstationAssignedRights($useraccount)
101    {
102        static::$workstation
103            ->getUseraccount()
104            ->testRights(
105                array_keys(
106                    array_filter($useraccount->rights, function ($right) {
107                        return (1 == $right);
108                    })
109                )
110            );
111    }
112
113    /**
114     * @return \BO\Zmsentities\Workstation
115     *
116     */
117    public static function checkRights(...$requiredRights)
118    {
119        $workstation = static::readWorkstation();
120        if (\App::RIGHTSCHECK_ENABLED) {
121            $workstation->getUseraccount()->testRights($requiredRights);
122        }
123        return $workstation;
124    }
125
126    public static function checkPermissions(...$requiredPermissions)
127    {
128        $workstation = static::readWorkstation();
129
130        if (\App::RIGHTSCHECK_ENABLED) {
131            $workstation->getUseraccount()->testPermissions($requiredPermissions);
132        }
133
134        return $workstation;
135    }
136
137    public static function checkAnyPermission(...$requiredPermissions)
138    {
139        $workstation = static::readWorkstation();
140
141        if (\App::RIGHTSCHECK_ENABLED) {
142            $workstation->getUseraccount()->testAnyPermission($requiredPermissions);
143        }
144
145        return $workstation;
146    }
147
148    public static function checkDepartments($departmentIds)
149    {
150        $normalizedIds = self::normalizeDepartmentIds($departmentIds);
151        $departments = new DepartmentList();
152
153        if (empty($normalizedIds)) {
154            return $departments;
155        }
156
157        $workstation = static::readWorkstation(2);
158        $userAccount = $workstation->getUseraccount();
159
160        if (! $userAccount->hasId()) {
161            throw new \BO\Zmsentities\Exception\UserAccountMissingLogin();
162        }
163
164        if ($userAccount->isSuperUser()) {
165            // Bulk-load all departments in one query for superusers
166            $departmentMap = (new \BO\Zmsdb\Department())->readEntitiesByIds($normalizedIds, 1);
167            foreach ($normalizedIds as $departmentId) {
168                if (!isset($departmentMap[$departmentId])) {
169                    throw new \BO\Zmsentities\Exception\UserAccountMissingDepartment(
170                        "No access to department " . htmlspecialchars((string) $departmentId)
171                    );
172                }
173                $departments->addEntity($departmentMap[$departmentId]);
174            }
175        } elseif ($userAccount->hasRights(['department'])) {
176            // Users with 'department' rights: need organisation-based access checks
177            // Group departments by organisation and load in batches
178            foreach ($normalizedIds as $departmentId) {
179                $departments->addEntity(self::checkDepartment($departmentId));
180            }
181        } else {
182            // Regular users: extract departments directly from already-loaded user department list
183            $userDepartmentList = $userAccount->getDepartmentList();
184            $accessibleDepartmentIds = $userDepartmentList->getIds();
185            $accessibleRequestedIds = array_intersect($normalizedIds, $accessibleDepartmentIds);
186
187            if (count($accessibleRequestedIds) !== count($normalizedIds)) {
188                // Some requested departments are not accessible
189                $missingIds = array_diff($normalizedIds, $accessibleRequestedIds);
190                throw new \BO\Zmsentities\Exception\UserAccountMissingDepartment(
191                    "No access to department(s): " . implode(', ', array_map('htmlspecialchars', $missingIds))
192                );
193            }
194
195            // Extract requested departments from already-loaded list (no DB query needed)
196            foreach ($accessibleRequestedIds as $departmentId) {
197                $department = $userDepartmentList->getEntity($departmentId);
198                if (!$department || !$department->hasId()) {
199                    throw new \BO\Zmsentities\Exception\UserAccountMissingDepartment(
200                        "No access to department " . htmlspecialchars((string) $departmentId)
201                    );
202                }
203                $departments->addEntity($department);
204            }
205        }
206
207        return $departments;
208    }
209
210    /**
211     * @return \BO\Zmsentities\Department
212     *
213     */
214    public static function checkDepartment($departmentId)
215    {
216        $workstation = static::readWorkstation(2);
217        $userAccount = $workstation->getUseraccount();
218        if (! $userAccount->hasId()) {
219            throw new \BO\Zmsentities\Exception\UserAccountMissingLogin();
220        }
221        if ($userAccount->isSuperUser()) {
222            $department = (new \BO\Zmsdb\Department())->readEntity($departmentId);
223        } elseif ($userAccount->hasRights(['department'])) {
224            $department = self::testReadDepartmentByOrganisation($departmentId, $userAccount);
225        } else {
226            $department = $userAccount->testDepartmentById($departmentId);
227        }
228        if (! $department) {
229            throw new \BO\Zmsentities\Exception\UserAccountMissingDepartment(
230                "No access to department " . htmlspecialchars($departmentId)
231            );
232        }
233        return $department;
234    }
235
236    public static function hasRights()
237    {
238        $userAccount = static::readWorkstation()->getUseraccount();
239        return $userAccount->hasId();
240    }
241
242    /**
243     * Get X-Api-Key from header
244     *
245    */
246    public static function hasXApiKey($request)
247    {
248        $xApiKeyEntity = null;
249        $xApiKey = $request->getHeaderLine('x-api-key');
250        if ($xApiKey) {
251            $xApiKeyEntity = (new \BO\Zmsdb\Apikey())->readEntity($xApiKey);
252        }
253        return ($xApiKeyEntity && $xApiKeyEntity->hasId());
254    }
255
256    public static function testWorkstationIsOveraged($workstation)
257    {
258        if ($workstation->hasId() && $workstation->getUseraccount()->isOveraged(\App::$now)) {
259            $exception = new \BO\Zmsapi\Exception\Useraccount\AuthKeyFound();
260            $exception->data = $workstation;
261            throw $exception;
262        }
263    }
264
265    protected static function testReadDepartmentByOrganisation($departmentId, $userAccount)
266    {
267        $organisation = (new \BO\Zmsdb\Organisation())->readByDepartmentId($departmentId, 1);
268        $organisation->departments = $organisation->getDepartmentList()->withAccess($userAccount);
269        $department = $organisation->departments->getEntity($departmentId);
270        return $department;
271    }
272
273    public static function normalizeDepartmentIds(array $departmentIds)
274    {
275        $normalized = [];
276        foreach ($departmentIds as $departmentId) {
277            if ($departmentId === null) {
278                continue;
279            }
280            $departmentId = trim((string) $departmentId);
281            if ($departmentId === '') {
282                continue;
283            }
284            $validatedId = filter_var($departmentId, FILTER_VALIDATE_INT);
285            if ($validatedId === false) {
286                throw new \BO\Zmsapi\Exception\BadRequest(
287                    "Invalid department ID: " . htmlspecialchars($departmentId)
288                );
289            }
290            $normalized[] = $validatedId;
291        }
292
293        return array_values(array_unique($normalized));
294    }
295}