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

src/Util/AdminObjectAclData.php (3 issues)

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\Admin\AdminInterface;
17
use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface;
18
use Symfony\Component\Form\Form;
19
use Symfony\Component\Security\Acl\Domain\Acl;
20
21
/**
22
 * AdminObjectAclData holds data manipulated by {@link AdminObjectAclManipulator}.
23
 *
24
 * @final since sonata-project/admin-bundle 3.52
25
 *
26
 * @author Kévin Dunglas <[email protected]>
27
 */
28
class AdminObjectAclData
29
{
30
    /**
31
     * @var array Permissions managed only by a OWNER
32
     */
33
    protected static $ownerPermissions = ['MASTER', 'OWNER'];
34
35
    /**
36
     * @var AdminInterface
37
     */
38
    protected $admin;
39
40
    /**
41
     * @var object
42
     */
43
    protected $object;
44
45
    /**
46
     * @var \Traversable Users to set ACL for
47
     */
48
    protected $aclUsers;
49
50
    /**
51
     * @var \Traversable Roles to set ACL for
52
     */
53
    protected $aclRoles;
54
55
    /**
56
     * @var array Cache of masks
57
     */
58
    protected $masks;
59
60
    /**
61
     * @var Form
62
     */
63
    protected $aclUsersForm;
64
65
    /**
66
     * @var Form
67
     */
68
    protected $aclRolesForm;
69
70
    /**
71
     * @var Acl
72
     */
73
    protected $acl;
74
75
    /**
76
     * @var string
77
     */
78
    protected $maskBuilderClass;
79
80
    /**
81
     * @param object $object
82
     * @param string $maskBuilderClass
83
     */
84
    public function __construct(
85
        AdminInterface $admin,
86
        $object,
87
        \Traversable $aclUsers,
88
        $maskBuilderClass,
89
        ?\Traversable $aclRoles = null
90
    ) {
91
        $this->admin = $admin;
92
        $this->object = $object;
93
        $this->aclUsers = $aclUsers;
94
        $this->aclRoles = (null === $aclRoles) ? new \ArrayIterator() : $aclRoles;
95
        $this->maskBuilderClass = $maskBuilderClass;
96
97
        $this->updateMasks();
98
    }
99
100
    /**
101
     * Gets admin.
102
     *
103
     * @return AdminInterface
104
     */
105
    public function getAdmin()
106
    {
107
        return $this->admin;
108
    }
109
110
    /**
111
     * Gets object.
112
     *
113
     * @return object
114
     */
115
    public function getObject()
116
    {
117
        return $this->object;
118
    }
119
120
    /**
121
     * Gets ACL users.
122
     *
123
     * @return \Traversable
124
     */
125
    public function getAclUsers()
126
    {
127
        return $this->aclUsers;
128
    }
129
130
    /**
131
     * Gets ACL roles.
132
     *
133
     * @return \Traversable
134
     */
135
    public function getAclRoles()
136
    {
137
        return $this->aclRoles;
138
    }
139
140
    /**
141
     * Sets ACL.
142
     *
143
     * @return AdminObjectAclData
144
     */
145
    public function setAcl(Acl $acl)
146
    {
147
        $this->acl = $acl;
148
149
        return $this;
150
    }
151
152
    /**
153
     * Gets ACL.
154
     *
155
     * @return Acl
156
     */
157
    public function getAcl()
158
    {
159
        return $this->acl;
160
    }
161
162
    /**
163
     * Gets masks.
164
     *
165
     * @return array
166
     */
167
    public function getMasks()
168
    {
169
        return $this->masks;
170
    }
171
172
    /**
173
     * Sets form.
174
     *
175
     * NEXT_MAJOR: remove this method.
176
     *
177
     * @return AdminObjectAclData
178
     *
179
     * @deprecated since sonata-project/admin-bundle 3.0. Use setAclUsersForm() instead
180
     */
181
    public function setForm(Form $form)
182
    {
183
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
184
            'setForm() is deprecated since version 3.0 and will be removed in 4.0. '
185
            .'Use setAclUsersForm() instead.',
186
            E_USER_DEPRECATED
187
        );
188
189
        return $this->setAclUsersForm($form);
190
    }
