Completed
Push — 3.x ( 3e834f...38b337 )
by Grégoire
03:36
created

src/Security/Handler/AclSecurityHandler.php (1 issue)

Severity

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
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
    /**
72
     * @param string $maskBuilderClass
73
     */
74
    public function __construct(
75
        TokenStorageInterface $tokenStorage,
76
        AuthorizationCheckerInterface $authorizationChecker,
77
        MutableAclProviderInterface $aclProvider,
78
        $maskBuilderClass,
79
        array $superAdminRoles
80
    ) {
81
        $this->tokenStorage = $tokenStorage;
82
        $this->authorizationChecker = $authorizationChecker;
83
        $this->aclProvider = $aclProvider;
84
        $this->maskBuilderClass = $maskBuilderClass;
85
        $this->superAdminRoles = $superAdminRoles;
86
    }
87
88
    public function setAdminPermissions(array $permissions)
89
    {
90
        $this->adminPermissions = $permissions;
91
    }
92
93
    public function getAdminPermissions()
94
    {
95
        return $this->adminPermissions;
96
    }
97
98
    public function setObjectPermissions(array $permissions)
99
    {
100
        $this->objectPermissions = $permissions;
101
    }
102
103
    public function getObjectPermissions()
104
    {
105
        return $this->objectPermissions;
106
    }
107
108
    public function isGranted(AdminInterface $admin, $attributes, $object = null)
109
    {
110
        if (!\is_array($attributes)) {
111
            $attributes = [$attributes];
112
        }
113
114
        try {
115
            return $this->isAnyGranted($this->superAdminRoles) ||
116
                $this->isAnyGranted($attributes, $object);
117
        } catch (AuthenticationCredentialsNotFoundException $e) {
118
            return false;
119
        }
120
    }
121
122
    public function getBaseRole(AdminInterface $admin)
123
    {
124
        return 'ROLE_'.str_replace('.', '_', strtoupper($admin->getCode())).'_%s';
125
    }
126
127
    public function buildSecurityInformation(AdminInterface $admin)
128
    {
129
        $baseRole = $this->getBaseRole($admin);
130
131
        $results = [];
132
        foreach ($admin->getSecurityInformation() as $role => $permissions) {
133
            $results[sprintf($baseRole, $role)] = $permissions;
134
        }
135
136
        return $results;
137
    }
138
139
    public function createObjectSecurity(AdminInterface $admin, $object)
140
    {
141
        // retrieving the ACL for the object identity
142
        $objectIdentity = ObjectIdentity::fromDomainObject($object);
143
        $acl = $this->getObjectAcl($objectIdentity);
144
        if (null === $acl) {
145
            $acl = $this->createAcl($objectIdentity);
146
        }
147
148
        // retrieving the security identity of the currently logged-in user
149
        $user = $this->tokenStorage->getToken()->getUser();
150
        $securityIdentity = UserSecurityIdentity::fromAccount($user);
151
152
        $this->addObjectOwner($acl, $securityIdentity);
153
        $this->addObjectClassAces($acl, $this->buildSecurityInformation($admin));
154
        $this->updateAcl($acl);
155
    }
156
157
    public function deleteObjectSecurity(AdminInterface $admin, $object)
158
    {
159
        $objectIdentity = ObjectIdentity::fromDomainObject($object);
160
        $this->deleteAcl($objectIdentity);
161
    }
162
163
    public function getObjectAcl(ObjectIdentityInterface $objectIdentity)
164
    {
165
        try {
166
            $acl = $this->aclProvider->findAcl($objectIdentity);
167
        } catch (AclNotFoundException $e) {
168
            return null;
169
        }
170
171
        return $acl;
172
    }
173
174
    public function findObjectAcls(\Traversable $oids, array $sids = [])
175
    {
176
        try {
177
            $acls = $this->aclProvider->findAcls(iterator_to_array($oids), $sids);
178
        } catch (NotAllAclsFoundException $e) {
179
            $acls = $e->getPartialResult();
180
        } catch (AclNotFoundException $e) { // if only one oid, this error is thrown
181
            $acls = new \SplObjectStorage();
182
        }
183
184
        return $acls;
185
    }
186
187
    public function addObjectOwner(AclInterface $acl, ?UserSecurityIdentity $securityIdentity = null)
188
    {
189
        if (false === $this->findClassAceIndexByUsername($acl, $securityIdentity->getUsername())) {
190
            // only add if not already exists
191
            $acl->insertObjectAce($securityIdentity, \constant("$this->maskBuilderClass::MASK_OWNER"));
192
        }
193
    }
194
195
    public function addObjectClassAces(AclInterface $acl, array $roleInformation = [])
196
    {
197
        $builder = new $this->maskBuilderClass();
198
199
        foreach ($roleInformation as $role => $permissions) {
200
            $aceIndex = $this->findClassAceIndexByRole($acl, $role);
201
            $hasRole = false;
202
203
            foreach ($permissions as $permission) {
204
                // add only the object permissions
205
                if (\in_array($permission, $this->getObjectPermissions(), true)) {
206
                    $builder->add($permission);
207
                    $hasRole = true;
208
                }
209
            }
210
211
            if ($hasRole) {
212
                if (false === $aceIndex) {
213
                    $acl->insertClassAce(new RoleSecurityIdentity($role), $builder->get());
214
                } else {
215
                    $acl->updateClassAce($aceIndex, $builder->get());
216
                }
217
218
                $builder->reset();
219
            } elseif (false !== $aceIndex) {
220
                $acl->deleteClassAce($aceIndex);
221
            }
222
        }
223
    }
224
225
    public function createAcl(ObjectIdentityInterface $objectIdentity)
226
    {
227
        return $this->aclProvider->createAcl($objectIdentity);
228
    }
229
230
    public function updateAcl(AclInterface $acl)
231
    {
232
        $this->aclProvider->updateAcl($acl);
0 ignored issues
show
$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...
233
    }
234
235
    public function deleteAcl(ObjectIdentityInterface $objectIdentity)
236
    {
237
        $this->aclProvider->deleteAcl($objectIdentity);
238
    }
239
240
    public function findClassAceIndexByRole(AclInterface $acl, $role)
241
    {
242
        foreach ($acl->getClassAces() as $index => $entry) {
243
            if ($entry->getSecurityIdentity() instanceof RoleSecurityIdentity && $entry->getSecurityIdentity()->getRole() === $role) {
244
                return $index;
245
            }
246
        }
247
248
        return false;
249
    }
250
251
    public function findClassAceIndexByUsername(AclInterface $acl, $username)
252
    {
253
        foreach ($acl->getClassAces() as $index => $entry) {
254
            if ($entry->getSecurityIdentity() instanceof UserSecurityIdentity && $entry->getSecurityIdentity()->getUsername() === $username) {
255
                return $index;
256
            }
257
        }
258
259
        return false;
260
    }
261
262
    private function isAnyGranted(array $attributes, $subject = null): bool
263
    {
264
        foreach ($attributes as $attribute) {
265
            if ($this->authorizationChecker->isGranted($attribute, $subject)) {
266
                return true;
267
            }
268
        }
269
270
        return false;
271
    }
272
}
273