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

src/Util/AdminObjectAclManipulator.php (1 issue)

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\Util;
15
16
use Sonata\AdminBundle\Form\Type\AclMatrixType;
17
use Symfony\Component\Form\Extension\Core\Type\FormType;
18
use Symfony\Component\Form\Form;
19
use Symfony\Component\Form\FormBuilderInterface;
20
use Symfony\Component\Form\FormFactoryInterface;
21
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
22
use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
23
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
24
use Symfony\Component\Security\Acl\Exception\NoAceFoundException;
25
use Symfony\Component\Security\Core\User\UserInterface;
26
27
/**
28
 * A manipulator for updating ACL related to an object.
29
 *
30
 * @final since sonata-project/admin-bundle 3.52
31
 *
32
 * @author Kévin Dunglas <[email protected]>
33
 * @author Baptiste Meyer <[email protected]>
34
 */
35
class AdminObjectAclManipulator
36
{
37
    public const ACL_USERS_FORM_NAME = 'acl_users_form';
38
    public const ACL_ROLES_FORM_NAME = 'acl_roles_form';
39
40
    /**
41
     * @var FormFactoryInterface
42
     */
43
    protected $formFactory;
44
    /**
45
     * @var string
46
     */
47
    protected $maskBuilderClass;
48
49
    /**
50
     * @param string $maskBuilderClass
51
     */
52
    public function __construct(FormFactoryInterface $formFactory, $maskBuilderClass)
53
    {
54
        $this->formFactory = $formFactory;
55
        $this->maskBuilderClass = $maskBuilderClass;
56
    }
57
58
    /**
59
     * Gets mask builder class name.
60
     *
61
     * @return string
62
     */
63
    public function getMaskBuilderClass()
64
    {
65
        return $this->maskBuilderClass;
66
    }
67
68
    /**
69
     * Gets the form.
70
     *
71
     * NEXT_MAJOR: remove this method.
72
     *
73
     * @return Form
74
     *
75
     * @deprecated since sonata-project/admin-bundle 3.0. Use createAclUsersForm() instead
76
     */
77
    public function createForm(AdminObjectAclData $data)
78
    {
79
        @trigger_error(
80
            'createForm() is deprecated since version 3.0 and will be removed in 4.0. '
81
            .'Use createAclUsersForm() instead.',
82
            E_USER_DEPRECATED
83
        );
84
85
        return $this->createAclUsersForm($data);
86
    }
87
88
    /**
89
     * Gets the ACL users form.
90
     *
91
     * @return Form
92
     */
93
    public function createAclUsersForm(AdminObjectAclData $data)
94
    {
95
        $aclValues = $data->getAclUsers();
96
        $formBuilder = $this->formFactory->createNamedBuilder(self::ACL_USERS_FORM_NAME, FormType::class);
97
        $form = $this->buildForm($data, $formBuilder, $aclValues);
98
        $data->setAclUsersForm($form);
99
100
        return $form;
101
    }
102
103
    /**
104
     * Gets the ACL roles form.
105
     *
106
     * @return Form
107
     */
108
    public function createAclRolesForm(AdminObjectAclData $data)
109
    {
110
        $aclValues = $data->getAclRoles();
111
        $formBuilder = $this->formFactory->createNamedBuilder(self::ACL_ROLES_FORM_NAME, FormType::class);
112
        $form = $this->buildForm($data, $formBuilder, $aclValues);
113
        $data->setAclRolesForm($form);
114
115
        return $form;
116
    }
117
118
    /**
119
     * Updates ACL users.
120
     */
121
    public function updateAclUsers(AdminObjectAclData $data)
122
    {
123
        $aclValues = $data->getAclUsers();
124
        $form = $data->getAclUsersForm();
125
126
        $this->buildAcl($data, $form, $aclValues);
127
    }
128
129
    /**
130
     * Updates ACL roles.
131
     */
132
    public function updateAclRoles(AdminObjectAclData $data)
133
    {
134
        $aclValues = $data->getAclRoles();
135
        $form = $data->getAclRolesForm();
136
137
        $this->buildAcl($data, $form, $aclValues);
138
    }
139
140
    /**
141
     * Updates ACl.
142
     *
143
     * NEXT_MAJOR: remove this method.
144
     *
145
     * @deprecated since sonata-project/admin-bundle 3.0. Use updateAclUsers() instead
146
     */
147
    public function updateAcl(AdminObjectAclData $data)
148
    {
149
        @trigger_error(
150
            'updateAcl() is deprecated since version 3.0 and will be removed in 4.0.'
151
            .'Use updateAclUsers() instead.',
152
            E_USER_DEPRECATED
153
        );
154
155
        $this->updateAclUsers($data);
156
    }
157
158
    /**
159
     * Builds ACL.
160
     */
161
    protected function buildAcl(AdminObjectAclData $data, Form $form, \Traversable $aclValues)
162
    {
163
        $masks = $data->getMasks();
164
        $acl = $data->getAcl();
165
        $matrices = $form->getData();
166
167
        foreach ($aclValues as $aclValue) {
168
            foreach ($matrices as $key => $matrix) {
169
                if ($aclValue instanceof UserInterface) {
170
                    if (\array_key_exists('user', $matrix) && $aclValue->getUsername() === $matrix['user']) {
171
                        $matrices[$key]['acl_value'] = $aclValue;
172
                    }
173
                } elseif (\array_key_exists('role', $matrix) && $aclValue === $matrix['role']) {
174
                    $matrices[$key]['acl_value'] = $aclValue;
175
                }
176
            }
177
        }
178
179
        foreach ($matrices as $matrix) {
180
            if (!isset($matrix['acl_value'])) {
181
                continue;
182
            }
183
184
            $securityIdentity = $this->getSecurityIdentity($matrix['acl_value']);
185
            $maskBuilder = new $this->maskBuilderClass();
186
187
            foreach ($data->getUserPermissions() as $permission) {
188
                if (isset($matrix[$permission]) && true === $matrix[$permission]) {
189
                    $maskBuilder->add($permission);
190
                }
191
            }
192
193
            // Restore OWNER and MASTER permissions
194
            if (!$data->isOwner()) {
195
                foreach ($data->getOwnerPermissions() as $permission) {
196
                    if ($acl->isGranted([$masks[$permission]], [$securityIdentity])) {
197
                        $maskBuilder->add($permission);
198
                    }
199
                }
200
            }
201
202
            $mask = $maskBuilder->get();
203
204
            $index = null;
205
            $ace = null;
206
            foreach ($acl->getObjectAces() as $currentIndex => $currentAce) {
207
                if ($currentAce->getSecurityIdentity()->equals($securityIdentity)) {
208
                    $index = $currentIndex;
209
                    $ace = $currentAce;
210
211
                    break;
212
                }
213
            }
214
215
            if ($ace) {
216
                $acl->updateObjectAce($index, $mask);
217
            } else {
218
                $acl->insertObjectAce($securityIdentity, $mask);
219
            }
220
        }
221
222
        $data->getSecurityHandler()->updateAcl($acl);
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Sonata\AdminBundle\Secur...ecurityHandlerInterface as the method updateAcl() does only exist in the following implementations of said interface: Sonata\AdminBundle\Secur...dler\AclSecurityHandler.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
223
    }
224
225
    /**
226
     * Builds the form.
227
     *
228
     * @return Form
229
     */
230
    protected function buildForm(AdminObjectAclData $data, FormBuilderInterface $formBuilder, \Traversable $aclValues)
231
    {
232
        // Retrieve object identity
233
        $objectIdentity = ObjectIdentity::fromDomainObject($data->getObject());
234
        $acl = $data->getSecurityHandler()->getObjectAcl($objectIdentity);
235
        if (!$acl) {
236
            $acl = $data->getSecurityHandler()->createAcl($objectIdentity);
237
        }
238
239
        $data->setAcl($acl);
240
241
        $masks = $data->getMasks();
242
        $securityInformation = $data->getSecurityInformation();
243
244
        foreach ($aclValues as $key => $aclValue) {
245
            $securityIdentity = $this->getSecurityIdentity($aclValue);
246
            $permissions = [];
247
248
            foreach ($data->getUserPermissions() as $permission) {
249
                try {
250
                    $checked = $acl->isGranted([$masks[$permission]], [$securityIdentity]);
251
                } catch (NoAceFoundException $e) {
252
                    $checked = false;
253
                }
254
255
                $attr = [];
256
                if (
257
                    self::ACL_ROLES_FORM_NAME === $formBuilder->getName()
258
                    && isset($securityInformation[$aclValue])
259
                    && false !== array_search($permission, $securityInformation[$aclValue], true)
260
                ) {
261
                    $attr['disabled'] = 'disabled';
262
                }
263
264
                $permissions[$permission] = [
265
                    'required' => false,
266
                    'data' => $checked,
267
                    'disabled' => \array_key_exists('disabled', $attr),
268
                    'attr' => $attr,
269
                ];
270
            }
271
272
            $formBuilder->add(
273
                $key,
274
                AclMatrixType::class,
275
                ['permissions' => $permissions, 'acl_value' => $aclValue]
276
            );
277
        }
278
279
        return $formBuilder->getForm();
280
    }
281
282
    /**
283
     * Gets a user or a role security identity.
284
     *
285
     * @param string|UserInterface $aclValue
286
     *
287
     * @return RoleSecurityIdentity|UserSecurityIdentity
288
     */
289
    protected function getSecurityIdentity($aclValue)
290
    {
291
        return ($aclValue instanceof UserInterface)
292
            ? UserSecurityIdentity::fromAccount($aclValue)
293
            : new RoleSecurityIdentity($aclValue)
294
        ;
295
    }
296
}
297