Issues (4)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Auth/HierAuthorize.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace HierAuth\Auth;
3
4
use Cake\Auth\BaseAuthorize;
5
use Cake\Cache\Cache;
6
use Cake\Controller\ComponentRegistry;
7
use Cake\Core\Exception\Exception;
8
use Cake\Network\Request;
9
use Symfony\Component\Yaml\Yaml;
10
11
/**
12
 * Licensed under The MIT License
13
 * For full copyright and license information, please see the LICENSE.txt
14
 * Redistributions of files must retain the above copyright notice.
15
 *
16
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
17
 */
18
class HierAuthorize extends BaseAuthorize
19
{
20
21
    protected $_denySign = '-';
22
    protected $_referenceSign = '@';
23
    protected $_allSign = 'ALL';
24
25
    protected $_hierarchy;
26
    protected $_acl;
27
28
    protected $_rootHierarchy;
29
30
    protected $_defaultConfig = [
31
        'hierarchyFile' => 'hierarchy.yml',
32
        'aclFile' => 'acl.yml',
33
        'roleColumn' => 'roles',
34
    ];
35
36
    /**
37
     * @param ComponentRegistry $registry The controller for this request.
38
     * @param array $config An array of config. This class does not use any config.
39
     */
40
    public function __construct(ComponentRegistry $registry, array $config = [])
41
    {
42
        parent::__construct($registry, $config);
43
44
        if (!file_exists(CONFIG . $this->config('hierarchyFile'))) {
45
            throw new Exception(
46
                sprintf("Provided hierarchy config file %s doesn't exist.", $this->config('hierarchyFile'))
47
            );
48
        }
49
50
        if (!file_exists(CONFIG . $this->config('aclFile'))) {
51
            throw new Exception(sprintf("Provided ACL config file %s doesn't exist.", $this->config('aclFile')));
52
        }
53
54
        // caching
55
        $hierarchyModified = filemtime(CONFIG . $this->config('hierarchyFile'));
56
        $aclModified = filemtime(CONFIG . $this->config('aclFile'));
57
58
        $lastModified = ($hierarchyModified > $aclModified) ? $hierarchyModified : $aclModified;
59
60
        if (Cache::read('hierarchy_auth_build_time') < $lastModified) {
61
            $this->_hierarchy = $this->_getHierarchy();
62
            $this->_acl = $this->_getAcl();
63
64
            Cache::write('hierarchy_auth_cache', ['acl' => $this->_acl, 'hierarchy' => $this->_hierarchy]);
65
            Cache::write('hierarchy_auth_build_time', time());
66
        } else {
67
            $cache = Cache::read('hierarchy_auth_cache');
68
            $this->_hierarchy = $cache['hierarchy'];
69
            $this->_acl = $cache['acl'];
70
        }
71
    }
72
73
    /**
74
     * @param array $user Active user data
75
     * @param Request $request Request instance.
76
     * @return bool
77
     */
78
    public function authorize($user, Request $request)
79
    {
80
        $controller = $request->param('controller');
81
        $action = $request->param('action');
82
83
        return $this->validate($user, $controller, $action);
84
    }
85
86
    /**
87
     * Authorize user based on controller, and action
88
     *
89
     * @param array $user Active user data
90
     * @param string $controller Controller to validate
91
     * @param string $action Action to validate
92
     * @return bool
93
     */
94
    public function validate($user, $controller, $action)
95
    {
96
        $userRoles = $this->_getRoles($user);
97
        $authRoles = [];
98
99
        if (isset($this->_acl[$this->_allSign])) {
100
            $authRoles = $this->_acl[$this->_allSign];
101
        }
102
103
        if (isset($this->_acl[$controller][$this->_allSign])) {
104
            $authRoles = array_merge($this->_acl[$controller][$this->_allSign], $authRoles);
105
        }
106
107
        if (isset($this->_acl[$controller][$action])) {
108
            $authRoles = array_merge($this->_acl[$controller][$action], $authRoles);
109
        }
110
111
        foreach ($userRoles as $userRole) {
112
            if (isset($authRoles[$userRole]) && $authRoles[$userRole]) {
113
                return true;
114
            }
115
        }
116
117
        return false;
118
    }
119
120
    /**
121
     * Retrieve and parse hierarchy configuration
122
     *
123
     * @return array
124
     */
125 View Code Duplication
    protected function _getHierarchy()
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
126
    {
127
        $file = $this->config('hierarchyFile');
128
129
        $yaml = file_get_contents(CONFIG . $file);
130
        try {
131
            $yaml = Yaml::parse($yaml);
132
        } catch (\Exception $e) {
133
            throw new Exception(sprintf('Malformed hierarchy config file, check YAML syntax: %s', $e->getMessage()));
134
        }
135
136
        if (!isset($yaml['hierarchy'])) {
137
            throw new Exception("The hierarchy configuration must be under key \"hierarchy\". No such key was found.");
138
        }
139
140
        $hierarchy = $this->_parseHierarchy($yaml['hierarchy']);
141
142
        return $hierarchy;
143
    }
144
145
    /**
146
     * Parses and flattens hierarchy settings.
147
     *
148
     * @param array $hierarchy Hierarchy settings as an array.
149
     * @param int $recLevel Recursion level.
150
     * @return array
151
     */
152
153
    /**
154
     * @param array $hierarchy An array of the hierarchy data
155
     * @param int $recLevel Recursion level
156
     * @return array
157
     * @throws Exception
158
     */
159
    protected function _parseHierarchy(array $hierarchy, $recLevel = 0)
160
    {
161
        if (!isset($this->_rootHierarchy)) {
162
            $this->_rootHierarchy = $hierarchy;
163
        }
164
165
        $offset = 0;
166
        foreach ($hierarchy as $key => $subRole) {
167
            // recursively go through roles
168
            if (is_array($subRole)) {
169
                $subRole = $this->_parseHierarchy($subRole);
170
                $hierarchy[$key] = array_unique($subRole); // remove duplicate roles
171
            } else {
172
                // flatten references
173
                if (substr($subRole, 0, strlen($this->_referenceSign)) == $this->_referenceSign) {
174
                    // check if reference is valid
175
                    if (!isset($this->_rootHierarchy[substr($subRole, strlen($this->_referenceSign))])) {
176
                        throw new Exception(sprintf("A reference in hierarchy doesn't exist: %s", $subRole));
177
                    }
178
179
                    // recursion protection
180
                    if ($recLevel >= 10) {
181
                        throw new Exception(sprintf("Recursion occured. Check reference: %s", $subRole));
182
                    }
183
184
                    // replace reference with referenced roles
185
                    $subRole = $this->_parseHierarchy(
186
                        $this->_rootHierarchy[substr($subRole, strlen($this->_referenceSign))],
187
                        ++$recLevel
188
                    );
189
                    array_splice($hierarchy, $offset, 1, $subRole);
190
                }
191
            }
192
            $offset++;
193
        }
194
195
        return $hierarchy;
196
    }
197
198
    /**
199
     * Retrieve and parse acl configuration
200
     *
201
     * @return array
202
     */
203 View Code Duplication
    protected function _getAcl()
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
204
    {
205
        $file = $this->config('aclFile');
206
207
        $yaml = file_get_contents(CONFIG . $file);
208
        try {
209
            $yaml = Yaml::parse($yaml);
210
        } catch (\Exception $e) {
211
            throw new Exception(sprintf('Malformed acl configuration file. Check syntax: %s', $e->getMessage()));
212
        }
213
214
        if (!isset($yaml['controllers'])) {
215
            throw new Exception('The ACL configuration must be under key \"controllers\". No such key was found.');
216
        }
217
218
        return $this->_parseAcl($yaml['controllers']);
219
    }
220
221
222
    /**
223
     * Parses ACL configuration
224
     *
225
     * @param array $acl Acl configuration in array format
226
     * @return array
227
     */
228
    protected function _parseAcl(array $acl)
229
    {
230
        $parsedAcl = [];
231
232
        // check global ACL access rights
233 View Code Duplication
        if (isset($acl[$this->_allSign])) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
234
            $parsedAcl[$this->_allSign] = $this->_iterateAccessRights($acl[$this->_allSign]);
235
            unset($acl[$this->_allSign]);
236
        }
237
238
        // iterate through controllers and format role authorization
239
        foreach ($acl as $controller => $actions) {
240
            $parsedAcl[$controller] = [];
241
            // check controller-wide access rights
242 View Code Duplication
            if (isset($actions[$this->_allSign])) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
243
                $parsedAcl[$controller][$this->_allSign] = $this->_iterateAccessRights($actions[$this->_allSign]);
244
                unset($actions[$this->_allSign]);
245
            }
246
247
            // check controller actions' access rights
248
            foreach ($actions as $action => $roles) {
249
                $parsedAcl[$controller][$action] = $this->_iterateAccessRights($roles);
250
            }
251
        }
