Completed
Push — master ( 8016bf...ca92e8 )
by Grégoire
14s
created

AclSecurityHandler::updateAcl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Security\Handler;
15
16
use Sonata\AdminBundle\Admin\AdminInterface;
17
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
18
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
19
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
20
use Symfony\Component\Security\Acl\Exception\AclNotFoundException;
21
use Symfony\Component\Security\Acl\Exception\NotAllAclsFoundException;
22
use Symfony\Component\Security\Acl\Model\AclInterface;
23
use Symfony\Component\Security\Acl\Model\MutableAclProviderInterface;
24
use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
25
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
26
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
27
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
28
29
/**
30
 * @final since sonata-project/admin-bundle 3.52
31
 *
32
 * @author Thomas Rabaix <[email protected]>
33
 */
34
class AclSecurityHandler implements AclSecurityHandlerInterface
35
{
36
    /**
37
     * @var TokenStorageInterface
38
     */
39
    protected $tokenStorage;
40
41
    /**
42
     * @var AuthorizationCheckerInterface
43
     */
44
    protected $authorizationChecker;
45
46
    /**
47
     * @var MutableAclProviderInterface
48
     */
49
    protected $aclProvider;
50
51
    /**
52
     * @var array
53
     */
54
    protected $superAdminRoles;
55
56
    /**
57
     * @var array
58
     */
59
    protected $adminPermissions;
60
61
    /**
62
     * @var array
63
     */
64
    protected $objectPermissions;
65
66
    /**
67
     * @var string
68
     */
69
    protected $maskBuilderClass;
70
71
    public function __construct(
72
        TokenStorageInterface $tokenStorage,
73
        AuthorizationCheckerInterface $authorizationChecker,
74
        MutableAclProviderInterface $aclProvider,
75
        string $maskBuilderClass,
76
        array $superAdminRoles
77
    ) {
78
        $this->tokenStorage = $tokenStorage;
79
        $this->authorizationChecker = $authorizationChecker;
80
        $this->aclProvider = $aclProvider;
81
        $this->maskBuilderClass = $maskBuilderClass;
82
        $this->superAdminRoles = $superAdminRoles;
83
    }
84
85
    public function setAdminPermissions(array $permissions): void
86
    {
87
        $this->adminPermissions = $permissions;
88
    }
89
90
    public function getAdminPermissions()
91
    {
92
        return $this->adminPermissions;
93
    }
94
95
    public function setObjectPermissions(array $permissions): void
96
    {
97
        $this->objectPermissions = $permissions;
98
    }
99
100
    public function getObjectPermissions()
101
    {
102
        return $this->objectPermissions;
103
    }
104
105
    public function isGranted(AdminInterface $admin, $attributes, $object = null)
106
    {
107
        if (!\is_array($attributes)) {
108
            $attributes = [$attributes];
109
        }
110
111
        try {
112
            return $this->isAnyGranted($this->superAdminRoles) ||
113
                $this->isAnyGranted($attributes, $object);
114
        } catch (AuthenticationCredentialsNotFoundException $e) {
115
            return false;
116
        }
117
    }
118
119
    public function getBaseRole(AdminInterface $admin)
120
    {
121
        return 'ROLE_'.str_replace('.', '_', strtoupper($admin->getCode())).'_%s';
122
    }
123
124
    public function buildSecurityInformation(AdminInterface $admin)
125
    {
126
        $baseRole = $this->getBaseRole($admin);
127
128
        $results = [];
129
        foreach ($admin->getSecurityInformation() as $role => $permissions) {
130
            $results[sprintf($baseRole, $role)] = $permissions;
131
        }
132
133
        return $results;
134
    }
135
136
    public function createObjectSecurity(AdminInterface $admin, $object): void
137
    {
138
        // retrieving the ACL for the object identity
139
        $objectIdentity = ObjectIdentity::fromDomainObject($object);
140
        $acl = $this->getObjectAcl($objectIdentity);
141
        if (null === $acl) {
142
            $acl = $this->createAcl($objectIdentity);
143
        }
144
145
        // retrieving the security identity of the currently logged-in user
146
        $user = $this->tokenStorage->getToken()->getUser();
147
        $securityIdentity = UserSecurityIdentity::fromAccount($user);
148
149
        $this->addObjectOwner($acl, $securityIdentity);
150
        $this->addObjectClassAces($acl, $this->buildSecurityInformation($admin));
151
        $this->updateAcl($acl);
152
    }
153
154
    public function deleteObjectSecurity(AdminInterface $admin, $object): void
155
    {
156
        $objectIdentity = ObjectIdentity::fromDomainObject($object);
157
        $this->deleteAcl($objectIdentity);
158
    }
159
160
    public function getObjectAcl(ObjectIdentityInterface $objectIdentity)
161
    {
162
        try {
163
            $acl = $this->aclProvider->findAcl($objectIdentity);
164
        } catch (AclNotFoundException $e) {
165
            return null;
166
        }
167
168
        return $acl;
169
    }
170
171
    public function findObjectAcls(\Traversable $oids, array $sids = [])
172
    {
173
        try {
174
            $acls = $this->aclProvider->findAcls(iterator_to_array($oids), $sids);
175
        } catch (NotAllAclsFoundException $e) {
176
            $acls = $e->getPartialResult();
177
        } catch (AclNotFoundException $e) { // if only one oid, this error is thrown
178
            $acls = new \SplObjectStorage();
179
        }
180
181
        return $acls;
182
    }
183
184
    public function addObjectOwner(AclInterface $acl, UserSecurityIdentity $securityIdentity = null): void
185
    {
186
        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...
187
            // only add if not already exists
188
            $acl->insertObjectAce($securityIdentity, \constant("$this->maskBuilderClass::MASK_OWNER"));
189
        }
190
    }
