Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 121
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
FuncAcl
0.00% covered (danger)
0.00%
0 / 121
0.00% covered (danger)
0.00%
0 / 6
1122
0.00% covered (danger)
0.00%
0 / 1
 accessPossible
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
20
 hasAccess
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 applyRule
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
42
 revokeRule
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
42
 evalFilterMask
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 1
240
 getAdvancedLogger
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
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) 2013-2021 (original work) Open Assessment Technologies SA;
19 */
20
21namespace oat\funcAcl\models;
22
23use oat\funcAcl\helpers\CacheHelper;
24use oat\funcAcl\helpers\MapHelper;
25use oat\oatbox\log\logger\AdvancedLogger;
26use oat\oatbox\log\logger\extender\ContextExtenderInterface;
27use oat\oatbox\service\ConfigurableService;
28use oat\oatbox\user\User;
29use oat\tao\model\accessControl\AccessControl;
30use oat\tao\model\accessControl\func\AccessRule;
31use oat\tao\model\accessControl\func\FuncAccessControl;
32use Psr\Log\LoggerInterface;
33use ReflectionException;
34
35/**
36 * @author Joel Bout, <joel@taotesting.com>
37 */
38class FuncAcl extends ConfigurableService implements FuncAccessControl, AccessControl
39{
40    /**
41     * {@inheritdoc}
42     */
43    public function accessPossible(User $user, $controller, $action)
44    {
45        $userRoles = $user->getRoles();
46
47        try {
48            $controllerAccess = CacheHelper::getControllerAccess($controller);
49            $allowedRoles = isset($controllerAccess['actions'][$action])
50                ? array_merge($controllerAccess['module'], $controllerAccess['actions'][$action])
51                : $controllerAccess['module'];
52
53            $accessAllowed = !empty(array_intersect($userRoles, $allowedRoles));
54
55            if (!$accessAllowed) {
56                $this->getAdvancedLogger()->info(
57                    'Access denied.',
58                    [
59                        'allowedRoles' => $allowedRoles,
60                    ]
61                );
62            }
63        } catch (ReflectionException $exception) {
64            $this->getAdvancedLogger()->error(
65                sprintf('Unknown controller "%s"', $controller),
66                [
67                    ContextExtenderInterface::CONTEXT_EXCEPTION => $exception,
68                ]
69            );
70            $accessAllowed = false;
71        }
72
73        return $accessAllowed;
74    }
75
76    /**
77     * {@inheritdoc}
78     */
79    public function hasAccess(User $user, $controllerName, $actionName, $parameters)
80    {
81        return $this->accessPossible($user, $controllerName, $actionName);
82    }
83
84    public function applyRule(AccessRule $rule)
85    {
86        if ($rule->isGrant()) {
87            $accessService = AccessService::singleton();
88            $elements = $this->evalFilterMask($rule->getMask());
89
90            switch (count($elements)) {
91                case 1:
92                    $extension = reset($elements);
93                    $accessService->grantExtensionAccess($rule->getRole(), $extension);
94
95                    break;
96                case 2:
97                    [$extension, $shortName] = $elements;
98                    $accessService->grantModuleAccess($rule->getRole(), $extension, $shortName);
99
100                    break;
101                case 3:
102                    [$extension, $shortName, $action] = $elements;
103                    $accessService->grantActionAccess($rule->getRole(), $extension, $shortName, $action);
104
105                    break;
106                default:
107                    // fail silently warning should already be send
108            }
109        } else {
110            $this->revokeRule(
111                new AccessRule(
112                    AccessRule::GRANT,
113                    $rule->getRole(),
114                    $rule->getMask()
115                )
116            );
117        }
118    }
119
120    public function revokeRule(AccessRule $rule)
121    {
122        if ($rule->isGrant()) {
123            $accessService = AccessService::singleton();
124            $elements = $this->evalFilterMask($rule->getMask());
125
126            switch (count($elements)) {
127                case 1:
128                    $extension = reset($elements);
129                    $accessService->revokeExtensionAccess($rule->getRole(), $extension);
130
131                    break;
132                case 2:
133                    [$extension, $shortName] = $elements;
134                    $accessService->revokeModuleAccess($rule->getRole(), $extension, $shortName);
135
136                    break;
137                case 3:
138                    [$extension, $shortName, $action] = $elements;
139                    $accessService->revokeActionAccess($rule->getRole(), $extension, $shortName, $action);
140
141                    break;
142                default:
143                    // fail silently warning should already be send
144            }
145        } else {
146            $this->getAdvancedLogger()->warning(
147                sprintf(
148                    'Only grant rules accepted in "%s"',
149                    __CLASS__
150                )
151            );
152        }
153    }
154
155    /**
156     * Evaluate the mask to ACL components
157     *
158     * @param mixed $mask
159     *
160     * @return string[] tao ACL components
161     */
162    public function evalFilterMask($mask)
163    {
164        // string masks
165        if (is_string($mask)) {
166            if (strpos($mask, '@') !== false) {
167                [$controller, $action] = explode('@', $mask, 2);
168            } else {
169                $controller = $mask;
170                $action = null;
171            }
172
173            if (class_exists($controller)) {
174                $extension = MapHelper::getExtensionFromController($controller);
175                $shortName = strpos($controller, '\\') !== false
176                    ? substr($controller, strrpos($controller, '\\') + 1)
177                    : substr($controller, strrpos($controller, '_') + 1);
178
179                if (is_null($action)) {
180                    // grant controller
181                    return [$extension, $shortName];
182                }
183
184                // grant action
185                return [$extension, $shortName, $action];
186            }
187
188            $this->getAdvancedLogger()->warning(
189                sprintf(
190                    'Unknown controller "%s"',
191                    $controller
192                )
193            );
194        } elseif (is_array($mask)) { /// array masks
195            if (isset($mask['act'], $mask['mod'], $mask['ext'])) {
196                return [$mask['ext'], $mask['mod'], $mask['act']];
197            }
198
199            if (isset($mask['mod'], $mask['ext'])) {
200                return [$mask['ext'], $mask['mod']];
201            }
202
203            if (isset($mask['ext'])) {
204                return [$mask['ext']];
205            }
206
207            if (isset($mask['controller'])) {
208                $extension = MapHelper::getExtensionFromController($mask['controller']);
209                $shortName = strpos($mask['controller'], '\\') !== false
210                    ? substr($mask['controller'], strrpos($mask['controller'], '\\') + 1)
211                    : substr($mask['controller'], strrpos($mask['controller'], '_') + 1);
212
213                return [$extension, $shortName];
214            }
215
216            if (isset($mask['act']) && strpos($mask['act'], '@') !== false) {
217                [$controller, $action] = explode('@', $mask['act'], 2);
218                $extension = MapHelper::getExtensionFromController($controller);
219                $shortName = strpos($controller, '\\') !== false
220                    ? substr($controller, strrpos($controller, '\\') + 1)
221                    : substr($controller, strrpos($controller, '_') + 1);
222
223                return [$extension, $shortName, $action];
224            }
225
226            $this->getAdvancedLogger()->warning(
227                sprintf(
228                    'Uninterpretable filter in "%s"',
229                    __CLASS__
230                )
231            );
232        } else {
233            $this->getAdvancedLogger()->warning(
234                sprintf(
235                    'Uninterpretable filter type "%s"',
236                    gettype($mask)
237                )
238            );
239        }
240
241        return [];
242    }
243
244    private function getAdvancedLogger(): LoggerInterface
245    {
246        return $this->getServiceManager()->getContainer()->get(AdvancedLogger::ACL_SERVICE_ID);
247    }
248}