Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
82.54% covered (warning)
82.54%
52 / 63
50.00% covered (danger)
50.00%
3 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
ChangePermissionsService
82.54% covered (warning)
82.54%
52 / 63
50.00% covered (danger)
50.00%
3 / 6
20.92
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 change
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
 getResourceToUpdate
66.67% covered (warning)
66.67%
8 / 12
0.00% covered (danger)
0.00%
0 / 1
3.33
 calculateChanges
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
6
 assertHasUserWithGrantPermission
33.33% covered (danger)
33.33%
3 / 9
0.00% covered (danger)
0.00%
0 / 1
5.67
 triggerEvents
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2023 (original work) Open Assessment Technologies SA.
19 */
20
21declare(strict_types=1);
22
23namespace oat\taoDacSimple\model;
24
25use core_kernel_classes_Resource;
26use oat\oatbox\event\EventManager;
27use oat\tao\model\event\DataAccessControlChangedEvent;
28use oat\taoDacSimple\model\Command\ChangeAccessCommand;
29use oat\taoDacSimple\model\Command\ChangePermissionsCommand;
30use oat\taoDacSimple\model\event\DacAffectedUsersEvent;
31use oat\taoDacSimple\model\event\DacRootChangedEvent;
32
33class ChangePermissionsService
34{
35    private DataBaseAccess $dataBaseAccess;
36    private PermissionsStrategyInterface $strategy;
37    private EventManager $eventManager;
38
39    public function __construct(
40        DataBaseAccess $dataBaseAccess,
41        PermissionsStrategyInterface $strategy,
42        EventManager $eventManager
43    ) {
44        $this->dataBaseAccess = $dataBaseAccess;
45        $this->strategy = $strategy;
46        $this->eventManager = $eventManager;
47    }
48
49    public function change(ChangePermissionsCommand $command): void
50    {
51        $resources = $this->getResourceToUpdate($command->getRoot(), $command->isRecursive());
52
53        $permissions = $this->dataBaseAccess->getResourcesPermissions(array_column($resources, 'id'));
54        $rootPermissions = $permissions[$command->getRoot()->getUri()];
55        $permissionsDelta = $this->strategy->normalizeRequest($rootPermissions, $command->getPrivilegesPerUser());
56
57        if (empty($permissionsDelta['remove']) && empty($permissionsDelta['add'])) {
58            return;
59        }
60
61        $changeAccessCommand = $this->calculateChanges($resources, $permissionsDelta, $permissions);
62        $this->dataBaseAccess->changeAccess($changeAccessCommand);
63
64        $this->triggerEvents($command->getRoot(), $permissionsDelta, $command->isRecursive());
65    }
66
67    private function getResourceToUpdate(core_kernel_classes_Resource $resource, bool $isRecursive): array
68    {
69        if ($isRecursive) {
70            $resources = [];
71
72            foreach ($resource->getNestedResources() as $result) {
73                $resources[$result['id']] = $result;
74            }
75
76            return $resources;
77        }
78
79        return [
80            $resource->getUri() => [
81                'id' => $resource->getUri(),
82                'isClass' => $resource->isClass(),
83                'level' => 1,
84            ],
85        ];
86    }
87
88    private function calculateChanges(
89        array $resources,
90        array $permissionsDelta,
91        array $currentPermissions
92    ): ChangeAccessCommand {
93        $command = new ChangeAccessCommand();
94
95        foreach ($resources as $resource) {
96            $resourcePermissions = $currentPermissions[$resource['id']];
97
98            $remove = $this->strategy->getPermissionsToRemove($resourcePermissions, $permissionsDelta);
99            $add = $this->strategy->getPermissionsToAdd($resourcePermissions, $permissionsDelta);
100
101            foreach ($remove as $userId => $permissions) {
102                $resourcePermissions[$userId] = array_diff($resourcePermissions[$userId] ?? [], $permissions);
103
104                foreach ($permissions as $permission) {
105                    $command->revokeResourceForUser($resource['id'], $permission, $userId);
106                }
107            }
108
109            foreach ($add as $userId => $permissions) {
110                $resourcePermissions[$userId] = array_merge($resourcePermissions[$userId] ?? [], $permissions);
111
112                foreach ($permissions as $permission) {
113                    $command->grantResourceForUser($resource['id'], $permission, $userId);
114                }
115            }
116
117            $this->assertHasUserWithGrantPermission($resource['id'], $resourcePermissions);
118        }
119
120        return $command;
121    }
122
123    /**
124     * Checks if all resources after all actions are applied will have at least
125     * one user with GRANT permission.
126     */
127    private function assertHasUserWithGrantPermission(string $resourceId, array $resourcePermissions): void
128    {
129        foreach ($resourcePermissions as $permissions) {
130            if (in_array(PermissionProvider::PERMISSION_GRANT, $permissions, true)) {
131                return;
132            }
133        }
134
135        throw new PermissionsServiceException(
136            sprintf(
137                'Resource %s should have at least one user with GRANT access',
138                $resourceId
139            )
140        );
141    }
142
143    private function triggerEvents(
144        core_kernel_classes_Resource $resource,
145        array $permissionsDelta,
146        bool $isRecursive
147    ): void {
148        if (!empty($permissionsDelta['add']) || !empty($permissionsDelta['remove'])) {
149            $this->eventManager->trigger(new DacRootChangedEvent($resource, $permissionsDelta));
150        }
151
152        $this->eventManager->trigger(
153            new DataAccessControlChangedEvent(
154                $resource->getUri(),
155                $permissionsDelta,
156                $isRecursive
157            )
158        );
159
160        $this->eventManager->trigger(
161            new DacAffectedUsersEvent(
162                array_keys($permissionsDelta['add']),
163                array_keys($permissionsDelta['remove'])
164            )
165        );
166    }
167}