Issues (3627)

Security/Permissions/CorePermissions.php (1 issue)

1
<?php
2
3
/*
4
 * @copyright   2016 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\CoreBundle\Helper\CoreParametersHelper;
15
use Mautic\CoreBundle\Helper\UserHelper;
16
use Mautic\CoreBundle\Security\Exception\PermissionBadFormatException;
17
use Mautic\CoreBundle\Security\Exception\PermissionNotFoundException;
18
use Mautic\UserBundle\Entity\Permission;
19
use Mautic\UserBundle\Entity\User;
20
use Symfony\Component\Translation\Translator;
21
use Symfony\Component\Translation\TranslatorInterface;
22
23
/**
24
 * Class Security.
25
 */
26
class CorePermissions
27
{
28
    /**
29
     * @var Translator
30
     */
31
    private $translator;
32
33
    /**
34
     * @var UserHelper
35
     */
36
    protected $userHelper;
37
38
    /**
39
     * @var CoreParametersHelper
40
     */
41
    private $coreParametersHelper;
42
43
    /**
44
     * @var array
45
     */
46
    private $bundles;
47
48
    /**
49
     * @var array
50
     */
51
    private $pluginBundles;
52
53
    /**
54
     * @var array
55
     */
56
    private $permissionClasses = [];
57
58
    /**
59
     * @var array
60
     */
61
    private $permissionObjects = [];
62
63
    /**
64
     * @var array
65
     */
66
    private $grantedPermissions = [];
67
68
    /**
69
     * @var array
70
     */
71
    private $checkedPermissions = [];
72
73
    public function __construct(
74
        UserHelper $userHelper,
75
        TranslatorInterface $translator,
76
        CoreParametersHelper $coreParametersHelper,
77
        array $bundles,
78
        array $pluginBundles
79
    ) {
80
        $this->userHelper           = $userHelper;
81
        $this->translator           = $translator;
82
        $this->coreParametersHelper = $coreParametersHelper;
83
        $this->bundles              = $bundles;
84
        $this->pluginBundles        = $pluginBundles;
85
86
        $this->registerPermissionClasses();
87
    }
88
89
    /**
90
     * Retrieves each bundles permission objects.
91
     *
92
     * @return array
93
     */
94
    public function getPermissionObjects()
95
    {
96
        $objects = [];
97
        foreach ($this->permissionClasses as $key => $class) {
98
            if ($object = $this->getPermissionObject($key, false)) {
99
                $objects[] = $object;
100
            }
101
        }
102
103
        return $objects;
104
    }
105
106
    /**
107
     * Returns the bundles permission class object.
108
     *
109
     * @param string $bundle
110
     * @param bool   $throwException
111
     *
112
     * @return mixed
113
     *
114
     * @throws \InvalidArgumentException
115
     */
116
    public function getPermissionObject($bundle, $throwException = true)
117
    {
118
        if (!empty($bundle)) {
119
            if (isset($this->permissionClasses[$bundle])) {
120
                if (empty($this->permissionObjects[$bundle])) {
121
                    $permissionClass                  = $this->permissionClasses[$bundle];
122
                    $this->permissionObjects[$bundle] = new $permissionClass($this->getParams());
123
                }
124
            } else {
125
                if ($throwException) {
126
                    throw new \InvalidArgumentException("Permission class not found for {$bundle} in permissions classes");
127
                }
128
129
                return false;
130
            }
131
132
            return $this->permissionObjects[$bundle];
133
        }
134
135
        throw new \InvalidArgumentException("Bundle and permission type must be specified. '$bundle' given.");
136
    }
137
138
    /**
139
     * Generates the bit value for the bundle's permission.
140
     *
141
     * @return array
142
     *
143
     * @throws \InvalidArgumentException
144
     */
145
    public function generatePermissions(array $permissions)
146
    {
147
        $entities = [];
148
149
        //give bundles an opportunity to analyze and adjust permissions based on others
150
        $classes = $this->getPermissionObjects();
151
152
        //bust out permissions into their respective bundles
153
        $bundlePermissions = [];
154
        foreach ($permissions as $permission => $perms) {
155
            [$bundle, $level]                   = explode(':', $permission);
156
            $bundlePermissions[$bundle][$level] = $perms;
157
        }
158
159
        $bundles = array_keys($classes);
160
161
        foreach ($bundles as $bundle) {
162
            if (!isset($bundlePermissions[$bundle])) {
163
                $bundlePermissions[$bundle] = [];
164
            }
165
        }
166
167
        //do a first round to give bundles a chance to update everything and give an opportunity to require a second round
168
        //if the permission it is looking for from another bundle is not configured yet
169
        $secondRound = [];
170
        foreach ($classes as $bundle => $class) {
171
            $needsRoundTwo = $class->analyzePermissions($bundlePermissions[$bundle], $bundlePermissions);
172
            if ($needsRoundTwo) {
173
                $secondRound[] = $bundle;
174
            }
175
        }
176
177
        foreach ($secondRound as $bundle) {
178
            $classes[$bundle]->analyzePermissions($bundlePermissions[$bundle], $bundlePermissions, true);
179
        }
180
181
        //create entities
182
        foreach ($bundlePermissions as $bundle => $permissions) {
183
            foreach ($permissions as $name => $perms) {
184
                $entity = new Permission();
185
                $entity->setBundle($bundle);
186
                $entity->setName($name);
187
188
                $bit   = 0;
189
                $class = $this->getPermissionObject($bundle);
190
191
                foreach ($perms as $perm) {
192
                    //get the bit for the perm
193
                    if (!$class->isSupported($name, $perm)) {
194
                        throw new \InvalidArgumentException("$perm does not exist for $bundle:$name");
195
                    }
196
197
                    $bit += $class->getValue($name, $perm);
198
                }
199
                $entity->setBitwise($bit);
200
                $entities[] = $entity;
201
            }
202
        }
203
204
        return $entities;
205
    }
206
207
    /**
208
     * @return bool
209
     */
210
    public function isAdmin()
211
    {
212
        return $this->userHelper->getUser()->isAdmin();
213
    }
214
215
    /**
216
     * Determines if the user has permission to access the given area.
217
     *
218
     * @param array|string $requestedPermission
219
     * @param string       $mode                MATCH_ALL|MATCH_ONE|RETURN_ARRAY
220
     * @param User         $userEntity
221
     * @param bool         $allowUnknown        If the permission is not recognized, false will be returned.  Otherwise an
222
     *                                          exception will be thrown
223
     *
224
     * @return mixed
225
     *
226
     * @throws \InvalidArgumentException
227
     */
228
    public function isGranted($requestedPermission, $mode = 'MATCH_ALL', $userEntity = null, $allowUnknown = false)
229
    {
230
        if (null === $userEntity) {
231
            $userEntity = $this->userHelper->getUser();
232
        }
233
234
        if (!is_array($requestedPermission)) {
235
            $requestedPermission = [$requestedPermission];
236
        }
237
238
        $permissions = [];
239
        foreach ($requestedPermission as $permission) {
240
            if (isset($this->grantedPermissions[$permission])) {
241
                $permissions[$permission] = $this->grantedPermissions[$permission];
242
                continue;
243
            }
244
245
            $parts = explode(':', $permission);
246
            if (false === in_array(count($parts), [3, 4])) {
247
                throw new PermissionBadFormatException($this->getTranslator()->trans('mautic.core.permissions.badformat', ['%permission%' => $permission]));
248
            }
249
250
            if ($userEntity->isAdmin()) {
251
                //admin user has access to everything
252
                $permissions[$permission] = true;
253
            } else {
254
                $activePermissions = ($userEntity instanceof User) ? $userEntity->getActivePermissions() : [];
255
256
                //check against bundle permissions class
257
                $permissionObject = $this->getPermissionObject($parts[0]);
258
259
                //Is the permission supported?
260
                if (!$permissionObject->isSupported($parts[1], $parts[2])) {
261
                    if ($allowUnknown) {
262
                        $permissions[$permission] = false;
263
                    } else {
264
                        throw new PermissionNotFoundException($this->getTranslator()->trans('mautic.core.permissions.notfound', ['%permission%' => $permission]));
265
                    }
266
                } elseif ('anon.' == $userEntity) {
267
                    //anon user or session timeout
268
                    $permissions[$permission] = false;
269
                } elseif (!isset($activePermissions[$parts[0]])) {
270
                    //user does not have implicit access to bundle so deny
271
                    $permissions[$permission] = false;
272
                } else {
273
                    $permissions[$permission] = $permissionObject->isGranted($activePermissions[$parts[0]], $parts[1], $parts[2]);
274
                }
275
            }
276
277
            $this->grantedPermissions[$permission] = $permissions[$permission];
278
        }
279
280
        if ('MATCH_ALL' == $mode) {
281
            //deny if any of the permissions are denied
282
            return in_array(0, $permissions) ? false : true;
283
        } elseif ('MATCH_ONE' == $mode) {
284
            //grant if any of the permissions were granted
285
            return in_array(1, $permissions) ? true : false;
286
        } elseif ('RETURN_ARRAY' == $mode) {
287
            return $permissions;
288
        } else {
289
            throw new PermissionNotFoundException($this->getTranslator()->trans('mautic.core.permissions.mode.notfound', ['%mode%' => $mode]));
290
        }
291
    }
292
293
    /**
294
     * Check if a permission or array of permissions exist.
295
     *
296
     * @param array|string $permission
297
     *
298
     * @return bool
299
     */
300
    public function checkPermissionExists($permission)
301
    {
302
        $checkPermissions = (!is_array($permission)) ? [$permission] : $permission;
303
304
        $result = [];
305
        foreach ($checkPermissions as $p) {
306
            if (isset($this->checkedPermissions[$p])) {
307
                $result[$p] = $this->checkedPermissions[$p];
308
                continue;
309
            }
310
311
            $parts = explode(':', $p);
312
            if (3 != count($parts)) {
313
                $result[$p] = false;
314
            } else {
315
                //check against bundle permissions class
316
                $permissionObject = $this->getPermissionObject($parts[0], false);
317
                $result[$p]       = $permissionObject && $permissionObject->isSupported($parts[1], $parts[2]);
318
            }
319
        }
320
321
        return (is_array($permission)) ? $result : $result[$permission];
0 ignored issues
show
Bug Best Practice introduced by
The expression return is_array($permiss... : $result[$permission] also could return the type array which is incompatible with the documented return type boolean.
Loading history...
322
    }
323
324
    /**
325
     * Checks if the user has access to the requested entity.
326
     *
327
     * @param string|bool $ownPermission
328
     * @param string|bool $otherPermission
329
     * @param User|int    $ownerId
330
     *
331
     * @return bool
332
     */
333
    public function hasEntityAccess($ownPermission, $otherPermission, $ownerId = 0)
334
    {
335
        $user = $this->userHelper->getUser();
336
        if (!is_object($user)) {
337
            //user is likely anon. so assume no access and let controller handle via published status
338
            return false;
339
        }
340
341
        if ($ownerId instanceof User) {
342
            $ownerId = $ownerId->getId();
343
        }
344
345
        if (!is_bool($ownPermission) && !is_bool($otherPermission)) {
346
            $permissions = $this->isGranted(
347
                [$ownPermission, $otherPermission],
348
                'RETURN_ARRAY'
349
            );
350
351
            $own   = $permissions[$ownPermission];
352
            $other = $permissions[$otherPermission];
353
        } else {
354
            if (!is_bool($ownPermission)) {
355
                $own = $this->isGranted($ownPermission);
356
            } else {
357
                $own = $ownPermission;
358
            }
359
360
            if (!is_bool($otherPermission)) {
361
                $other = $this->isGranted($otherPermission);
362
            } else {
363
                $other = $otherPermission;
364
            }
365
        }
366
367
        $ownerId = (int) $ownerId;
368
369
        if (0 === $ownerId) {
370
            if ($other) {
371
                return true;
372
            } else {
373
                return false;
374
            }
375
        } elseif ($own && (int) $this->userHelper->getUser()->getId() === (int) $ownerId) {
376
            return true;
377
        } elseif ($other && (int) $this->userHelper->getUser()->getId() !== (int) $ownerId) {
378
            return true;
379
        } else {
380
            return false;
381
        }
382
    }
383
384
    /**
385
     * Retrieves all permissions.
386
     *
387
     * @param bool $forJs
388
     *
389
     * @return array
390
     */
391
    public function getAllPermissions($forJs = false)
392
    {
393
        $permissionClasses = $this->getPermissionObjects();
394
        $permissions       = [];
395
        foreach ($permissionClasses as $object) {
396
            $perms = $object->getPermissions();
397
            if ($forJs) {
398
                foreach ($perms as $level => $perm) {
399
                    $levelPerms = array_keys($perm);
400
                    $object->parseForJavascript($levelPerms);
401
                    $permissions[$object->getName()][$level] = $levelPerms;
402
                }
403
            } else {
404
                $permissions[$object->getName()] = $perms;
405
            }
406
        }
407
408
        return $permissions;
409
    }
410
411
    /**
412
     * @return bool
413
     */
414
    public function isAnonymous()
415
    {
416
        $userEntity = $this->userHelper->getUser();
417
418
        return ($userEntity instanceof User && !$userEntity->isGuest()) ? false : true;
419
    }
420
421
    /**
422
     * @return \Symfony\Bundle\FrameworkBundle\Translation\Translator
423
     */
424
    protected function getTranslator()
425
    {
426
        return $this->translator;
427
    }
428
429
    /**
430
     * @return bool|mixed
431
     */
432
    protected function getBundles()
433
    {
434
        return $this->bundles;
435
    }
436
437
    /**
438
     * @return array
439
     */
440
    protected function getPluginBundles()
441
    {
442
        return $this->pluginBundles;
443
    }
444
445
    /**
446
     * @return array
447
     */
448
    protected function getParams()
449
    {
450
        return $this->coreParametersHelper->all();
451
    }
452
453
    /**
454
     * Register permission classes.
455
     */
456
    private function registerPermissionClasses()
457
    {
458
        foreach ($this->getBundles() as $bundle) {
459
            if (!empty($bundle['permissionClasses'])) {
460
                $this->permissionClasses = array_merge($this->permissionClasses, $bundle['permissionClasses']);
461
            }
462
        }
463
464
        foreach ($this->getPluginBundles() as $bundle) {
465
            if (!empty($bundle['permissionClasses'])) {
466
                $this->permissionClasses = array_merge($this->permissionClasses, $bundle['permissionClasses']);
467
            }
468
        }
469
    }
470
}
471