SimpleRbacAuthorize   A
last analyzed

Complexity

Total Complexity 25

Size/Duplication

Total Lines 212
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 7

Test Coverage

Coverage 96.61%

Importance

Changes 0
Metric Value
dl 0
loc 212
ccs 57
cts 59
cp 0.9661
rs 10
c 0
b 0
f 0
wmc 25
lcom 2
cbo 7

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 2
A _loadPermissions() 0 16 3
A authorize() 0 12 2
A _checkRules() 0 12 3
B _matchRule() 0 26 8
B _matchOrAsterisk() 0 14 7
1
<?php
2
/**
3
 * Copyright 2016 - 2018, Cake Development Corporation (http://cakedc.com)
4
 *
5
 * Licensed under The MIT License
6
 * Redistributions of files must retain the above copyright notice.
7
 *
8
 * @copyright Copyright 2016 - 2018, Cake Development Corporation (http://cakedc.com)
9
 * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
10
 */
11
12
namespace CakeDC\Api\Service\Auth\Authorize;
13
14
use CakeDC\Api\Service\Action\Action;
15
use CakeDC\Users\Auth\Rules\Rule;
16
use Cake\Core\Configure;
17
use Cake\Core\Exception\Exception;
18
use Cake\Http\ServerRequest;
19
use Cake\Log\LogTrait;
20
use Cake\Utility\Hash;
21
use Cake\Utility\Inflector;
22
use Psr\Log\LogLevel;
23
24
/**
25
 * Simple Rbac Authorize
26
 *
27
 * Matches current plugin/controller/action against defined permissions in permissions.php file
28
 */