252
253
        return $parsedAcl;
254
    }
255
256
    /**
257
     * Helper function
258
     * Convert YAML config access rights
259
     *
260
     * @param array $yamlRoles Array of roles to iterate through
261
     * @return array
262
     */
263
    protected function _iterateAccessRights(array $yamlRoles)
264
    {
265
        $checkedRoles = [];
266
267
        foreach ($yamlRoles as $role) {
268
            if (substr($role, 0, strlen($this->_denySign)) == $this->_denySign) {
269
                $checkedRoles = array_merge(
270
                    $checkedRoles,
271
                    $this->_flattenSuperRole(substr($role, strlen($this->_denySign)), false)
272
                );
273
            } else {
274
                $checkedRoles = array_merge($checkedRoles, $this->_flattenSuperRole($role, true));
275
            }
276
        }
277
278
        return $checkedRoles;
279
    }
280
281
    /**
282
     * Helper function
283
     * Check and return roles belonging to a super role with super role's access rights
284
     *
285
     * @param string $role Referenced role to flatten
286
     * @param bool $authorized Super role is authorized or not
287
     * @return array
288
     */
289
    protected function _flattenSuperRole($role, $authorized)
290
    {
291
        if (isset($this->_hierarchy[$role])) {
292
            $roles = [];
293
            foreach ($this->_hierarchy[$role] as $subRole) {
294
                $roles[$subRole] = $authorized;
295
            }
296
            $roles[$role] = $authorized;
297
        } else {
298
            $roles = [$role => $authorized];
299
        }
300
301
        return $roles;
302
    }
