AclSecurityHandler   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 238
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 9

Importance

Changes 0
Metric Value
dl 0
loc 238
c 0
b 0
f 0
wmc 43
lcom 2
cbo 9
rs 8.96

20 Methods

Rating   Name   Duplication   Size   Complexity  
A findObjectAcls() 0 12 3
A addObjectOwner() 0 7 2
B addObjectClassAces() 0 29 7
A createAcl() 0 4 1
A updateAcl() 0 4 1
A deleteAcl() 0 4 1
A findClassAceIndexByRole() 0 10 4
A findClassAceIndexByUsername() 0 10 4
A isAnyGranted() 0 10 3
A __construct() 0 13 1
A setAdminPermissions() 0 4 1
A getAdminPermissions() 0 4 1
A setObjectPermissions() 0 4 1
A getObjectPermissions() 0 4 1
A isGranted() 0 13 4
A getBaseRole() 0 4 1
A buildSecurityInformation() 0 11 2
A createObjectSecurity() 0 17 2
A deleteObjectSecurity() 0 5 1
A getObjectAcl() 0 12 2

How to fix   Complexity   

Complex Class

Complex classes like AclSecurityHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AclSecurityHandler, and based on these observations, apply Extract Interface, too.

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\MutableAclInterface;
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
 * @author Thomas Rabaix <[email protected]>
31
 */
32
final class AclSecurityHandler implements AclSecurityHandlerInterface
33
{
34
    /**
35
     * @var TokenStorageInterface
36
     */
37
    private $tokenStorage;
38
39
    /**
40
     * @var AuthorizationCheckerInterface
41
     */
42
    private $authorizationChecker;
43
44
    /**
45
     * @var MutableAclProviderInterface
46
     */
47
    private $aclProvider;
48
49
    /**
50
     * @var array
51
     */
52
    private $superAdminRoles = [];
53
54
    /**
55
     * @var array
56
     */
57
    private $adminPermissions = [];
58
59
    /**
60
     * @var array
61
     */
62
    private $objectPermissions = [];
63
64
    /**
65
     * @var string
66
     */
67
    private $maskBuilderClass;
68
69
    public function __construct(
70
        TokenStorageInterface $tokenStorage,
71
        AuthorizationCheckerInterface $authorizationChecker,
72
        MutableAclProviderInterface $aclProvider,
73
        string $maskBuilderClass,
74
        array $superAdminRoles
75
    ) {
76
        $this->tokenStorage = $tokenStorage;
77
        $this->authorizationChecker = $authorizationChecker;
78
        $this->aclProvider = $aclProvider;
79
        $this->maskBuilderClass = $maskBuilderClass;
80
        $this->superAdminRoles = $superAdminRoles;
81
    }
82
83
    public function setAdminPermissions(array $permissions): void
84
    {
85
        $this->adminPermissions = $permissions;
86
    }
87
88
    public function getAdminPermissions()
89
    {
90
        return $this->adminPermissions;
91
    }
92
93
    public function setObjectPermissions(array $permissions): void
94
    {
95
        $this->objectPermissions = $permissions;
96
    }
97
98
    public function getObjectPermissions()
99
    {
100
        return $this->objectPermissions;
101
    }
102
103
    public function isGranted(AdminInterface $admin, $attributes, $object = null)
104
    {
105
        if (!\is_array($attributes)) {
106
            $attributes = [$attributes];
107
        }
108
109
        try {
110
            return $this->isAnyGranted($this->superAdminRoles) ||
111
                $this->isAnyGranted($attributes, $object);
112
        } catch (AuthenticationCredentialsNotFoundException $e) {
113
            return false;
114
        }
115
    }
116
117
    public function getBaseRole(AdminInterface $admin)
118
    {
119
        return sprintf('ROLE_%s_%%s', str_replace('.', '_', strtoupper($admin->getCode())));
120
    }
121
122
    public function buildSecurityInformation(AdminInterface $admin)
123
    {
124
        $baseRole = $this->getBaseRole($admin);
125
126
        $results = [];
127
        foreach ($admin->getSecurityInformation() as $role => $permissions) {
128
            $results[sprintf($baseRole, $role)] = $permissions;
129
        }
130
131
        return $results;
132
    }
133
134
    public function createObjectSecurity(AdminInterface $admin, $object): void
135
    {
136
        // retrieving the ACL for the object identity
137
        $objectIdentity = ObjectIdentity::fromDomainObject($object);
138
        $acl = $this->getObjectAcl($objectIdentity);
139
        if (null === $acl) {
140
            $acl = $this->createAcl($objectIdentity);
141
        }
142
143
        // retrieving the security identity of the currently logged-in user
144
        $user = $this->tokenStorage->getToken()->getUser();
145
        $securityIdentity = UserSecurityIdentity::fromAccount($user);
146
147
        $this->addObjectOwner($acl, $securityIdentity);
148
        $this->addObjectClassAces($acl, $this->buildSecurityInformation($admin));
149
        $this->updateAcl($acl);
150
    }
151
152
    public function deleteObjectSecurity(AdminInterface $admin, $object): void
153
    {
154
        $objectIdentity = ObjectIdentity::fromDomainObject($object);
155
        $this->deleteAcl($objectIdentity);
156
    }
157
158
    public function getObjectAcl(ObjectIdentityInterface $objectIdentity): ?MutableAclInterface
159
    {
160
        try {
161
            $acl = $this->aclProvider->findAcl($objectIdentity);
162
            // todo - remove `assert` statement after https://github.com/phpstan/phpstan-symfony/pull/92 is released
163
            \assert($acl instanceof MutableAclInterface);
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(MutableAclInterface $acl, ?UserSecurityIdentity $securityIdentity = null): void
185
    {
186
        if (false === $this->findClassAceIndexByUsername($acl, $securityIdentity->getUsername())) {
187
            // only add if not already exists
188
            $acl->insertObjectAce($securityIdentity, \constant("$this->maskBuilderClass::MASK_OWNER"));
189
        }
190
    }
191
192
    public function addObjectClassAces(MutableAclInterface $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): MutableAclInterface
223
    {
224
        return $this->aclProvider->createAcl($objectIdentity);
225
    }
226
227
    public function updateAcl(MutableAclInterface $acl): void
228
    {
229
        $this->aclProvider->updateAcl($acl);
230
    }
231
232
    public function deleteAcl(ObjectIdentityInterface $objectIdentity): void
233
    {
234
        $this->aclProvider->deleteAcl($objectIdentity);
235
    }
236
237
    public function findClassAceIndexByRole(MutableAclInterface $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(MutableAclInterface $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