Issues (3627)

Security/Permissions/AbstractPermissions.php (2 issues)

1
<?php
2
3
/*
4
 * @copyright   2014 Mautic Contributors. All rights reserved
5
 * @author      Mautic
6
 *
7
 * @link        http://mautic.org
8
 *
9
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
10
 */
11
12
namespace Mautic\CoreBundle\Security\Permissions;
13
14
use Mautic\UserBundle\Form\Type\PermissionListType;
15
use Symfony\Component\Form\FormBuilderInterface;
16
17
/**
18
 * Class AbstractPermissions.
19
 */
20
abstract class AbstractPermissions
21
{
22
    /**
23
     * @var array
24
     */
25
    protected $permissions = [];
26
27
    /**
28
     * @var array
29
     */
30
    protected $params = [];
31
32
    public function __construct(array $params)
33
    {
34
        $this->params = $params;
35
    }
36
37
    /**
38
     * Returns bundle's permissions array.
39
     *
40
     * @return array
41
     */
42
    public function getPermissions()
43
    {
44
        return $this->permissions;
45
    }
46
47
    /**
48
     * Checks to see if the requested permission is supported by the bundle.
49
     *
50
     * @param string $name
51
     * @param string $level
52
     *
53
     * @return bool
54
     */
55
    public function isSupported($name, $level = '')
56
    {
57
        list($name, $level) = $this->getSynonym($name, $level);
58
59
        if (empty($level)) {
60
            //verify permission name only
61
            return isset($this->permissions[$name]);
62
        }
63
64
        //verify permission name and level as well
65
        return isset($this->permissions[$name][$level]);
66
    }
67
68
    /**
69
     * Allows permission classes to be disabled if criteria is not met (such as bundle is disabled).
70
     *
71
     * @return bool
72
     */
73
    public function isEnabled()
74
    {
75
        return true;
76
    }
77
78
    /**
79
     * Returns the value assigned to a specific permission.
80
     *
81
     * @param string $name
82
     * @param string $perm
83
     *
84
     * @return int
85
     */
86
    public function getValue($name, $perm)
87
    {
88
        return ($this->isSupported($name, $perm)) ? $this->permissions[$name][$perm] : 0;
89
    }
90
91
    /**
92
     * Builds the bundle's specific form elements for its permissions.
93
     */
94
    public function buildForm(FormBuilderInterface &$builder, array $options, array $data)
0 ignored issues
show
The parameter $options is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

94
    public function buildForm(FormBuilderInterface &$builder, /** @scrutinizer ignore-unused */ array $options, array $data)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
95
    {
96
    }
97
98
    /**
99
     * Returns the name of the permission set (should be the bundle identifier).
100
     *
101
     * @return string|void
102
     */
103
    abstract public function getName();
104
105
    /**
106
     * Takes an array from PermissionRepository::getPermissionsByRole() and converts the bitwise integers to an array
107
     * of permission names that can be used in forms, for example.
108
     *
109
     * @return mixed
110
     */
111
    public function convertBitsToPermissionNames(array $permissions)
112
    {
113
        static $permissionLevels = [];
114
        $bundle                  = $this->getName();
115
116
        if (!in_array($bundle, $permissionLevels)) {
117
            $permissionLevels[$bundle] = [];
118
            if (isset($permissions[$bundle])) {
119
                if ($this->isEnabled()) {
120
                    foreach ($permissions[$bundle] as $details) {
121
                        $permName    = $details['name'];
122
                        $permBitwise = $details['bitwise'];
123
                        //ensure the permission still exists
124
                        if ($this->isSupported($permName)) {
125
                            $levels = $this->permissions[$permName];
126
                            //ensure that at least keys exist
127
                            $permissionLevels[$bundle][$permName] = [];
128
                            //$permissionLevels[$bundle][$permName]["$bundle:$permName"] = $permId;
129
                            foreach ($levels as $levelName => $levelBit) {
130
                                //compare bit against levels to see if it is a match
131
                                if ($levelBit & $permBitwise) {
132
                                    //bitwise compares so add the level
133
                                    $permissionLevels[$bundle][$permName][] = $levelName;
134
                                    continue;
135
                                }
136
                            }
137
                        }
138
                    }
139
                }
140
            }
141
        }
142
143
        return $permissionLevels[$bundle];
144
    }
145
146
    /**
147
     * Allows the bundle permission class to utilize synonyms for permissions.
148
     *
149
     * @param string $name
150
     * @param string $level
151
     *
152
     * @return array
153
     */
154
    protected function getSynonym($name, $level)
155
    {
156
        if (in_array($level, ['viewown', 'viewother'])) {
157
            if (isset($this->permissions[$name]['view'])) {
158
                $level = 'view';
159
            }
160
        } elseif ('view' == $level) {
161
            if (isset($this->permissions[$name]['viewown'])) {
162
                $level = 'viewown';
163
            }
164
        } elseif (in_array($level, ['editown', 'editother'])) {
165
            if (isset($this->permissions[$name]['edit'])) {
166
                $level = 'edit';
167
            }
168
        } elseif ('edit' == $level) {
169
            if (isset($this->permissions[$name]['editown'])) {
170
                $level = 'editown';
171
            }
172
        } elseif (in_array($level, ['deleteown', 'deleteother'])) {
173
            if (isset($this->permissions[$name]['delete'])) {
174
                $level = 'delete';
175
            }
176
        } elseif ('delete' == $level) {
177
            if (isset($this->permissions[$name]['deleteown'])) {
178
                $level = 'deleteown';
179
            }
180
        } elseif (in_array($level, ['publishown', 'publishother'])) {
181
            if (isset($this->permissions[$name]['publish'])) {
182
                $level = 'publish';
183
            }
184
        } elseif ('publish' == $level) {
185
            if (isset($this->permissions[$name]['publishown'])) {
186
                $level = 'publishown';
187
            }
188
        }
189
190
        return [$name, $level];
191
    }
192
193
    /**
194
     * Determines if the user has access to the specified permission.
195
     *
196
     * @param array  $userPermissions
197
     * @param string $name
198
     * @param string $level
199
     *
200
     * @return bool
201
     */
202
    public function isGranted($userPermissions, $name, $level)
203
    {
204
        list($name, $level) = $this->getSynonym($name, $level);
205
206
        if (!isset($userPermissions[$name])) {
207
            //the user doesn't have implicit access
208
            return false;
209
        } elseif ($this->permissions[$name]['full'] & $userPermissions[$name]) {
210
            return true;
211
        } else {
212
            //otherwise test for specific level
213
            $result = ($this->permissions[$name][$level] & $userPermissions[$name]);
214
215
            return ($result) ? true : false;
216
        }
217
    }
218
219
    /**
220
     * @param      $allPermissions
221
     * @param bool $isSecondRound
222
     *
223
     * @return bool Return true if a second round is required after all other bundles have analyzed it's permissions
224
     */
225
    public function analyzePermissions(array &$permissions, $allPermissions, $isSecondRound = false)
226
    {
227
        $hasViewAccess = false;
228
        foreach ($permissions as $level => &$perms) {
229
            foreach ($perms as $perm) {
230
                $required = [];
231
                switch ($perm) {
232
                    case 'editother':
233
                    case 'edit':
234
                        $required = ['viewother', 'viewown'];
235
                        break;
236
                    case 'deleteother':
237
                    case 'delete':
238
                        $required = ['editother', 'viewother', 'viewown'];
239
                        break;
240
                    case 'publishother':
241
                    case 'publish':
242
                        $required = ['viewother', 'viewown'];
243
                        break;
244
                    case 'viewother':
245
                    case 'editown':
246
                    case 'deleteown':
247
                    case 'publishown':
248
                    case 'create':
249
                        $required = ['viewown'];
250
                        break;
251
                }
252
                if (!empty($required)) {
253
                    foreach ($required as $r) {
254
                        list($ignore, $r) = $this->getSynonym($level, $r);
255
                        if ($this->isSupported($level, $r) && !in_array($r, $perms)) {
256
                            $perms[] = $r;
257
                        }
258
                    }
259
                }
260
            }
261
            $hasViewAccess = (!$hasViewAccess && (in_array('view', $perms) || in_array('viewown', $perms)));
262
        }
263
264
        //check categories for view permissions and add it if the user has view access to the other permissions
265
        if (isset($this->permissions['categories']) && $hasViewAccess && (!isset($permissions['categories']) || !in_array('view', $permissions['categories']))) {
266
            $permissions['categories'][] = 'view';
267
        }
268
269
        return false;
270
    }
271
272
    /**
273
     * Generates an array of granted and total permissions.
274
     *
275
     * @return array
276
     */
277
    public function getPermissionRatio(array $data)
278
    {
279
        $totalAvailable = $totalGranted = 0;
280
281
        foreach ($this->permissions as $level => $perms) {
282
            $perms = array_keys($perms);
283
            $totalAvailable += count($perms);
284
285
            if (in_array('full', $perms)) {
286
                if (1 === count($perms)) {
287
                    //full is the only permission so count as 1
288
                    if (!empty($data[$level]) && in_array('full', $data[$level])) {
289
                        ++$totalGranted;
290
                    }
291
                } else {
292
                    //remove full from total count
293
                    --$totalAvailable;
294
                    if (!empty($data[$level]) && in_array('full', $data[$level])) {
295
                        //user has full access so sum perms minus full
296
                        $totalGranted += count($perms) - 1;
297
                        //move on to the next level
298
                        continue;
299
                    }
300
                }
301
            }
302
303
            if (isset($data[$level])) {
304
                $totalGranted += count($data[$level]);
305
            }
306
        }
307
308
        return [$totalGranted, $totalAvailable];
309
    }
310
311
    /**
312
     * Gives the bundle an opportunity to change how JavaScript calculates permissions granted.
313
     */
314
    public function parseForJavascript(array &$perms)
0 ignored issues
show
The parameter $perms is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

314
    public function parseForJavascript(/** @scrutinizer ignore-unused */ array &$perms)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
315
    {
316
    }
317
318
    /**
319
     * Adds the standard permission set of view, edit, create, delete, publish and full.
320
     *
321
     * @param array $permissionNames
322
     * @param bool  $includePublish
323
     */
324
    protected function addStandardPermissions($permissionNames, $includePublish = true)
325
    {
326
        if (!is_array($permissionNames)) {
327
            $permissionNames = [$permissionNames];
328
        }
329
330
        foreach ($permissionNames as $p) {
331
            $this->permissions[$p] = [
332
                'view'   => 4,
333
                'edit'   => 16,
334
                'create' => 32,
335
                'delete' => 128,
336
                'full'   => 1024,
337
            ];
338
            if ($includePublish) {
339
                $this->permissions[$p]['publish'] = 512;
340
            }
341
        }
342
    }
343
344
    /**
345
     * Adds the standard permission set of view, edit, create, delete, publish and full to the form builder.
346
     *
347
     * @param string               $bundle
348
     * @param string               $level
349
     * @param FormBuilderInterface $builder
350
     * @param array                $data
351
     * @param bool                 $includePublish
352
     */
353
    protected function addStandardFormFields($bundle, $level, &$builder, $data, $includePublish = true)
354
    {
355
        $choices = [
356
            'mautic.core.permissions.view'   => 'view',
357
            'mautic.core.permissions.edit'   => 'edit',
358
            'mautic.core.permissions.create' => 'create',
359
            'mautic.core.permissions.delete' => 'delete',
360
        ];
361
362
        if ($includePublish) {
363
            $choices['mautic.core.permissions.publish'] = 'publish';
364
        }
365
366
        $choices['mautic.core.permissions.full'] = 'full';
367
368
        $label = ('categories' == $level) ? 'mautic.category.permissions.categories' : "mautic.$bundle.permissions.$level";
369
        $builder->add(
370
            "$bundle:$level",
371
            PermissionListType::class,
372
            [
373
                'choices'           => $choices,
374
                'label'             => $label,
375
                'bundle'            => $bundle,
376
                'level'             => $level,
377
                'data'              => (!empty($data[$level]) ? $data[$level] : []),
378
            ]
379
        );
380
    }
381
382
    /**
383
     * Add a single full permission.
384
     *
385
     * @param array $permissionNames
386
     */
387
    protected function addManagePermission($permissionNames)
388
    {
389
        if (!is_array($permissionNames)) {
390
            $permissionNames = [$permissionNames];
391
        }
392
393
        foreach ($permissionNames as $p) {
394
            $this->permissions[$p] = [
395
                'manage' => 1024,
396
            ];
397
        }
398
    }
399
400
    /**
401
     * Adds a single full permission to the form builder, i.e. config only bundles.
402
     *
403
     * @param string               $bundle
404
     * @param string               $level
405
     * @param FormBuilderInterface $builder
406
     * @param array                $data
407
     */
408
    protected function addManageFormFields($bundle, $level, &$builder, $data)
409
    {
410
        $choices = [
411
            'mautic.core.permissions.manage' => 'manage',
412
        ];
413
414
        $builder->add(
415
            "$bundle:$level",
416
            PermissionListType::class,
417
            [
418
                'choices'           => $choices,
419
                'label'             => "mautic.$bundle.permissions.$level",
420
                'data'              => (!empty($data[$level]) ? $data[$level] : []),
421
                'bundle'            => $bundle,
422
                'level'             => $level,
423
            ]
424
        );
425
    }
426
427
    /**
428
     * Adds the standard permission set of viewown, viewother, editown, editother, create, deleteown, deleteother,
429
     * publishown, publishother and full.
430
     *
431
     * @param array $permissionNames
432
     * @param bool  $includePublish
433
     */
434
    protected function addExtendedPermissions($permissionNames, $includePublish = true)
435
    {
436
        if (!is_array($permissionNames)) {
437
            $permissionNames = [$permissionNames];
438
        }
439
440
        foreach ($permissionNames as $p) {
441
            $this->permissions[$p] = [
442
                'viewown'     => 2,
443
                'viewother'   => 4,
444
                'editown'     => 8,
445
                'editother'   => 16,
446
                'create'      => 32,
447
                'deleteown'   => 64,
448
                'deleteother' => 128,
449
                'full'        => 1024,
450
            ];
451
            if ($includePublish) {
452
                $this->permissions[$p]['publishown']   = 256;
453
                $this->permissions[$p]['publishother'] = 512;
454
            }
455
        }
456
    }
457
458
    /**
459
     * Adds the standard permission set of viewown, viewother, editown, editother, create, deleteown, deleteother,
460
     * publishown, publishother and full to the form builder.
461
     *
462
     * @param string               $bundle
463
     * @param string               $level
464
     * @param FormBuilderInterface $builder
465
     * @param array                $data
466
     * @param bool                 $includePublish
467
     */
468
    protected function addExtendedFormFields($bundle, $level, &$builder, $data, $includePublish = true)
469
    {
470
        $choices = [
471
            'mautic.core.permissions.viewown'     => 'viewown',
472
            'mautic.core.permissions.viewother'   => 'viewother',
473
            'mautic.core.permissions.editown'     => 'editown',
474
            'mautic.core.permissions.editother'   => 'editother',
475
            'mautic.core.permissions.create'      => 'create',
476
            'mautic.core.permissions.deleteown'   => 'deleteown',
477
            'mautic.core.permissions.deleteother' => 'deleteother',
478
            'mautic.core.permissions.full'        => 'full',
479
        ];
480
481
        if ($includePublish) {
482
            $choices['mautic.core.permissions.publishown']   = 'publishown';
483
            $choices['mautic.core.permissions.publishother'] = 'publishother';
484
        }
485
486
        $builder->add(
487
            "$bundle:$level",
488
            PermissionListType::class,
489
            [
490
                'choices'           => $choices,
491
                'label'             => "mautic.$bundle.permissions.$level",
492
                'data'              => (!empty($data[$level]) ? $data[$level] : []),
493
                'bundle'            => $bundle,
494
                'level'             => $level,
495
            ]
496
        );
497
    }
498
}
499