303
304
    /**
305
     * Retrieve role labels based on configuration.
306
     *
307
     * @param array $user Active user data
308
     * @return bool|array
309
     */
310
    protected function _getRoles(array $user)
311
    {
312
        // check if json column based authentication
313
        if ($this->config('roleColumn')) {
314
            if (!isset($user[$this->config('roleColumn')])) {
315
                throw new Exception(
316
                    sprintf('Provided roleColumn "%s" doesn\'t exist for this user.', $this->config('roleColumn'))
317
                );
318
            }
319
320
            $roles = $user[$this->config('roleColumn')];
321
            // check if column is received already decoded, if not, json decode.
322
            if (!is_array($roles)) {
323
                $roles = json_decode($roles, true);
324
                if (!is_array($roles)) {
325
                    throw new Exception(
326
                        sprintf('roleColumn "%s" is not in a valid format.', $this->config('roleColumn'))
327
                    );
328
                }
329
            }
330
        } else {
331
            $roleKeys = $this->config('roleKeys');
332
333
            // check if role keys are configured correctly
334
            if (!is_array($roleKeys)) {
335
                throw new Exception('roleKeys must be an array.');
336
            }
337
338
            $roles = [];
339
            // collect all roles based on configuration, from provided associations
340
            foreach ($roleKeys as $role => $settings) {
341
                // check if multiple roles or single role per user
342
                if (!empty($settings['multi'])) {
343
                    if (!isset($user[$role])) {
344
                        throw new Exception(sprintf('Provided association %s doesn\'t exist.', $role));
345
                    }
346
347
                    foreach ($user[$role] as $userRole) {
348
                        if (!isset($userRole[$settings['column']])) {
349
                            throw new Exception(
350
                                sprintf(
351
                                    'Provided column %s with association %s doesn\'t exist.',
352
                                    $settings['column'],
353
                                    $role
354
                                )
355
                            );
356
                        }
357
358
                        $roles[] = $userRole[$settings['column']];
359
                    }
360
                } else {
361
                    if (!isset($user[$role]) || !isset($user[$role][$settings['column']])) {
362
                        throw new Exception(
363
                            sprintf(
364
                                'Provided column %s with association %s doesn\'t exist.',
365
                                $settings['column'],
366
                                $role
367
                            )
368
                        );
369
                    }
370
371
                    $roles[] = $user[$role][$settings['column']];
372
                }
373
            }
374
        }
375
376
        return $roles;
377
    }
378
}
379