Completed
Push — master ( 1de9b7...830752 )
by Kristof
38:46 queued 24:09
created

Helper/Security/Acl/Permission/PermissionAdmin.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Kunstmaan\AdminBundle\Helper\Security\Acl\Permission;
4
5
use Doctrine\ORM\EntityManager;
6
use Kunstmaan\AdminBundle\Entity\AbstractEntity;
7
use Kunstmaan\AdminBundle\Entity\AclChangeset;
8
use Kunstmaan\AdminBundle\Entity\Role;
9
use Kunstmaan\UtilitiesBundle\Helper\Shell\Shell;
10
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
11
use Symfony\Component\HttpFoundation\Request;
12
use Symfony\Component\HttpKernel\KernelInterface;
13
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
14
use Symfony\Component\Security\Acl\Exception\AclNotFoundException;
15
use Symfony\Component\Security\Acl\Model\AclInterface;
16
use Symfony\Component\Security\Acl\Model\AclProviderInterface;
17
use Symfony\Component\Security\Acl\Model\AuditableEntryInterface;
18
use Symfony\Component\Security\Acl\Model\MutableAclInterface;
19
use Symfony\Component\Security\Acl\Model\MutableAclProviderInterface;
20
use Symfony\Component\Security\Acl\Model\ObjectIdentityRetrievalStrategyInterface;
21
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
22
use Symfony\Component\Security\Core\Role\RoleInterface;
23
use Symfony\Component\Security\Core\User\UserInterface;
24
25
/**
26
 * Helper to manage the permissions on a certain entity
27
 */