29
class SimpleRbacAuthorize extends BaseAuthorize
30
{
31
    use LogTrait;
32
33
    protected $_defaultConfig = [
34
        //autoload permissions.php
35
        'autoload_config' => 'api_permissions',
36
        //role field in the Users table
37
        'role_field' => 'role',
38
        //default role, used in new users registered and also as role matcher when no role is available
39
        'default_role' => 'user',
40
        /*
41
         * This is a quick roles-permissions implementation
42
         * Rules are evaluated top-down, first matching rule will apply
43
         * Each line define
44
         *      [
45
         *          'role' => 'admin',
46
         *          'plugin', (optional, default = null)
47
         *          'prefix', (optional, default = null)
48
         *          'extension', (optional, default = null)
49
         *          'controller',
50
         *          'action',
51
         *          'allowed' (optional, default = true)
52
         *      ]
53
         * You could use '*' to match anything
54
         * You could use [] to match an array of options, example 'role' => ['adm1', 'adm2']
55
         * You could use a callback in your 'allowed' to process complex authentication, like
56
         *   - ownership
57
         *   - permissions stored in your database
58
         *   - permission based on an external service API call
59
         * You could use an instance of the \CakeDC\Users\Auth\Rules\Rule interface to reuse your custom rules
60
         *
61
         * Examples:
62
         * 1. Callback to allow users editing their own Posts:
63
         *
64
         * 'allowed' => function (array $user, $role, Request $request) {
65
         *       $postId = Hash::get($request->params, 'pass.0');
66
         *       $post = TableRegistry::get('Posts')->get($postId);
67
         *       $userId = Hash::get($user, 'id');
68
         *       if (!empty($post->user_id) && !empty($userId)) {
69
         *           return $post->user_id === $userId;
70
         *       }
71
         *       return false;
72
         *   }
73
         * 2. Using the Owner Rule
74
         * 'allowed' => new Owner() //will pick by default the post id from the first pass param
75
         *
76
         * Check the Owner Rule docs for more details
77
         *
78
         *
79
         */
80
        'permissions' => [],
81
    ];
82
83
    /**
84
     * Default permissions to be loaded if no provided permissions
85
     *
86
     * @var array
87
     */
88
    protected $_defaultPermissions = [
89
        [
90
            'role' => 'admin',
91
            'version' => '*',
92
            'service' => '*',
93
            'action' => '*',
94
        ],
95
    ];
96
97
    /**
98
     * Autoload permission configuration
99
     *
100
     * @param Action $action An Action instance.
101
     * @param array $config config
102
     */
103 3
    public function __construct(Action $action, array $config = [])
104
    {
105 3
        parent::__construct($action, $config);
106 3
        $autoload = $this->getConfig('autoload_config');
107 3
        if ($autoload) {
108 2
            $loadedPermissions = $this->_loadPermissions($autoload);
109 2
            $this->setConfig('permissions', $loadedPermissions);
110 2
        }
111 3
    }
112
113
    /**
114
     * Load config and retrieve permissions
115
     * If the configuration file does not exist, or the permissions key not present, return defaultPermissions
116
     * To be mocked
117
     *
118
     * @param string $key name of the configuration file to read permissions from
119
     * @return array permissions
120
     */
121 1
    protected function _loadPermissions($key)
122
    {
123
        try {
124 1
            Configure::load($key, 'default');
125
            $permissions = Configure::read('Api.SimpleRbac.permissions');
126 1
        } catch (Exception $ex) {
127 1
            $msg = __d('CakeDC/Api', 'Missing configuration file: "config/{0}.php". Using default permissions', $key);
128 1
            $this->log($msg, LogLevel::WARNING);
129
        }
130
131 1
        if (empty($permissions)) {
132 1
            return $this->_defaultPermissions;
133
        }
134
135
        return $permissions;
136
    }
137
138
    /**
139
     * Match the current plugin/controller/action against loaded permissions
140
     * Set a default role if no role is provided
141
     *
142
     * @param array $user user data
143
     * @param Request $request request
144
     * @return bool
145
     */
146 17
    public function authorize($user, ServerRequest $request)
147
    {
148 17
        $roleField = $this->getConfig('role_field');
149 17
        $role = $this->getConfig('default_role');
150 17
        if (Hash::check($user, $roleField)) {
151 17
            $role = Hash::get($user, $roleField);
152 17
        }
153
154 17
        $allowed = $this->_checkRules($user, $role, $request);
155
156 17
        return $allowed;
157
    }
158
159
    /**
160
     * Match against permissions, return if matched
161
     * Permissions are processed based on the 'permissions' config values
162
     *
163
     * @param array $user current user array
164
     * @param string $role effective role for the current user
165
     * @param Request $request request
166
     * @return bool true if there is a match in permissions
167
     */
168 17
    protected function _checkRules(array $user, $role, ServerRequest $request)
169
    {
170 17
        $permissions = $this->getConfig('permissions');
171 17
        foreach ($permissions as $permission) {
172 17
            $allowed = $this->_matchRule($permission, $user, $role, $request);
0 ignored issues
show
Documentation introduced by
$request is of type object<Cake\Http\ServerRequest>, but the function expects a object<CakeDC\Api\Service\Auth\Authorize\Request>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
173 17
            if ($allowed !== null) {
174 16
                return $allowed;
175
            }
176 1
        }
177
178 1
        return false;
179
    }
180
181
    /**
182
     * Match the rule for current permission
183
     *
184
     * @param array $permission configuration
185
     * @param array $user current user
186
     * @param string $role effective user role
187
     * @param Request $request request
188
     * @return bool if rule matched, null if rule not matched
189
     */
190 17
    protected function _matchRule($permission, $user, $role, $request)
191
    {
192 17
        $action = $this->_action->getName();
193 17
        $service = $this->_action->getService()->getName();
194 17
        $version = $this->_action->getService()->getVersion();
195
196 17
        if ($this->_matchOrAsterisk($permission, 'role', $role) &&
0 ignored issues
show
Documentation introduced by
$permission is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
197 17
                $this->_matchOrAsterisk($permission, 'version', $version, true) &&
0 ignored issues
show
Documentation introduced by
$permission is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
198 17
                $this->_matchOrAsterisk($permission, 'service', $service) &&
0 ignored issues
show
Documentation introduced by
$permission is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
199 17
                $this->_matchOrAsterisk($permission, 'action', $action)) {
0 ignored issues
show
Documentation introduced by
$permission is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
200 16
            $allowed = Hash::get($permission, 'allowed');
201
202 16
            if ($allowed === null) {
203
                //allowed will be true by default
204 8
                return true;
205 8
            } elseif (is_callable($allowed)) {
206 2
                return (bool)call_user_func($allowed, $user, $role, $request);
207 6
            } elseif ($allowed instanceof Rule) {
0 ignored issues
show
Bug introduced by
The class CakeDC\Users\Auth\Rules\Rule does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
208 1
                return (bool)$allowed->allowed($user, $role, $request);
209
            } else {
210 5
                return (bool)$allowed;
211
            }
212
        }
213
214 1
        return null;
215
    }
216
217
    /**
218
     * Check if rule matched or '*' present in rule matching anything
219
     *
220
     * @param string $permission permission configuration
221
     * @param string $key key to retrieve and check in permissions configuration
222
     * @param string $value value to check with (coming from the request) We'll check the DASHERIZED value too
223
     * @param bool $allowEmpty true if we allow
224
     * @return bool
225
     */
226 17
    protected function _matchOrAsterisk($permission, $key, $value, $allowEmpty = false)
227
    {
228 17
        $possibleValues = (array)Hash::get($permission, $key);
229 17
        if ($allowEmpty && empty($possibleValues) && $value === null) {
230 17
            return true;
231
        }
232 17
        if (Hash::get($permission, $key) === '*' ||
0 ignored issues
show
Documentation introduced by
$permission is of type string, but the function expects a array|object<ArrayAccess>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
233 16
                in_array($value, $possibleValues) ||
234 17
                in_array(Inflector::camelize($value, '-'), $possibleValues)) {
235 17
            return true;
236
        }
237
238 1
        return false;
239
    }
240
}
241