Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 85
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
ActionService
0.00% covered (danger)
0.00%
0 / 85
0.00% covered (danger)
0.00%
0 / 5
420
0.00% covered (danger)
0.00%
0 / 1
 hasAccess
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
90
 computePermissions
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 getRequiredRights
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
12
 getResolvedAction
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
20
 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) 2017-2021 (original work) Open Assessment Technologies SA;
19 */
20
21namespace oat\tao\model\menu;
22
23use Throwable;
24use common_user_User;
25use ResolverException;
26use Psr\Log\LoggerInterface;
27use oat\tao\helpers\ControllerHelper;
28use oat\oatbox\log\logger\AdvancedLogger;
29use oat\tao\model\accessControl\AclProxy;
30use oat\oatbox\service\ConfigurableService;
31use oat\tao\model\accessControl\ActionResolver;
32use oat\oatbox\log\logger\extender\ContextExtenderInterface;
33use oat\taoBackOffice\model\menuStructure\Action as MenuAction;
34
35/**
36 * @author Bertrand Chevrier <bertrand@taotesting.com>
37 */
38class 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}