191
192
    public function addObjectClassAces(AclInterface $acl, array $roleInformation = []): void
193
    {
194
        $builder = new $this->maskBuilderClass();
195
196
        foreach ($roleInformation as $role => $permissions) {
197
            $aceIndex = $this->findClassAceIndexByRole($acl, $role);
198
            $hasRole = false;
199
200
            foreach ($permissions as $permission) {
201
                // add only the object permissions
202
                if (\in_array($permission, $this->getObjectPermissions(), true)) {
203
                    $builder->add($permission);
204
                    $hasRole = true;
205
                }
206
            }
207
208
            if ($hasRole) {
209
                if (false === $aceIndex) {
210
                    $acl->insertClassAce(new RoleSecurityIdentity($role), $builder->get());
211
                } else {
212
                    $acl->updateClassAce($aceIndex, $builder->get());
213
                }
214
215
                $builder->reset();
216
            } elseif (false !== $aceIndex) {
217
                $acl->deleteClassAce($aceIndex);
218
            }
219
        }
220
    }
221
222
    public function createAcl(ObjectIdentityInterface $objectIdentity)
223
    {
224
        return $this->aclProvider->createAcl($objectIdentity);
225
    }
226
227
    public function updateAcl(AclInterface $acl): void
228
    {
229
        $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...
230
    }
231
232
    public function deleteAcl(ObjectIdentityInterface $objectIdentity): void
233
    {
234
        $this->aclProvider->deleteAcl($objectIdentity);
235
    }
236
237
    public function findClassAceIndexByRole(AclInterface $acl, $role)
238
    {
239
        foreach ($acl->getClassAces() as $index => $entry) {
240
            if ($entry->getSecurityIdentity() instanceof RoleSecurityIdentity && $entry->getSecurityIdentity()->getRole() === $role) {
241
                return $index;
242
            }
243
        }
244
245
        return false;
246
    }
247
248
    public function findClassAceIndexByUsername(AclInterface $acl, $username)
249
    {
250
        foreach ($acl->getClassAces() as $index => $entry) {
251
            if ($entry->getSecurityIdentity() instanceof UserSecurityIdentity && $entry->getSecurityIdentity()->getUsername() === $username) {
252
                return $index;
253
            }
254
        }
255
256
        return false;
257
    }
258
259
    private function isAnyGranted(array $attributes, $subject = null): bool
260
    {
261
        foreach ($attributes as $attribute) {
262
            if ($this->authorizationChecker->isGranted($attribute, $subject)) {
263
                return true;
264
            }
265
        }
266
267
        return false;
268
    }
269
}
270