28
class PermissionAdmin
29
{
30
    const ADD = 'ADD';
31
    const DELETE = 'DEL';
32
33
    /**
34
     * @var AbstractEntity
35
     */
36
    protected $resource = null;
37
38
    /**
39
     * @var EntityManager
40
     */
41
    protected $em = null;
42
43
    /**
44
     * @var TokenStorageInterface
45
     */
46
    protected $tokenStorage = null;
47
48
    /**
49
     * @var MutableAclProviderInterface
50
     */
51
    protected $aclProvider = null;
52
53
    /**
54
     * @var ObjectIdentityRetrievalStrategyInterface
55
     */
56
    protected $oidRetrievalStrategy = null;
57
58
    /**
59
     * @var PermissionMap
60
     */
61
    protected $permissionMap = null;
62
63
    /**
64
     * @var array
65
     */
66
    protected $permissions = null;
67
68
    /**
69
     * @var EventDispatcherInterface
70
     */
71
    protected $eventDispatcher = null;
72
73
    /**
74
     * @var KernelInterface
75
     */
76
    protected $kernel;
77
78
    /**
79
     * @var Shell
80
     */
81
    protected $shellHelper;
82
83
    /**
84
     * Constructor
85
     *
86
     * @param EntityManager                            $em                   The EntityManager
87
     * @param TokenStorageInterface                    $tokenStorage         The token storage
88
     * @param AclProviderInterface                     $aclProvider          The ACL provider
89
     * @param ObjectIdentityRetrievalStrategyInterface $oidRetrievalStrategy The object retrieval strategy
90
     * @param EventDispatcherInterface                 $eventDispatcher      The event dispatcher
91
     * @param Shell                                    $shellHelper          The shell helper
92
     * @param KernelInterface                          $kernel               The kernel
93
     */
94
    public function __construct(
95
        EntityManager $em,
0 ignored issues
show
You have injected the EntityManager via parameter $em. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
96
        TokenStorageInterface $tokenStorage,
97
        AclProviderInterface $aclProvider,
98
        ObjectIdentityRetrievalStrategyInterface $oidRetrievalStrategy,
99
        EventDispatcherInterface $eventDispatcher,
100
        Shell $shellHelper,
101
        KernelInterface $kernel
102
    ) {
103
        $this->em = $em;
104
        $this->tokenStorage = $tokenStorage;
105
        $this->aclProvider = $aclProvider;
0 ignored issues
show
Documentation Bug introduced by
$aclProvider is of type object<Symfony\Component...l\AclProviderInterface>, but the property $aclProvider was declared to be of type object<Symfony\Component...leAclProviderInterface>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
106
        $this->oidRetrievalStrategy = $oidRetrievalStrategy;
107
        $this->eventDispatcher = $eventDispatcher;
108
        $this->shellHelper = $shellHelper;
109
        $this->kernel = $kernel;
110
    }
111
112
    /**
113
     * Initialize permission admin with specified entity.
114
     *
115
     * @param AbstractEntity         $resource      The object which has the permissions
116
     * @param PermissionMapInterface $permissionMap The permission map to use
117
     */
118
    public function initialize(AbstractEntity $resource, PermissionMapInterface $permissionMap)
119
    {
120
        $this->resource = $resource;
121
        $this->permissionMap = $permissionMap;
0 ignored issues
show
Documentation Bug introduced by
$permissionMap is of type object<Kunstmaan\AdminBu...PermissionMapInterface>, but the property $permissionMap was declared to be of type object<Kunstmaan\AdminBu...rmission\PermissionMap>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
122
        $this->permissions = array();
123
124
        // Init permissions
125
        try {
126
            $objectIdentity = $this->oidRetrievalStrategy->getObjectIdentity($this->resource);
127
            /* @var $acl AclInterface */
128
            $acl = $this->aclProvider->findAcl($objectIdentity);
129
            $objectAces = $acl->getObjectAces();
130
            /* @var $ace AuditableEntryInterface */
131 View Code Duplication
            foreach ($objectAces as $ace) {
132
                $securityIdentity = $ace->getSecurityIdentity();
133
                if ($securityIdentity instanceof RoleSecurityIdentity) {
134
                    $this->permissions[$securityIdentity->getRole()] = new MaskBuilder($ace->getMask());
135
                }
136
            }
137
        } catch (AclNotFoundException $e) {
138
            // No Acl found - do nothing (or should we initialize with default values here?)
139
        }
140
    }
141
142
    /**
143
     * Get permissions.
144
     *
145
     * @return MaskBuilder[]
146
     */
147
    public function getPermissions()
148
    {
149
        return $this->permissions;
150
    }
151
152
    /**
153
     * Get permission for specified role.
154
     *
155
     * @param RoleInterface|string $role
156
     *
157
     * @return MaskBuilder|null
158
     */
159
    public function getPermission($role)
160
    {
161
        if ($role instanceof RoleInterface || $role instanceof \Symfony\Component\Security\Core\Role\Role) {
162
            $role = $role->getRole();
163
        }
164
        if (isset($this->permissions[$role])) {
165
            return $this->permissions[$role];
166
        }
167
168
        return null;
169
    }
170
171
    /**
172
     * Get all roles.
173
     *
174
     * @return Role[]
175
     */
176
    public function getAllRoles()
177
    {
178
        return $this->em->getRepository('KunstmaanAdminBundle:Role')->findAll();
179
    }
180
181
    /**
182
     * Get all manageable roles for pages
183
     *
184
     * @return Role[]
185
     */
186
    public function getManageableRolesForPages()
187
    {
188
        $roles = $this->em->getRepository('KunstmaanAdminBundle:Role')->findAll();
189
190
        if (($token = $this->tokenStorage->getToken()) && ($user = $token->getUser())) {
191
            if ($user && !$user->isSuperAdmin() && ($superAdminRole = array_keys($roles, 'ROLE_SUPER_ADMIN'))) {
192
                $superAdminRole = current($superAdminRole);
193
                unset($roles[$superAdminRole]);
194
            }
195
        }
196
197
        return $roles;
198
    }
199
200
    /**
201
     * Get possible permissions.
202
     *
203
     * @return array
204
     */
205
    public function getPossiblePermissions()
206
    {
207
        return $this->permissionMap->getPossiblePermissions();
208
    }
209
210
    /**
211
     * Handle form entry of permission changes.
212
     *
213
     * @param Request $request
214
     *
215
     * @return bool
216
     */
217
    public function bindRequest(Request $request)
218
    {
219
        $changes = $request->request->get('permission-hidden-fields');
220
221
        if (empty($changes)) {
222
            return true;
223
        }
224
225
        // Just apply the changes to the current node (non recursively)
226
        $this->applyAclChangeset($this->resource, $changes, false);
227
228
        // Apply recursively (on request)
229
        $applyRecursive = $request->request->get('applyRecursive');
230
        if ($applyRecursive) {
231
            // Serialize changes & store them in DB
232
            $user = $this->tokenStorage->getToken()->getUser();
233
            $this->createAclChangeSet($this->resource, $changes, $user);
234
235
            $cmd = 'php ' . $this->kernel->getRootDir() . '/../bin/console kuma:acl:apply';
236
            $cmd .= ' --env=' . $this->kernel->getEnvironment();
237
238
            $this->shellHelper->runInBackground($cmd);
239
        }
240
241
        return true;
242
    }
243
244
    /**
245
     * Create a new ACL changeset.
246
     *
247
     * @param AbstractEntity $entity  The entity
248
     * @param array          $changes The changes
249
     * @param UserInterface  $user    The user
250
     *
251
     * @return AclChangeset
252
     */
253
    public function createAclChangeSet(AbstractEntity $entity, $changes, UserInterface $user)
254
    {
255
        $aclChangeset = new AclChangeset();
256
        $aclChangeset->setRef($entity);
257
        $aclChangeset->setChangeset($changes);
258
        /* @var $user BaseUser */
259
        $aclChangeset->setUser($user);
260
        $this->em->persist($aclChangeset);
261
        $this->em->flush();
262
263
        return $aclChangeset;
264
    }
265
266
    /**
267
     * Apply the specified ACL changeset.
268
     *
269
     * @param AbstractEntity $entity    The entity
270
     * @param array          $changeset The changeset
271
     * @param bool           $recursive The recursive
272
     */
273
    public function applyAclChangeset(AbstractEntity $entity, $changeset, $recursive = true)
274
    {
275
        if ($recursive) {
276
            if (!method_exists($entity, 'getChildren')) {
277
                return;
278
            }
279
280
            // Iterate over children and apply recursively
281
            /* @noinspection PhpUndefinedMethodInspection */
282
            foreach ($entity->getChildren() as $child) {
0 ignored issues
show
It seems like you code against a specific sub-type and not the parent class Kunstmaan\AdminBundle\Entity\AbstractEntity as the method getChildren() does only exist in the following sub-classes of Kunstmaan\AdminBundle\Entity\AbstractEntity: Kunstmaan\MediaBundle\Entity\Folder, Kunstmaan\MenuBundle\Entity\MenuItem, Kunstmaan\NodeBundle\Entity\Node. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
283
                $this->applyAclChangeset($child, $changeset);
284
            }
285
        }
286
287
        // Apply ACL modifications to node
288
        $objectIdentity = $this->oidRetrievalStrategy->getObjectIdentity($entity);
289
290
        try {
291
            /* @var $acl MutableAclInterface */
292
            $acl = $this->aclProvider->findAcl($objectIdentity);
293
        } catch (AclNotFoundException $e) {
294
            /* @var $acl MutableAclInterface */
295
            $acl = $this->aclProvider->createAcl($objectIdentity);
296
        }
297
298
        // Process permissions in changeset
299
        foreach ($changeset as $role => $roleChanges) {
300
            $index = $this->getObjectAceIndex($acl, $role);
301
            $mask = 0;
302
            if (false !== $index) {
303
                $mask = $this->getMaskAtIndex($acl, $index);
304
            }
305
            foreach ($roleChanges as $type => $permissions) {
306
                $maskChange = new MaskBuilder();
307
                foreach ($permissions as $permission) {
308
                    $maskChange->add($permission);
309
                }
310
                switch ($type) {
311
                    case self::ADD:
312
                        $mask = $mask | $maskChange->get();
313
314
                        break;
315
                    case self::DELETE:
316
                        $mask = $mask & ~$maskChange->get();
317
318
                        break;
319
                }
320
            }
321
            if (false !== $index) {
322
                $acl->updateObjectAce($index, $mask);
323
            } else {
324
                $securityIdentity = new RoleSecurityIdentity($role);
325
                $acl->insertObjectAce($securityIdentity, $mask);
326
            }
327
        }
328
        $this->aclProvider->updateAcl($acl);
329
    }
330
331
    /**
332
     * Get current object ACE index for specified role.
333
     *
334
     * @param AclInterface $acl  The AclInterface
335
     * @param string       $role The role
336
     *
337
     * @return bool|int
338
     */
339 View Code Duplication
    private function getObjectAceIndex(AclInterface $acl, $role)
340
    {
341
        $objectAces = $acl->getObjectAces();
342
        /* @var $ace AuditableEntryInterface */
343
        foreach ($objectAces as $index => $ace) {
344
            $securityIdentity = $ace->getSecurityIdentity();
345
            if ($securityIdentity instanceof RoleSecurityIdentity) {
346
                if ($securityIdentity->getRole() == $role) {
347
                    return $index;
348
                }
349
            }
350
        }
351
352
        return false;
353
    }
354
355
    /**
356
     * Get object ACE mask at specified index.
357
     *
358
     * @param AclInterface $acl   The acl interface
359
     * @param int          $index The index
360
     *
361
     * @return bool|int
362
     */
363 View Code Duplication
    private function getMaskAtIndex(AclInterface $acl, $index)
364
    {
365
        $objectAces = $acl->getObjectAces();
366
        /* @var $ace AuditableEntryInterface */
367
        $ace = $objectAces[$index];
368
        $securityIdentity = $ace->getSecurityIdentity();
369
        if ($securityIdentity instanceof RoleSecurityIdentity) {
370
            return $ace->getMask();
371
        }
372
373
        return false;
374
    }
375
}
376