PrivilegesTrait   F
last analyzed

Complexity

Total Complexity 76

Size/Duplication

Total Lines 397
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 186
c 1
b 0
f 0
dl 0
loc 397
rs 2.32
wmc 76

3 Methods

Rating   Name   Duplication   Size   Complexity  
C getPrivileges() 0 68 14
F setPrivileges() 0 131 36
F parseACL() 0 128 26

How to fix   Complexity   

Complex Class

Complex classes like PrivilegesTrait often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PrivilegesTrait, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * PHPPgAdmin 6.1.3
5
 */
6
7
namespace PHPPgAdmin\Database\Traits;
8
9
/**
10
 * Common trait for privileges manipulation.
11
 */
12
trait PrivilegesTrait
13
{
14
    /**
15
     * Grabs an array of users and their privileges for an object,
16
     * given its type.
17
     *
18
     * @param string      $object The name of the object whose privileges are to be retrieved
19
     * @param string      $type   The type of the object (eg. database, schema, relation, function or language)
20
     * @param null|string $table  Optional, column's table if type = column
21
     *
22
     * @return array|int Privileges array or error code
23
     *                   - -1         invalid type
24
     *                   - -2         object not found
25
     *                   - -3         unknown privilege type
26
     */
27
    public function getPrivileges($object, $type, $table = null)
28
    {
29
        $c_schema = $this->_schema;
30
        $this->clean($c_schema);
31
        $this->clean($object);
32
33
        switch ($type) {
34
            case 'column':
35
                $this->clean($table);
36
                $sql = "
37
                    SELECT E'{' || pg_catalog.array_to_string(attacl, E',') || E'}' as acl
38
                    FROM pg_catalog.pg_attribute a
39
                        LEFT JOIN pg_catalog.pg_class c ON (a.attrelid = c.oid)
40
                        LEFT JOIN pg_catalog.pg_namespace n ON (c.relnamespace=n.oid)
41
                    WHERE n.nspname='{$c_schema}'
42
                        AND c.relname='{$table}'
43
                        AND a.attname='{$object}'";
44
45
                break;
46
            case 'table':
47
            case 'view':
48
            case 'sequence':
49
                $sql = "
50
                    SELECT relacl AS acl FROM pg_catalog.pg_class
51
                    WHERE relname='{$object}'
52
                        AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace
53
                            WHERE nspname='{$c_schema}')";
54
55
                break;
56
            case 'database':
57
                $sql = "SELECT datacl AS acl FROM pg_catalog.pg_database WHERE datname='{$object}'";
58
59
                break;
60
            case 'function':
61
                // Since we fetch functions by oid, they are already constrained to
62
                // the current schema.
63
                $sql = "SELECT proacl AS acl FROM pg_catalog.pg_proc WHERE oid='{$object}'";
64
65
                break;
66
            case 'language':
67
                $sql = "SELECT lanacl AS acl FROM pg_catalog.pg_language WHERE lanname='{$object}'";
68
69
                break;
70
            case 'schema':
71
                $sql = "SELECT nspacl AS acl FROM pg_catalog.pg_namespace WHERE nspname='{$object}'";
72
73
                break;
74
            case 'tablespace':
75
                $sql = "SELECT spcacl AS acl FROM pg_catalog.pg_tablespace WHERE spcname='{$object}'";
76
77
                break;
78
79
            default:
80
                return -1;
81
        }
82
83
        // Fetch the ACL for object
84
        $acl = $this->selectField($sql, 'acl');
85
86
        if (-1 === $acl) {
87
            return -2;
88
        }
89
90
        if ('' === $acl || null === $acl || !(bool) $acl) {
91
            return [];
92
        }
93
94
        return $this->parseACL($acl);
95
    }
96
97
    /**
98
     * Grants a privilege to a user, group or public.
99
     *
100
     * @param string $mode        'GRANT' or 'REVOKE';
101
     * @param mixed  $type        The type of object
102
     * @param string $object      The name of the object
103
     * @param bool   $public      True to grant to public, false otherwise
104
     * @param mixed  $usernames   the array of usernames to grant privs to
105
     * @param mixed  $groupnames  the array of group names to grant privs to
106
     * @param mixed  $privileges  The array of privileges to grant (eg. ('SELECT', 'ALL PRIVILEGES', etc.) )
107
     * @param bool   $grantoption True if has grant option, false otherwise
108
     * @param bool   $cascade     True for cascade revoke, false otherwise
109
     * @param string $table       the column's table if type=column
110
     *
111
     * @return int|\PHPPgAdmin\ADORecordSet
112
     */
