Completed
Push — master ( 1bbe3a...a1970e )
by Oleg
08:32
created

Rbac   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 209
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 209
wmc 22
lcom 1
cbo 3
rs 10

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 2
assign() 0 1 ?
B check() 0 25 5
rawRoles() 0 1 ?
A tree() 0 18 4
A assigned() 0 11 4
A searchRoleRecursive() 0 17 4
A execute() 0 10 2
A revoke() 0 5 1
1
<?php /** MicroRBAC */
2
3
namespace Micro\Auth\Drivers;
4
5
use Micro\Auth\Adapter;
6
use Micro\Db\Drivers\IDriver;
7
use Micro\Db\IConnection;
8
use Micro\Mvc\Models\Query;
9
10
/**
11
 * Abstract RBAC class file.
12
 *
13
 * @author Oleg Lunegov <[email protected]>
14
 * @link https://github.com/linpax/microphp-framework
15
 * @copyright Copyright (c) 2013 Oleg Lunegov
16
 * @license https://github.com/linpax/microphp-framework/blob/master/LICENSE
17
 * @package Micro
18
 * @subpackage Auth\Drivers
19
 * @version 1.0
20
 * @since 1.0
21
 */
22
abstract class Rbac implements Adapter
23
{
24
    /** @const integer TYPE_ROLE */
25
    const TYPE_ROLE = 0;
26
    /** @const integer TYPE_PERMISSION */
27
    const TYPE_PERMISSION = 1;
28
    /** @const integer TYPE_OPERATION */
29
    const TYPE_OPERATION = 2;
30
31
    /** @var IDriver $db */
32
    protected $db;
33
34
35
    /**
36
     * Based constructor for RBAC rules
37
     *
38
     * @access public
39
     *
40
     * @param IConnection $connection
41
     *
42
     * @result void
43
     */
44
    public function __construct(IConnection $connection)
45
    {
46
        $this->db = $connection->getDriver();
47
48
        if (!$this->db->tableExists('rbac_user')) {
49
            $this->db->createTable('rbac_user', [
50
                '`role` varchar(127) NOT NULL',
51
                '`user` int(10) unsigned NOT NULL',
52
                'UNIQUE KEY `name` (`name`,`user`)'
53
            ], 'ENGINE=MyISAM DEFAULT CHARSET=utf8');
54
        }
55
    }
56
57
    /**
58
     * Assign RBAC element into user
59
     *
60
     * @access public
61
     *
62
     * @param integer $userId user id
63
     * @param string $name element name
64
     *
65
     * @return bool
66
     */
67
    abstract public function assign($userId, $name);
68
69
    /**
70
     * Check privileges to operation
71
     *
72
     * @access public
73
     *
74
     * @param integer $userId user id
75
     * @param string $permission permission name
76
     * @param array $data action params
77
     *
78
     * @return boolean
79
     * @throws \Micro\Base\Exception
80
     */
81
    public function check($userId, $permission, array $data = [])
82
    {
83
        $rawRoles = $this->rawRoles();
84
        $tree = $this->tree($rawRoles);
85
86
        /** @var array $roles */
87
        $roles = $this->assigned($userId);
88
        if (!$roles) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $roles of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
89
            return false;
90
        }
91
92
        foreach ($roles AS $role) {
93
94
            $actionRole = $this->searchRoleRecursive($tree, $role['name']);
95
            if ($actionRole) {
96
                /** @var array $trustRole */
97
                $trustRole = $this->searchRoleRecursive($actionRole, $permission);
98
                if ($trustRole) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $trustRole of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
99
                    return $this->execute($trustRole[$permission], $data);
100
                }
101
            }
102
        }
103
104
        return false;
105
    }
106
107
    /**
108
     * Get raw roles
109
     *
110
     * @access public
111
     * @return mixed
112
     */
113
    abstract public function rawRoles();
114
115
    /**
116
     * Build tree from RBAC rules
117
     *
118
     * @access public
119
     *
120
     * @param array $elements elements array
121
     * @param string $parentId parent ID
122
     *
123
     * @return array
124
     */
125
    public function tree(&$elements, $parentId = '0')
126
    {
127
        $branch = [];
128
        foreach ($elements AS $key => $element) {
129
            if ($element['based'] === (string)$parentId) {
130
                $children = $this->tree($elements, $element['name']);
131
132
                if ($children) {
133
                    $element['childs'] = $children;
134
                }
135
136
                $branch[$element['name']] = $element;
137
                unset($elements[$key]);
138
            }
139
        }
140
141
        return $branch;
142
    }
143
144
    /**
145
     * Get assigned to user RBAC elements
146
     *
147
     * @access public
148
     *
149
     * @param integer $userId user ID
150
     *
151
     * @return mixed
152
     * @throws \Micro\Base\Exception
153
     */
154
    public function assigned($userId)
155
    {
156
        $query = new Query($this->db);
157
        $query->distinct = true;
158
        $query->select = $this->db->getDriverType() === 'pgsql' ? '"role" AS "name"' : '`role` AS `name`';
159
        $query->table = $this->db->getDriverType() === 'pgsql' ? '"rbac_user"' : '`rbac_user`';
160
        $query->addWhere(($this->db->getDriverType() === 'pgsql' ? '"user"=' : '`user`=').$userId);
161
        $query->single = false;
162
163
        return $query->run(\PDO::FETCH_ASSOC);
164
    }
165
166
    /**
167
     * Recursive search in roles array
168
     *
169
     * @access public
170
     *
171
     * @param array $roles elements
172
     * @param string $finder element name to search
173
     *
174
     * @return array|false
175
     */
176
    protected function searchRoleRecursive($roles, $finder)
177
    {
178
        $result = false;
179
        foreach ($roles AS $id => $role) {
180
            if ($id === $finder) {
181
                $result = [$id => $role];
182
                break;
183
            } else {
184
                if (!empty($role['childs'])) {
185
                    $result = $this->searchRoleRecursive($role['childs'], $finder);
186
                    break;
187
                }
188
            }
189
        }
190
191
        return $result;
192
    }
193
194
    /**
195
     * Execute rule
196
     *
197
     * @access public
198
     *
199
     * @param array $role element
200
     * @param array $data action params
201
     *
202
     * @return bool
203
     */
204
    public function execute(array $role, array $data)
205
    {
206
        if (!$role['data']) {
207
            return true;
208
        } else {
209
            extract($data, EXTR_OVERWRITE);
210
211
            return eval('return '.$role['data']);
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
212
        }
213
    }
214
215
    /**
216
     * Revoke RBAC element from user
217
     *
218
     * @access public
219
     *
220
     * @param integer $userId user id
221
     * @param string $name element name
222
     *
223
     * @return bool
224
     */
225
    public function revoke($userId, $name)
226
    {
227
        return $this->db->delete('rbac_user', 'name=:name AND user=:user',
228
            ['name' => $name, 'user' => $userId]);
229
    }
230
}
231