Completed
Push — master ( 3a65e8...9f4bbe )
by Grégoire
16s
created

AclSecurityHandler::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 10
nc 3
nop 5
1
<?php
2
3
/*
4
 * This file is part of the Sonata Project package.
5
 *
6
 * (c) Thomas Rabaix <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sonata\AdminBundle\Security\Handler;
13
14
use Sonata\AdminBundle\Admin\AdminInterface;
15
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
16
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
17
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
18
use Symfony\Component\Security\Acl\Exception\AclNotFoundException;
19
use Symfony\Component\Security\Acl\Exception\NotAllAclsFoundException;
20
use Symfony\Component\Security\Acl\Model\AclInterface;
21
use Symfony\Component\Security\Acl\Model\MutableAclProviderInterface;
22
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
23
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
24
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
25
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
26
27
/**
28
 * @author Thomas Rabaix <[email protected]>
29
 */
30
class AclSecurityHandler implements AclSecurityHandlerInterface
31
{
32
    /**
33
     * @var TokenStorageInterface
34
     */
35
    protected $tokenStorage;
36
37
    /**
38
     * @var AuthorizationCheckerInterface
39
     */
40
    protected $authorizationChecker;
41
42
    /**
43
     * @var MutableAclProviderInterface
44
     */
45
    protected $aclProvider;
46
47
    /**
48
     * @var array
49
     */
50
    protected $superAdminRoles;
51
52
    /**
53
     * @var array
54
     */
55
    protected $adminPermissions;
56
57
    /**
58
     * @var array
59
     */
60
    protected $objectPermissions;
61
62
    /**
63
     * @var string
64
     */
65
    protected $maskBuilderClass;
66
67
    /**
68
     * @param TokenStorageInterface         $tokenStorage
69
     * @param AuthorizationCheckerInterface $authorizationChecker
70
     * @param MutableAclProviderInterface   $aclProvider
71
     * @param string                        $maskBuilderClass
72
     * @param array                         $superAdminRoles
73
     */
74
    public function __construct($tokenStorage, $authorizationChecker, MutableAclProviderInterface $aclProvider, $maskBuilderClass, array $superAdminRoles)
75
    {
76
        // NEXT_MAJOR: Move TokenStorageInterface check to method signature
77
        if (!$tokenStorage instanceof TokenStorageInterface) {
78
            throw new \InvalidArgumentException('Argument 1 should be an instance of Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface');
79
        }
80
        // NEXT_MAJOR: Move AuthorizationCheckerInterface check to method signature
81
        if (!$authorizationChecker instanceof AuthorizationCheckerInterface) {
82
            throw new \InvalidArgumentException('Argument 2 should be an instance of Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface');
83
        }
84
85
        $this->tokenStorage = $tokenStorage;
86
        $this->authorizationChecker = $authorizationChecker;
87
        $this->aclProvider = $aclProvider;
88
        $this->maskBuilderClass = $maskBuilderClass;
89
        $this->superAdminRoles = $superAdminRoles;
90
    }
91
92
    /**
93
     * {@inheritdoc}
94
     */
95
    public function setAdminPermissions(array $permissions)
96
    {
97
        $this->adminPermissions = $permissions;
98
    }
99
100
    /**
101
     * {@inheritdoc}
102
     */
103
    public function getAdminPermissions()
104
    {
105
        return $this->adminPermissions;
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111
    public function setObjectPermissions(array $permissions)
112
    {
113
        $this->objectPermissions = $permissions;
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    public function getObjectPermissions()
120
    {
121
        return $this->objectPermissions;
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127
    public function isGranted(AdminInterface $admin, $attributes, $object = null)
128
    {
129
        if (!is_array($attributes)) {
130
            $attributes = [$attributes];
131
        }
132
133
        try {
134
            return $this->authorizationChecker->isGranted($this->superAdminRoles) || $this->authorizationChecker->isGranted($attributes, $object);
135
        } catch (AuthenticationCredentialsNotFoundException $e) {
136
            return false;
137
        }
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143
    public function getBaseRole(AdminInterface $admin)
144
    {
145
        return 'ROLE_'.str_replace('.', '_', strtoupper($admin->getCode())).'_%s';
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     */
151
    public function buildSecurityInformation(AdminInterface $admin)
152
    {
153
        $baseRole = $this->getBaseRole($admin);
154
155
        $results = [];
156
        foreach ($admin->getSecurityInformation() as $role => $permissions) {
157
            $results[sprintf($baseRole, $role)] = $permissions;
158
        }
159
160
        return $results;
161
    }
162
163
    /**
164
     * {@inheritdoc}
165
     */
166
    public function createObjectSecurity(AdminInterface $admin, $object)
167
    {
168
        // retrieving the ACL for the object identity
169
        $objectIdentity = ObjectIdentity::fromDomainObject($object);
170
        $acl = $this->getObjectAcl($objectIdentity);
171
        if (is_null($acl)) {
172
            $acl = $this->createAcl($objectIdentity);
173
        }
174
175
        // retrieving the security identity of the currently logged-in user
176
        $user = $this->tokenStorage->getToken()->getUser();
177
        $securityIdentity = UserSecurityIdentity::fromAccount($user);
178
179
        $this->addObjectOwner($acl, $securityIdentity);
180
        $this->addObjectClassAces($acl, $this->buildSecurityInformation($admin));
181
        $this->updateAcl($acl);
182
    }
183
184
    /**
185
     * {@inheritdoc}
186
     */
187
    public function deleteObjectSecurity(AdminInterface $admin, $object)
188
    {
189
        $objectIdentity = ObjectIdentity::fromDomainObject($object);
190
        $this->deleteAcl($objectIdentity);
191
    }
192
193
    /**
194
     * {@inheritdoc}
195
     */
196
    public function getObjectAcl(ObjectIdentityInterface $objectIdentity)
197
    {
198
        try {
199
            $acl = $this->aclProvider->findAcl($objectIdentity);
200
        } catch (AclNotFoundException $e) {
201
            return;
202
        }
203
204
        return $acl;
205
    }
206
207
    /**
208
     * {@inheritdoc}
209
     */
210
    public function findObjectAcls(\Traversable $oids, array $sids = [])
211
    {
212
        try {
213
            $acls = $this->aclProvider->findAcls(iterator_to_array($oids), $sids);
214
        } catch (NotAllAclsFoundException $e) {
215
            $acls = $e->getPartialResult();
216
        } catch (AclNotFoundException $e) { // if only one oid, this error is thrown
217
            $acls = new \SplObjectStorage();
218
        }
219
220
        return $acls;
221
    }
222
223
    /**
224
     * {@inheritdoc}
225
     */
226
    public function addObjectOwner(AclInterface $acl, UserSecurityIdentity $securityIdentity = null)
227
    {
228
        if (false === $this->findClassAceIndexByUsername($acl, $securityIdentity->getUsername())) {
0 ignored issues
show
Bug introduced by
It seems like $securityIdentity is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
229
            // only add if not already exists
230
            $acl->insertObjectAce($securityIdentity, constant("$this->maskBuilderClass::MASK_OWNER"));
231
        }
232
    }
233
234
    /**
235
     * {@inheritdoc}
236
     */
237
    public function addObjectClassAces(AclInterface $acl, array $roleInformation = [])
238
    {
239
        $builder = new $this->maskBuilderClass();
240
241
        foreach ($roleInformation as $role => $permissions) {
242
            $aceIndex = $this->findClassAceIndexByRole($acl, $role);
243
            $hasRole = false;
244
245
            foreach ($permissions as $permission) {
246
                // add only the object permissions
247
                if (in_array($permission, $this->getObjectPermissions())) {
248
                    $builder->add($permission);
249
                    $hasRole = true;
250
                }
251
            }
252
253
            if ($hasRole) {
254
                if (false === $aceIndex) {
255
                    $acl->insertClassAce(new RoleSecurityIdentity($role), $builder->get());
256
                } else {
257
                    $acl->updateClassAce($aceIndex, $builder->get());
258
                }
259
260
                $builder->reset();
261
            } elseif (false !== $aceIndex) {
262
                $acl->deleteClassAce($aceIndex);
263
            }
264
        }
265
    }
266
267
    /**
268
     * {@inheritdoc}
269
     */
270
    public function createAcl(ObjectIdentityInterface $objectIdentity)
271
    {
272
        return $this->aclProvider->createAcl($objectIdentity);
273
    }
274
275
    /**
276
     * {@inheritdoc}
277
     */
278
    public function updateAcl(AclInterface $acl)
279
    {
280
        $this->aclProvider->updateAcl($acl);
0 ignored issues
show
Compatibility introduced by
$acl of type object<Symfony\Component...Acl\Model\AclInterface> is not a sub-type of object<Symfony\Component...el\MutableAclInterface>. It seems like you assume a child interface of the interface Symfony\Component\Security\Acl\Model\AclInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
281
    }
282
283
    /**
284
     * {@inheritdoc}
285
     */
286
    public function deleteAcl(ObjectIdentityInterface $objectIdentity)
287
    {
288
        $this->aclProvider->deleteAcl($objectIdentity);
289
    }
290
291
    /**
292
     * {@inheritdoc}
293
     */
294
    public function findClassAceIndexByRole(AclInterface $acl, $role)
295
    {
296
        foreach ($acl->getClassAces() as $index => $entry) {
297
            if ($entry->getSecurityIdentity() instanceof RoleSecurityIdentity && $entry->getSecurityIdentity()->getRole() === $role) {
298
                return $index;
299
            }
300
        }
301
302
        return false;
303
    }
304
305
    /**
306
     * {@inheritdoc}
307
     */
308
    public function findClassAceIndexByUsername(AclInterface $acl, $username)
309
    {
310
        foreach ($acl->getClassAces() as $index => $entry) {
311
            if ($entry->getSecurityIdentity() instanceof UserSecurityIdentity && $entry->getSecurityIdentity()->getUsername() === $username) {
312
                return $index;
313
            }
314
        }
315
316
        return false;
317
    }
318
}
319