113
    public function setPrivileges(
114
        $mode,
115
        $type,
116
        $object,
117
        $public,
118
        $usernames,
119
        $groupnames,
120
        $privileges,
121
        $grantoption,
122
        $cascade,
123
        $table
124
    ) {
125
        $f_schema = $this->_schema;
126
        $this->fieldClean($f_schema);
127
        $this->fieldArrayClean($usernames);
128
        $this->fieldArrayClean($groupnames);
129
130
        // Input checking
131
        if (!\is_array($privileges) || 0 === \count($privileges)) {
132
            return -3;
133
        }
134
135
        if (!\is_array($usernames) || !\is_array($groupnames) ||
136
            (!$public && 0 === \count($usernames) && 0 === \count($groupnames))) {
137
            return -4;
138
        }
139
140
        if ('GRANT' !== $mode && 'REVOKE' !== $mode) {
141
            return -5;
142
        }
143
144
        $sql = $mode;
145
146
        // Grant option
147
        if ($this->hasGrantOption() && 'REVOKE' === $mode && $grantoption) {
148
            $sql .= ' GRANT OPTION FOR';
149
        }
150
151
        if (\in_array('ALL PRIVILEGES', $privileges, true)) {
152
            $sql .= ' ALL PRIVILEGES';
153
        } else {
154
            if ('column' === $type) {
155
                $this->fieldClean($object);
156
                $sql .= ' ' . \implode(" (\"{$object}\"), ", $privileges);
157
            } else {
158
                $sql .= ' ' . \implode(', ', $privileges);
159
            }
160
        }
161
162
        switch ($type) {
163
            case 'column':
164
                $sql .= " (\"{$object}\")";
165
                $object = $table;
166
            // no break
167
            case 'table':
168
            case 'view':
169
            case 'sequence':
170
                $this->fieldClean($object);
171
                $sql .= " ON \"{$f_schema}\".\"{$object}\"";
172
173
                break;
174
            case 'database':
175
                $this->fieldClean($object);
176
                $sql .= " ON DATABASE \"{$object}\"";
177
178
                break;
179
            case 'function':
180
                // Function comes in with $object as function OID
181
                $fn = $this->getFunction($object);
182
                $this->fieldClean($fn->fields['proname']);
183
                $sql .= " ON FUNCTION \"{$f_schema}\".\"{$fn->fields['proname']}\"({$fn->fields['proarguments']})";
184
185
                break;
186
            case 'language':
187
                $this->fieldClean($object);
188
                $sql .= " ON LANGUAGE \"{$object}\"";
189
190
                break;
191
            case 'schema':
192
                $this->fieldClean($object);
193
                $sql .= " ON SCHEMA \"{$object}\"";
194
195
                break;
196
            case 'tablespace':
197
                $this->fieldClean($object);
198
                $sql .= " ON TABLESPACE \"{$object}\"";
199
200
                break;
201
202
            default:
203
                return -1;
204
        }
205
206
        // Dump
207
        $first = true;
208
        $sql .= ('GRANT' === $mode) ? ' TO ' : ' FROM ';
209
210
        if ($public) {
211
            $sql .= 'PUBLIC';
212
            $first = false;
213
        }
214
        // Dump users
215
        foreach ($usernames as $v) {
216
            if ($first) {
217
                $sql .= "\"{$v}\"";
218
                $first = false;
219
            } else {
220
                $sql .= ", \"{$v}\"";
221
            }
222
        }
223
        // Dump groups
224
        foreach ($groupnames as $v) {
225
            if ($first) {
226
                $sql .= "GROUP \"{$v}\"";
227
                $first = false;
228
            } else {
229
                $sql .= ", GROUP \"{$v}\"";
230
            }
231
        }
232
233
        // Grant option
234
        if ($this->hasGrantOption() && 'GRANT' === $mode && $grantoption) {
235
            $sql .= ' WITH GRANT OPTION';
236
        }
237
238
        // Cascade revoke
239
        if ($this->hasGrantOption() && 'REVOKE' === $mode && $cascade) {
240
            $sql .= ' CASCADE';
241
        }
242
243
        return $this->execute($sql);
244
    }
245
246
    abstract public function fieldClean(&$str);
247
248
    abstract public function beginTransaction();
249
250
    abstract public function rollbackTransaction();
251
252
    abstract public function endTransaction();
253
254
    abstract public function execute($sql);
255
256
    abstract public function setComment($obj_type, $obj_name, $table, $comment, $basetype = null);
257
258
    abstract public function selectSet($sql);
259
260
    abstract public function clean(&$str);
261
262
    abstract public function hasGrantOption();
263
264
    abstract public function getFunction($function_oid);
265
266
    abstract public function fieldArrayClean(&$arr);
267
268
    abstract public function selectField($sql, $field);
269
270
    abstract public function hasRoles();
271
272
    /**
273
     * Internal function used for parsing ACLs.
274
     *
275
     * @param string $acl The ACL to parse (of type aclitem[])
276
     *
277
     * @return array|int Privileges array or integer with error code
278
     *
279
     * @internal bool $in_quotes toggles acl in_quotes attribute
280
     */