191
192
    /**
193
     * Gets form.
194
     *
195
     * NEXT_MAJOR: remove this method.
196
     *
197
     * @return Form
198
     *
199
     * @deprecated since sonata-project/admin-bundle version 3.0. Use getAclUsersForm() instead
200
     */
201
    public function getForm()
202
    {
203
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
204
            'getForm() is deprecated since version 3.0 and will be removed in 4.0. '
205
            .'Use getAclUsersForm() instead.',
206
            E_USER_DEPRECATED
207
        );
208
209
        return $this->getAclUsersForm();
210
    }
211
212
    /**
213
     * Sets ACL users form.
214
     *
215
     * @return AdminObjectAclData
216
     */
217
    public function setAclUsersForm(Form $form)
218
    {
219
        $this->aclUsersForm = $form;
220
221
        return $this;
222
    }
223
224
    /**
225
     * Gets ACL users form.
226
     *
227
     * @return Form
228
     */
229
    public function getAclUsersForm()
230
    {
231
        return $this->aclUsersForm;
232
    }
233
234
    /**
235
     * Sets ACL roles form.
236
     *
237
     * @return AdminObjectAclData
238
     */
239
    public function setAclRolesForm(Form $form)
240
    {
241
        $this->aclRolesForm = $form;
242
243
        return $this;
244
    }
245
246
    /**
247
     * Gets ACL roles form.
248
     *
249
     * @return Form
250
     */
251
    public function getAclRolesForm()
252
    {
253
        return $this->aclRolesForm;
254
    }
255
256
    /**
257
     * Gets permissions.
258
     *
259
     * @return array
260
     */
261
    public function getPermissions()
262
    {
263
        return $this->admin->getSecurityHandler()->getObjectPermissions();
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface Sonata\AdminBundle\Secur...ecurityHandlerInterface as the method getObjectPermissions() 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...
264
    }
265
266
    /**
267
     * Get permissions that the current user can set.
268
     *
269
     * @return array
270
     */
271
    public function getUserPermissions()
272
    {
273
        $permissions = $this->getPermissions();
274
275
        if (!$this->isOwner()) {
276
            foreach (self::$ownerPermissions as $permission) {
277
                $key = array_search($permission, $permissions, true);
278
                if (false !== $key) {
279
                    unset($permissions[$key]);
280
                }
281
            }
282
        }
283
284
        return $permissions;
285
    }
286
287
    public function getOwnerPermissions()
288
    {
289
        return self::$ownerPermissions;
290
    }
291
292
    /**
293
     * Tests if the current user has the OWNER right.
294
     *
295
     * @return bool
296
     */
297
    public function isOwner()
298
    {
299
        // Only a owner can set MASTER and OWNER ACL
300
        return $this->admin->isGranted('OWNER', $this->object);
301
    }
302
303
    /**
304
     * Gets security handler.
305
     *
306
     * @return SecurityHandlerInterface
307
     */
308
    public function getSecurityHandler()
309
    {
310
        return $this->admin->getSecurityHandler();
311
    }
312
313
    /**
314
     * @return array
315
     */
316
    public function getSecurityInformation()
317
    {
318
        return $this->admin->getSecurityHandler()->buildSecurityInformation($this->admin);
319
    }
320
321
    /**
322
     * Cache masks.
323
     */
324
    protected function updateMasks()
325
    {
326
        $permissions = $this->getPermissions();
327
328
        $reflectionClass = new \ReflectionClass(new $this->maskBuilderClass());
329
        $this->masks = [];
330
        foreach ($permissions as $permission) {
331
            $this->masks[$permission] = $reflectionClass->getConstant('MASK_'.$permission);
332
        }
333
    }
334
}
335