Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 85 |
|
0.00% |
0 / 5 |
CRAP | |
0.00% |
0 / 1 |
ActionService | |
0.00% |
0 / 85 |
|
0.00% |
0 / 5 |
420 | |
0.00% |
0 / 1 |
hasAccess | |
0.00% |
0 / 33 |
|
0.00% |
0 / 1 |
90 | |||
computePermissions | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getRequiredRights | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
12 | |||
getResolvedAction | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
20 | |||
getAdvancedLogger | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
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) 2017-2021 (original work) Open Assessment Technologies SA; |
19 | */ |
20 | |
21 | namespace oat\tao\model\menu; |
22 | |
23 | use Throwable; |
24 | use common_user_User; |
25 | use ResolverException; |
26 | use Psr\Log\LoggerInterface; |
27 | use oat\tao\helpers\ControllerHelper; |
28 | use oat\oatbox\log\logger\AdvancedLogger; |
29 | use oat\tao\model\accessControl\AclProxy; |
30 | use oat\oatbox\service\ConfigurableService; |
31 | use oat\tao\model\accessControl\ActionResolver; |
32 | use oat\oatbox\log\logger\extender\ContextExtenderInterface; |
33 | use oat\taoBackOffice\model\menuStructure\Action as MenuAction; |
34 | |
35 | /** |
36 | * @author Bertrand Chevrier <bertrand@taotesting.com> |
37 | */ |
38 | class ActionService extends ConfigurableService |
39 | { |
40 | public const SERVICE_ID = 'tao/menuaction'; |
41 | |
42 | public const ACCESS_DENIED = 0; |
43 | public const ACCESS_GRANTED = 1; |
44 | public const ACCESS_UNDEFINED = 2; |
45 | |
46 | /** Keep an index of resolved actions */ |
47 | private $resolvedActions = []; |
48 | |
49 | /** |
50 | * @return int The access level |
51 | */ |
52 | public function hasAccess(MenuAction $action, common_user_User $user, array $node) |
53 | { |
54 | $resolvedAction = $this->getResolvedAction($action); |
55 | $advancedLogger = $this->getAdvancedLogger(); |
56 | |
57 | if ($resolvedAction !== null && $user !== null) { |
58 | if ($node['type'] = $resolvedAction['context'] || $resolvedAction['context'] == 'resource') { |
59 | foreach ($resolvedAction['required'] as $key) { |
60 | if (!array_key_exists($key, $node)) { |
61 | $advancedLogger->info( |
62 | sprintf( |
63 | 'Undefined access level (%d): missing required key "%s".', |
64 | self::ACCESS_UNDEFINED, |
65 | $key |
66 | ) |
67 | ); |
68 | |
69 | return self::ACCESS_UNDEFINED; |
70 | } |
71 | } |
72 | |
73 | try { |
74 | return AclProxy::hasAccess($user, $resolvedAction['controller'], $resolvedAction['action'], $node) |
75 | ? self::ACCESS_GRANTED |
76 | : self::ACCESS_DENIED; |
77 | } catch (Throwable $exception) { |
78 | $advancedLogger->error( |
79 | sprintf( |
80 | 'Unable to resolve permission for action "%s": %s', |
81 | $action->getId(), |
82 | $exception->getMessage() |
83 | ), |
84 | [ContextExtenderInterface::CONTEXT_EXCEPTION => $exception] |
85 | ); |
86 | } |
87 | } |
88 | } |
89 | |
90 | $advancedLogger->info( |
91 | sprintf( |
92 | 'Undefined access level (%d).', |
93 | self::ACCESS_UNDEFINED |
94 | ) |
95 | ); |
96 | |
97 | return self::ACCESS_UNDEFINED; |
98 | } |
99 | |
100 | /** |
101 | * Compute the permissions of a node against a list of actions (as actionId => boolean) |
102 | * |
103 | * @param MenuAction[] $actions |
104 | * |
105 | * @return array |
106 | */ |
107 | public function computePermissions(array $actions, common_user_User $user, array $node) |
108 | { |
109 | $permissions = []; |
110 | |
111 | foreach ($actions as $action) { |
112 | $access = $this->hasAccess($action, $user, $node); |
113 | |
114 | if ($access !== self::ACCESS_UNDEFINED) { |
115 | $permissions[$action->getId()] = $access === self::ACCESS_GRANTED; |
116 | } |
117 | } |
118 | |
119 | return $permissions; |
120 | } |
121 | |
122 | /** |
123 | * Get the rights required for the given action |
124 | * |
125 | * @return array |
126 | */ |
127 | public function getRequiredRights(MenuAction $action) |
128 | { |
129 | $rights = []; |
130 | $resolvedAction = $this->getResolvedAction($action); |
131 | |
132 | if ($resolvedAction !== null) { |
133 | try { |
134 | $rights = ControllerHelper::getRequiredRights( |
135 | $resolvedAction['controller'], |
136 | $resolvedAction['action'] |
137 | ); |
138 | } catch (Throwable $exception) { |
139 | $this->getAdvancedLogger()->warning( |
140 | sprintf( |
141 | 'Do not handle permissions for action: %s %s', |
142 | $action->getName(), |
143 | $action->getUrl() |
144 | ), |
145 | [ |
146 | ContextExtenderInterface::CONTEXT_EXCEPTION => $exception, |
147 | ] |
148 | ); |
149 | } |
150 | } |
151 | |
152 | return $rights; |
153 | } |
154 | |
155 | |
156 | /** |
157 | * Get the action resolved against itself in the current context |
158 | * |
159 | * @return array |
160 | */ |
161 | private function getResolvedAction(MenuAction $action) |
162 | { |
163 | $actionId = $action->getId(); |
164 | |
165 | if (!isset($this->resolvedActions[$actionId])) { |
166 | try { |
167 | if ($action->getContext() == '*') { |
168 | // We assume the star context is not permission aware |
169 | $this->resolvedActions[$actionId] = null; |
170 | } else { |
171 | $resolver = new ActionResolver($action->getUrl()); |
172 | $resolvedAction = [ |
173 | 'id' => $action->getId(), |
174 | 'context' => $action->getContext(), |
175 | 'controller' => $resolver->getController(), |
176 | 'action' => $resolver->getAction(), |
177 | ]; |
178 | $resolvedAction['required'] = array_keys( |
179 | ControllerHelper::getRequiredRights($resolvedAction['controller'], $resolvedAction['action']) |
180 | ); |
181 | |
182 | $this->resolvedActions[$actionId] = $resolvedAction; |
183 | } |
184 | } catch (ResolverException | Throwable $exception) { |
185 | $this->resolvedActions[$actionId] = null; |
186 | |
187 | $this->getAdvancedLogger()->warning( |
188 | sprintf( |
189 | 'Do not handle permissions for action: %s %s', |
190 | $action->getName(), |
191 | $action->getUrl() |
192 | ), |
193 | [ContextExtenderInterface::CONTEXT_EXCEPTION => $exception] |
194 | ); |
195 | } |
196 | } |
197 | |
198 | return $this->resolvedActions[$actionId]; |
199 | } |
200 | |
201 | private function getAdvancedLogger(): LoggerInterface |
202 | { |
203 | return $this->getServiceManager()->getContainer()->get(AdvancedLogger::ACL_SERVICE_ID); |
204 | } |
205 | } |