281
    protected function parseACL($acl)
282
    {
283
        // Take off the first and last characters (the braces)
284
        $acl = \mb_substr($acl, 1, \mb_strlen($acl) - 2);
285
286
        // Pick out individual ACE's by carefully parsing.  This is necessary in order
287
        // to cope with usernames and stuff that contain commas
288
        $aces = [];
289
        $i = $j = 0;
290
        $in_quotes = false;
291
292
        while (\mb_strlen($acl) > $i) {
293
            // If current char is a double quote and it's not escaped, then
294
            // enter quoted bit
295
            $char = \mb_substr($acl, $i, 1);
296
297
            if ('"' === $char && (0 === $i || '\\' !== \mb_substr($acl, $i - 1, 1))) {
298
                $in_quotes = !$in_quotes;
299
            } elseif (',' === $char && !$in_quotes) {
300
                // Add text so far to the array
301
                $aces[] = \mb_substr($acl, $j, $i - $j);
302
                $j = $i + 1;
303
            }
304
            ++$i;
305
        }
306
        // Add final text to the array
307
        $aces[] = \mb_substr($acl, $j);
308
309
        // Create the array to be returned
310
        $temp = [];
311
312
        // For each ACE, generate an entry in $temp
313
        foreach ($aces as $v) {
314
            // If the ACE begins with a double quote, strip them off both ends
315
            // and unescape backslashes and double quotes
316
            // $unquote = false;
317
            if (0 === \mb_strpos($v, '"')) {
318
                $v = \mb_substr($v, 1, \mb_strlen($v) - 2);
319
                $v = \str_replace('\\"', '"', $v);
320
                $v = \str_replace('\\\\', '\\', $v);
321
            }
322
323
            // Figure out type of ACE (public, user or group)
324
            if (0 === \mb_strpos($v, '=')) {
325
                $atype = 'public';
326
            } else {
327
                if ($this->hasRoles()) {
328
                    $atype = 'role';
329
                } else {
330
                    if (0 === \mb_strpos($v, 'group ')) {
331
                        $atype = 'group';
332
                        // Tear off 'group' prefix
333
                        $v = \mb_substr($v, 6);
334
                    } else {
335
                        $atype = 'user';
336
                    }
337
                }
338
            }
339
340
            // Break on unquoted equals sign...
341
            $i = 0;
342
            $in_quotes = false;
343
            $entity = null;
344
            $chars = null;
345
346
            while (\mb_strlen($v) > $i) {
347
                // If current char is a double quote and it's not escaped, then
348
                // enter quoted bit
349
                $char = \mb_substr($v, $i, 1);
350
                $next_char = \mb_substr($v, $i + 1, 1);
351
352
                if ('"' === $char && (0 === $i || '"' !== $next_char)) {
353
                    $in_quotes = !$in_quotes;
354
                } elseif ('"' === $char && '"' === $next_char) {
355
                    // Skip over escaped double quotes
356
                    ++$i;
357
                } elseif ('=' === $char && !$in_quotes) {
358
                    // Split on current equals sign
359
                    $entity = \mb_substr($v, 0, $i);
360
                    $chars = \mb_substr($v, $i + 1);
361
362
                    break;
363
                }
364
                ++$i;
365
            }
366
367
            // Check for quoting on entity name, and unescape if necessary
368
            if (0 === \mb_strpos($entity, '"')) {
369
                $entity = \mb_substr($entity, 1, \mb_strlen($entity) - 2);
370
                $entity = \str_replace('""', '"', $entity);
371
            }
372
373
            // New row to be added to $temp
374
            // (type, grantee, privileges, grantor, grant option?
375
            $row = [$atype, $entity, [], '', []];
376
377
            // Loop over chars and add privs to $row
378
            for ($i = 0; \mb_strlen($chars) > $i; ++$i) {
379
                // Append to row's privs list the string representing
380
                // the privilege
381
                $char = \mb_substr($chars, $i, 1);
382
383
                if ('*' === $char) {
384
                    $row[4][] = $this->privmap[\mb_substr($chars, $i - 1, 1)];
385
                } elseif ('/' === $char) {
386
                    $grantor = \mb_substr($chars, $i + 1);
387
                    // Check for quoting
388
                    if (0 === \mb_strpos($grantor, '"')) {
389
                        $grantor = \mb_substr($grantor, 1, \mb_strlen($grantor) - 2);
390
                        $grantor = \str_replace('""', '"', $grantor);
391
                    }
392
                    $row[3] = $grantor;
393
394
                    break;
395
                } else {
396
                    if (!isset($this->privmap[$char])) {
397
                        return -3;
398
                    }
399
400
                    $row[2][] = $this->privmap[$char];
401
                }
402
            }
403
404
            // Append row to temp
405
            $temp[] = $row;
406
        }
407
408
        return $temp;
409
    }
410
}
411