Completed
Push — EZP-31383 ( 83ce0c )
by
unknown
19:12
created

RoleService   F

Complexity

Total Complexity 173

Size/Duplication

Total Lines 1514
Duplicated Lines 27.48 %

Coupling/Cohesion

Components 1
Dependencies 36

Importance

Changes 0
Metric Value
dl 416
loc 1514
c 0
b 0
f 0
rs 0.8
wmc 173
lcom 1
cbo 36

42 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 1
B createRole() 0 40 7
A createRoleDraft() 27 27 4
B copyRole() 0 51 9
A loadRoleDraft() 12 12 2
A loadRoleDraftByRoleId() 12 12 2
B updateRoleDraft() 3 50 9
B addPolicyByRoleDraft() 47 47 10
A removePolicyByRoleDraft() 0 14 3
B updatePolicyByRoleDraft() 0 48 7
A deleteRoleDraft() 13 13 2
A publishRoleDraft() 0 25 4
B updateRole() 3 45 9
B addPolicy() 47 47 10
A deletePolicy() 11 11 2
A internalDeletePolicy() 11 11 2
B updatePolicy() 0 43 6
A loadRole() 12 12 2
A loadRoleByIdentifier() 16 16 3
A loadRoles() 0 18 1
A deleteRole() 17 17 3
A loadPoliciesByUserId() 0 15 3
A assignRoleToUserGroup() 36 36 5
B unassignRoleFromUserGroup() 31 31 6
A assignRoleToUser() 36 36 5
B unassignRoleFromUser() 31 31 6
A removeRoleAssignment() 17 17 3
A loadRoleAssignment() 0 36 4
A getRoleAssignments() 0 36 5
A getRoleAssignmentsForUser() 0 29 5
A getRoleAssignmentsForUserGroup() 0 18 3
A newRoleCreateStruct() 0 9 1
A newRoleCopyStruct() 0 9 1
A newPolicyCreateStruct() 0 10 1
A newPolicyUpdateStruct() 0 8 1
A newRoleUpdateStruct() 0 4 1
A getLimitationType() 0 4 1
A getLimitationTypesByModuleFunction() 0 21 4
A validateRoleCreateStruct() 17 17 3
A validateRoleCopyStruct() 17 17 3
B validatePolicy() 0 25 7
B checkAssignmentAndFilterLimitationValues() 0 42 7

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like RoleService 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 RoleService, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * File containing the eZ\Publish\Core\Repository\RoleService class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\Core\Repository;
10
11
use Exception;
12
use eZ\Publish\API\Repository\Exceptions\NotFoundException as APINotFoundException;
13
use eZ\Publish\API\Repository\Repository as RepositoryInterface;
14
use eZ\Publish\API\Repository\RoleService as RoleServiceInterface;
15
use eZ\Publish\API\Repository\Values\User\Limitation\RoleLimitation;
16
use eZ\Publish\API\Repository\Values\User\Policy as APIPolicy;
17
use eZ\Publish\API\Repository\Values\User\PolicyCreateStruct as APIPolicyCreateStruct;
18
use eZ\Publish\API\Repository\Values\User\PolicyDraft;
19
use eZ\Publish\API\Repository\Values\User\PolicyUpdateStruct as APIPolicyUpdateStruct;
20
use eZ\Publish\API\Repository\Values\User\Role as APIRole;
21
use eZ\Publish\API\Repository\Values\User\RoleAssignment;
22
use eZ\Publish\API\Repository\Values\User\RoleCreateStruct as APIRoleCreateStruct;
23
use eZ\Publish\API\Repository\Values\User\RoleCopyStruct as APIRoleCopyStruct;
24
use eZ\Publish\API\Repository\Values\User\RoleDraft as APIRoleDraft;
25
use eZ\Publish\API\Repository\Values\User\RoleUpdateStruct;
26
use eZ\Publish\API\Repository\Values\User\User;
27
use eZ\Publish\API\Repository\Values\User\UserGroup;
28
use eZ\Publish\Core\Base\Exceptions\BadStateException;
29
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
30
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue;
31
use eZ\Publish\Core\Base\Exceptions\LimitationValidationException;
32
use eZ\Publish\Core\Base\Exceptions\NotFound\LimitationNotFoundException;
33
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
34
use eZ\Publish\Core\Repository\Values\User\PolicyCreateStruct;
35
use eZ\Publish\Core\Repository\Values\User\PolicyUpdateStruct;
36
use eZ\Publish\Core\Repository\Values\User\Role;
37
use eZ\Publish\Core\Repository\Values\User\RoleCopyStruct;
38
use eZ\Publish\Core\Repository\Values\User\RoleCreateStruct;
39
use eZ\Publish\SPI\Persistence\User\Handler;
40
use eZ\Publish\SPI\Persistence\User\Role as SPIRole;
41
use eZ\Publish\SPI\Persistence\User\RoleUpdateStruct as SPIRoleUpdateStruct;
42
43
/**
44
 * This service provides methods for managing Roles and Policies.
45
 */
46
class RoleService implements RoleServiceInterface
47
{
48
    /** @var \eZ\Publish\API\Repository\Repository */
49
    protected $repository;
50
51
    /** @var \eZ\Publish\SPI\Persistence\User\Handler */
52
    protected $userHandler;
53
54
    /** @var \eZ\Publish\Core\Repository\Helper\LimitationService */
55
    protected $limitationService;
56
57
    /** @var \eZ\Publish\Core\Repository\Helper\RoleDomainMapper */
58
    protected $roleDomainMapper;
59
60
    /** @var array */
61
    protected $settings;
62
63
    /** @var \eZ\Publish\API\Repository\PermissionResolver */
64
    private $permissionResolver;
65
66
    /**
67
     * Setups service with reference to repository object that created it & corresponding handler.
68
     *
69
     * @param \eZ\Publish\API\Repository\Repository $repository
70
     * @param \eZ\Publish\SPI\Persistence\User\Handler $userHandler
71
     * @param \eZ\Publish\Core\Repository\Helper\LimitationService $limitationService
72
     * @param \eZ\Publish\Core\Repository\Helper\RoleDomainMapper $roleDomainMapper
73
     * @param array $settings
74
     */
75
    public function __construct(
76
        RepositoryInterface $repository,
77
        Handler $userHandler,
78
        Helper\LimitationService $limitationService,
79
        Helper\RoleDomainMapper $roleDomainMapper,
80
        array $settings = []
81
    ) {
82
        $this->repository = $repository;
83
        $this->userHandler = $userHandler;
84
        $this->limitationService = $limitationService;
85
        $this->roleDomainMapper = $roleDomainMapper;
86
        $this->settings = $settings;
87
        $this->permissionResolver = $repository->getPermissionResolver();
88
    }
89
90
    /**
91
     * Creates a new RoleDraft.
92
     *
93
     * @since 6.0
94
     *
95
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to create a RoleDraft
96
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
97
     *         if the name of the role already exists or if limitation of the same type
98
     *         is repeated in the policy create struct or if limitation is not allowed on module/function
99
     * @throws \eZ\Publish\API\Repository\Exceptions\LimitationValidationException if a policy limitation in the $roleCreateStruct is not valid
100
     *
101
     * @param \eZ\Publish\API\Repository\Values\User\RoleCreateStruct $roleCreateStruct
102
     *
103
     * @return \eZ\Publish\API\Repository\Values\User\RoleDraft
104
     */
105
    public function createRole(APIRoleCreateStruct $roleCreateStruct)
106
    {
107
        if (!is_string($roleCreateStruct->identifier) || empty($roleCreateStruct->identifier)) {
108
            throw new InvalidArgumentValue('identifier', $roleCreateStruct->identifier, 'RoleCreateStruct');
109
        }
110
111
        if (!$this->permissionResolver->canUser('role', 'create', $roleCreateStruct)) {
112
            throw new UnauthorizedException('role', 'create');
113
        }
114
115
        try {
116
            $existingRole = $this->loadRoleByIdentifier($roleCreateStruct->identifier);
117
118
            throw new InvalidArgumentException(
119
                '$roleCreateStruct',
120
                "Role '{$existingRole->id}' with the specified identifier '{$roleCreateStruct->identifier}' " .
121
                'already exists'
122
            );
123
        } catch (APINotFoundException $e) {
124
            // Do nothing
125
        }
126
127
        $limitationValidationErrors = $this->validateRoleCreateStruct($roleCreateStruct);
128
        if (!empty($limitationValidationErrors)) {
129
            throw new LimitationValidationException($limitationValidationErrors);
130
        }
131
132
        $spiRoleCreateStruct = $this->roleDomainMapper->buildPersistenceRoleCreateStruct($roleCreateStruct);
133
134
        $this->repository->beginTransaction();
135
        try {
136
            $spiRole = $this->userHandler->createRole($spiRoleCreateStruct);
137
            $this->repository->commit();
138
        } catch (Exception $e) {
139
            $this->repository->rollback();
140
            throw $e;
141
        }
142
143
        return $this->roleDomainMapper->buildDomainRoleDraftObject($spiRole);
144
    }
145
146
    /**
147
     * Creates a new RoleDraft for an existing Role.
148
     *
149
     * @since 6.0
150
     *
151
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to create a RoleDraft
152
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the Role already has a RoleDraft that will need to be removed first
153
     *
154
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
155
     *
156
     * @return \eZ\Publish\API\Repository\Values\User\RoleDraft
157
     */
158 View Code Duplication
    public function createRoleDraft(APIRole $role)
159
    {
160
        if (!$this->permissionResolver->canUser('role', 'create', $role)) {
161
            throw new UnauthorizedException('role', 'create');
162
        }
163
164
        try {
165
            $this->userHandler->loadRole($role->id, Role::STATUS_DRAFT);
166
167
            // Throw exception, so platformui et al can do conflict management. Follow-up: EZP-24719
168
            throw new InvalidArgumentException(
169
                '$role',
170
                "Cannot create a draft for role '{$role->identifier}' because another draft exists"
171
            );
172
        } catch (APINotFoundException $e) {
173
            $this->repository->beginTransaction();
174
            try {
175
                $spiRole = $this->userHandler->createRoleDraft($role->id);
176
                $this->repository->commit();
177
            } catch (Exception $e) {
178
                $this->repository->rollback();
179
                throw $e;
180
            }
181
        }
182
183
        return $this->roleDomainMapper->buildDomainRoleDraftObject($spiRole);
184
    }
185
186
    /**
187
     * Copies an existing Role.
188
     *
189
     * @since 7.5
190
     *
191
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to copy a Role
192
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
193
     *         if the name of the role already exists or if limitation of the same type
194
     *         is repeated in the policy create struct or if limitation is not allowed on module/function
195
     * @throws \eZ\Publish\API\Repository\Exceptions\LimitationValidationException if a policy limitation in the $roleCopyStruct is not valid
196
     *
197
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
198
     * @param \eZ\Publish\API\Repository\Values\User\RoleCopyStruct $roleCopyStruct
199
     *
200
     * @return \eZ\Publish\API\Repository\Values\User\Role
201
     */
202
    public function copyRole(APIRole $role, APIRoleCopyStruct $roleCopyStruct)
203
    {
204
        if (!is_string($roleCopyStruct->newIdentifier) || empty($roleCopyStruct->newIdentifier)) {
205
            throw new InvalidArgumentValue('newIdentifier', $roleCopyStruct->newIdentifier, 'RoleCopyStruct');
206
        }
207
208
        if (!$this->permissionResolver->canUser('role', 'create', $roleCopyStruct)) {
209
            throw new UnauthorizedException('role', 'create');
210
        }
211
212
        try {
213
            $existingRole = $this->loadRoleByIdentifier($roleCopyStruct->newIdentifier);
214
215
            throw new InvalidArgumentException(
216
                '$roleCopyStruct',
217
                "Role '{$existingRole->id}' with the specified identifier '{$roleCopyStruct->newIdentifier}' " .
218
                'already exists'
219
            );
220
        } catch (APINotFoundException $e) {
221
            // Do nothing
222
        }
223
224
        foreach ($role->getPolicies() as $policy) {
225
            $policyCreateStruct = new PolicyCreateStruct([
226
                'module' => $policy->module,
227
                'function' => $policy->function,
228
            ]);
229
            foreach ($policy->getLimitations() as $limitation) {
230
                $policyCreateStruct->addLimitation($limitation);
231
            }
232
            $roleCopyStruct->addPolicy($policyCreateStruct);
233
        }
234
235
        $limitationValidationErrors = $this->validateRoleCopyStruct($roleCopyStruct);
236
        if (!empty($limitationValidationErrors)) {
237
            throw new LimitationValidationException($limitationValidationErrors);
238
        }
239
240
        $spiRoleCopyStruct = $this->roleDomainMapper->buildPersistenceRoleCopyStruct($roleCopyStruct, $role->id);
241
242
        $this->repository->beginTransaction();
243
        try {
244
            $spiRole = $this->userHandler->copyRole($spiRoleCopyStruct);
245
            $this->repository->commit();
246
        } catch (Exception $e) {
247
            $this->repository->rollback();
248
            throw $e;
249
        }
250
251
        return $this->loadRole($spiRole->id);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->loadRole($spiRole->id); (eZ\Publish\Core\Repository\Values\User\Role) is incompatible with the return type declared by the interface eZ\Publish\API\Repository\RoleService::copyRole of type eZ\Publish\API\Repository\Values\User\RoleDraft.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
252
    }
253
254
    /**
255
     * Loads a RoleDraft for the given id.
256
     *
257
     * @since 6.0
258
     *
259
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to read this RoleDraft
260
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a RoleDraft with the given id was not found
261
     *
262
     * @param mixed $id
263
     *
264
     * @return \eZ\Publish\API\Repository\Values\User\RoleDraft
265
     */
266 View Code Duplication
    public function loadRoleDraft($id)
267
    {
268
        $spiRole = $this->userHandler->loadRole($id, Role::STATUS_DRAFT);
269
270
        $role = $this->roleDomainMapper->buildDomainRoleDraftObject($spiRole);
271
272
        if (!$this->permissionResolver->canUser('role', 'read', $role)) {
273
            throw new UnauthorizedException('role', 'read');
274
        }
275
276
        return $role;
277
    }
278
279
    /**
280
     * Loads a RoleDraft by the ID of the role it was created from.
281
     *
282
     * @param mixed $roleId ID of the role the draft was created from.
283
     *
284
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to read this role
285
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a RoleDraft with the given id was not found
286
     *
287
     * @return \eZ\Publish\API\Repository\Values\User\RoleDraft
288
     */
289 View Code Duplication
    public function loadRoleDraftByRoleId($roleId)
290
    {
291
        $spiRole = $this->userHandler->loadRoleDraftByRoleId($roleId);
292
293
        $role = $this->roleDomainMapper->buildDomainRoleDraftObject($spiRole);
294
295
        if (!$this->permissionResolver->canUser('role', 'read', $role)) {
296
            throw new UnauthorizedException('role', 'read');
297
        }
298
299
        return $role;
300
    }
301
302
    /**
303
     * Updates the properties of a RoleDraft.
304
     *
305
     * @since 6.0
306
     *
307
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to update a RoleDraft
308
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the identifier of the RoleDraft already exists
309
     *
310
     * @param \eZ\Publish\API\Repository\Values\User\RoleDraft $roleDraft
311
     * @param \eZ\Publish\API\Repository\Values\User\RoleUpdateStruct $roleUpdateStruct
312
     *
313
     * @return \eZ\Publish\API\Repository\Values\User\RoleDraft
314
     */
315
    public function updateRoleDraft(APIRoleDraft $roleDraft, RoleUpdateStruct $roleUpdateStruct)
316
    {
317 View Code Duplication
        if ($roleUpdateStruct->identifier !== null && !is_string($roleUpdateStruct->identifier)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
318
            throw new InvalidArgumentValue('identifier', $roleUpdateStruct->identifier, 'RoleUpdateStruct');
319
        }
320
321
        $loadedRoleDraft = $this->loadRoleDraft($roleDraft->id);
322
323
        if (!$this->permissionResolver->canUser('role', 'update', $loadedRoleDraft)) {
324
            throw new UnauthorizedException('role', 'update');
325
        }
326
327
        if ($roleUpdateStruct->identifier !== null) {
328
            try {
329
                /* Throw exception if:
330
                 * - A published role with the same identifier exists, AND
331
                 * - The ID of the published role does not match the original ID of the draft
332
                */
333
                $existingSPIRole = $this->userHandler->loadRoleByIdentifier($roleUpdateStruct->identifier);
334
                $SPIRoleDraft = $this->userHandler->loadRole($loadedRoleDraft->id, Role::STATUS_DRAFT);
335
                if ($existingSPIRole->id != $SPIRoleDraft->originalId) {
336
                    throw new InvalidArgumentException(
337
                        '$roleUpdateStruct',
338
                        "Role '{$existingSPIRole->id}' with the specified identifier '{$roleUpdateStruct->identifier}' " .
339
                        'already exists'
340
                    );
341
                }
342
            } catch (APINotFoundException $e) {
343
                // Do nothing
344
            }
345
        }
346
347
        $this->repository->beginTransaction();
348
        try {
349
            $this->userHandler->updateRole(
350
                new SPIRoleUpdateStruct(
351
                    [
352
                        'id' => $loadedRoleDraft->id,
353
                        'identifier' => $roleUpdateStruct->identifier ?: $loadedRoleDraft->identifier,
354
                    ]
355
                )
356
            );
357
            $this->repository->commit();
358
        } catch (Exception $e) {
359
            $this->repository->rollback();
360
            throw $e;
361
        }
362
363
        return $this->loadRoleDraft($loadedRoleDraft->id);
364
    }
365
366
    /**
367
     * Adds a new policy to the RoleDraft.
368
     *
369
     * @since 6.0
370
     *
371
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to add  a policy
372
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if limitation of the same type is repeated in policy create
373
     *                                                                        struct or if limitation is not allowed on module/function
374
     * @throws \eZ\Publish\API\Repository\Exceptions\LimitationValidationException if a limitation in the $policyCreateStruct is not valid
375
     *
376
     * @param \eZ\Publish\API\Repository\Values\User\RoleDraft $roleDraft
377
     * @param \eZ\Publish\API\Repository\Values\User\PolicyCreateStruct $policyCreateStruct
378
     *
379
     * @return \eZ\Publish\API\Repository\Values\User\RoleDraft
380
     */
381 View Code Duplication
    public function addPolicyByRoleDraft(APIRoleDraft $roleDraft, APIPolicyCreateStruct $policyCreateStruct)
382
    {
383
        if (!is_string($policyCreateStruct->module) || empty($policyCreateStruct->module)) {
384
            throw new InvalidArgumentValue('module', $policyCreateStruct->module, 'PolicyCreateStruct');
385
        }
386
387
        if (!is_string($policyCreateStruct->function) || empty($policyCreateStruct->function)) {
388
            throw new InvalidArgumentValue('function', $policyCreateStruct->function, 'PolicyCreateStruct');
389
        }
390
391
        if ($policyCreateStruct->module === '*' && $policyCreateStruct->function !== '*') {
392
            throw new InvalidArgumentValue('module', $policyCreateStruct->module, 'PolicyCreateStruct');
393
        }
394
395
        if (!$this->permissionResolver->canUser('role', 'update', $roleDraft)) {
396
            throw new UnauthorizedException('role', 'update');
397
        }
398
399
        $loadedRoleDraft = $this->loadRoleDraft($roleDraft->id);
400
401
        $limitations = $policyCreateStruct->getLimitations();
402
        $limitationValidationErrors = $this->validatePolicy(
403
            $policyCreateStruct->module,
404
            $policyCreateStruct->function,
405
            $limitations
406
        );
407
        if (!empty($limitationValidationErrors)) {
408
            throw new LimitationValidationException($limitationValidationErrors);
409
        }
410
411
        $spiPolicy = $this->roleDomainMapper->buildPersistencePolicyObject(
412
            $policyCreateStruct->module,
413
            $policyCreateStruct->function,
414
            $limitations
415
        );
416
417
        $this->repository->beginTransaction();
418
        try {
419
            $this->userHandler->addPolicyByRoleDraft($loadedRoleDraft->id, $spiPolicy);
420
            $this->repository->commit();
421
        } catch (Exception $e) {
422
            $this->repository->rollback();
423
            throw $e;
424
        }
425
426
        return $this->loadRoleDraft($loadedRoleDraft->id);
427
    }
428
429
    /**
430
     * Removes a policy from a RoleDraft.
431
     *
432
     * @since 6.0
433
     *
434
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to remove a policy
435
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if policy does not belong to the given RoleDraft
436
     *
437
     * @param \eZ\Publish\API\Repository\Values\User\RoleDraft $roleDraft
438
     * @param PolicyDraft $policyDraft the policy to remove from the RoleDraft
439
     *
440
     * @return APIRoleDraft if the authenticated user is not allowed to remove a policy
441
     */
442
    public function removePolicyByRoleDraft(APIRoleDraft $roleDraft, PolicyDraft $policyDraft)
443
    {
444
        if (!$this->permissionResolver->canUser('role', 'update', $roleDraft)) {
445
            throw new UnauthorizedException('role', 'update');
446
        }
447
448
        if ($policyDraft->roleId != $roleDraft->id) {
449
            throw new InvalidArgumentException('$policy', 'Policy does not belong to the given role');
450
        }
451
452
        $this->internalDeletePolicy($policyDraft);
453
454
        return $this->loadRoleDraft($roleDraft->id);
455
    }
456
457
    /**
458
     * Updates the limitations of a policy. The module and function cannot be changed and
459
     * the limitations are replaced by the ones in $roleUpdateStruct.
460
     *
461
     * @since 6.0
462
     *
463
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to update a policy
464
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if limitation of the same type is repeated in policy update
465
     *                                                                        struct or if limitation is not allowed on module/function
466
     * @throws \eZ\Publish\API\Repository\Exceptions\LimitationValidationException if a limitation in the $policyUpdateStruct is not valid
467
     *
468
     * @param \eZ\Publish\API\Repository\Values\User\RoleDraft $roleDraft
469
     * @param \eZ\Publish\API\Repository\Values\User\PolicyDraft $policy
470
     * @param \eZ\Publish\API\Repository\Values\User\PolicyUpdateStruct $policyUpdateStruct
471
     *
472
     * @return \eZ\Publish\API\Repository\Values\User\PolicyDraft
473
     */
474
    public function updatePolicyByRoleDraft(APIRoleDraft $roleDraft, PolicyDraft $policy, APIPolicyUpdateStruct $policyUpdateStruct)
475
    {
476
        if (!is_string($policy->module)) {
477
            throw new InvalidArgumentValue('module', $policy->module, 'Policy');
478
        }
479
480
        if (!is_string($policy->function)) {
481
            throw new InvalidArgumentValue('function', $policy->function, 'Policy');
482
        }
483
484
        if (!$this->permissionResolver->canUser('role', 'update', $roleDraft)) {
485
            throw new UnauthorizedException('role', 'update');
486
        }
487
488
        if ($policy->roleId !== $roleDraft->id) {
489
            throw new InvalidArgumentException('$policy', "doesn't belong to provided role draft");
490
        }
491
492
        $limitations = $policyUpdateStruct->getLimitations();
493
        $limitationValidationErrors = $this->validatePolicy(
494
            $policy->module,
495
            $policy->function,
496
            $limitations
497
        );
498
        if (!empty($limitationValidationErrors)) {
499
            throw new LimitationValidationException($limitationValidationErrors);
500
        }
501
502
        $spiPolicy = $this->roleDomainMapper->buildPersistencePolicyObject(
503
            $policy->module,
504
            $policy->function,
505
            $limitations
506
        );
507
        $spiPolicy->id = $policy->id;
508
        $spiPolicy->roleId = $policy->roleId;
509
        $spiPolicy->originalId = $policy->originalId;
510
511
        $this->repository->beginTransaction();
512
        try {
513
            $this->userHandler->updatePolicy($spiPolicy);
514
            $this->repository->commit();
515
        } catch (Exception $e) {
516
            $this->repository->rollback();
517
            throw $e;
518
        }
519
520
        return $this->roleDomainMapper->buildDomainPolicyObject($spiPolicy);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->roleDomainMapper-...licyObject($spiPolicy); of type eZ\Publish\Core\Reposito...tory\Values\User\Policy adds the type eZ\Publish\Core\Repository\Values\User\Policy to the return on line 520 which is incompatible with the return type declared by the interface eZ\Publish\API\Repositor...updatePolicyByRoleDraft of type eZ\Publish\API\Repository\Values\User\PolicyDraft.
Loading history...
521
    }
522
523
    /**
524
     * Deletes the given RoleDraft.
525
     *
526
     * @since 6.0
527
     *
528
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to delete this RoleDraft
529
     *
530
     * @param \eZ\Publish\API\Repository\Values\User\RoleDraft $roleDraft
531
     */
532 View Code Duplication
    public function deleteRoleDraft(APIRoleDraft $roleDraft)
533
    {
534
        $loadedRoleDraft = $this->loadRoleDraft($roleDraft->id);
535
536
        $this->repository->beginTransaction();
537
        try {
538
            $this->userHandler->deleteRole($loadedRoleDraft->id, Role::STATUS_DRAFT);
539
            $this->repository->commit();
540
        } catch (Exception $e) {
541
            $this->repository->rollback();
542
            throw $e;
543
        }
544
    }
545
546
    /**
547
     * Publishes a given RoleDraft.
548
     *
549
     * @since 6.0
550
     *
551
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to publish this RoleDraft
552
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the role draft cannot be loaded
553
     *
554
     * @param \eZ\Publish\API\Repository\Values\User\RoleDraft $roleDraft
555
     */
556
    public function publishRoleDraft(APIRoleDraft $roleDraft)
557
    {
558
        if (!$this->permissionResolver->canUser('role', 'update', $roleDraft)) {
559
            throw new UnauthorizedException('role', 'update');
560
        }
561
562
        try {
563
            $loadedRoleDraft = $this->loadRoleDraft($roleDraft->id);
564
        } catch (APINotFoundException $e) {
565
            throw new BadStateException(
566
                '$roleDraft',
567
                'The role does not have a draft.',
568
                $e
569
            );
570
        }
571
572
        $this->repository->beginTransaction();
573
        try {
574
            $this->userHandler->publishRoleDraft($loadedRoleDraft->id);
575
            $this->repository->commit();
576
        } catch (Exception $e) {
577
            $this->repository->rollback();
578
            throw $e;
579
        }
580
    }
581
582
    /**
583
     * Updates the name of the role.
584
     *
585
     * @deprecated since 6.0, use {@see updateRoleDraft}
586
     *
587
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to update a role
588
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the name of the role already exists
589
     *
590
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
591
     * @param \eZ\Publish\API\Repository\Values\User\RoleUpdateStruct $roleUpdateStruct
592
     *
593
     * @return \eZ\Publish\API\Repository\Values\User\Role
594
     */
595
    public function updateRole(APIRole $role, RoleUpdateStruct $roleUpdateStruct)
596
    {
597 View Code Duplication
        if ($roleUpdateStruct->identifier !== null && !is_string($roleUpdateStruct->identifier)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
598
            throw new InvalidArgumentValue('identifier', $roleUpdateStruct->identifier, 'RoleUpdateStruct');
599
        }
600
601
        $loadedRole = $this->loadRole($role->id);
602
603
        if (!$this->permissionResolver->canUser('role', 'update', $role)) {
604
            throw new UnauthorizedException('role', 'update');
605
        }
606
607
        if ($roleUpdateStruct->identifier !== null) {
608
            try {
609
                $existingRole = $this->loadRoleByIdentifier($roleUpdateStruct->identifier);
610
611
                if ($existingRole->id != $loadedRole->id) {
612
                    throw new InvalidArgumentException(
613
                        '$roleUpdateStruct',
614
                        'Role with provided identifier already exists'
615
                    );
616
                }
617
            } catch (APINotFoundException $e) {
618
                // Do nothing
619
            }
620
        }
621
622
        $this->repository->beginTransaction();
623
        try {
624
            $this->userHandler->updateRole(
625
                new SPIRoleUpdateStruct(
626
                    [
627
                        'id' => $loadedRole->id,
628
                        'identifier' => $roleUpdateStruct->identifier ?: $loadedRole->identifier,
629
                    ]
630
                )
631
            );
632
            $this->repository->commit();
633
        } catch (Exception $e) {
634
            $this->repository->rollback();
635
            throw $e;
636
        }
637
638
        return $this->loadRole($loadedRole->id);
639
    }
640
641
    /**
642
     * Adds a new policy to the role.
643
     *
644
     * @deprecated since 6.0, use {@see addPolicyByRoleDraft}
645
     *
646
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to add  a policy
647
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if limitation of the same type is repeated in policy create
648
     *                                                                        struct or if limitation is not allowed on module/function
649
     * @throws \eZ\Publish\API\Repository\Exceptions\LimitationValidationException if a limitation in the $policyCreateStruct is not valid
650
     *
651
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
652
     * @param \eZ\Publish\API\Repository\Values\User\PolicyCreateStruct $policyCreateStruct
653
     *
654
     * @return \eZ\Publish\API\Repository\Values\User\Role
655
     */
656 View Code Duplication
    public function addPolicy(APIRole $role, APIPolicyCreateStruct $policyCreateStruct)
657
    {
658
        if (!is_string($policyCreateStruct->module) || empty($policyCreateStruct->module)) {
659
            throw new InvalidArgumentValue('module', $policyCreateStruct->module, 'PolicyCreateStruct');
660
        }
661
662
        if (!is_string($policyCreateStruct->function) || empty($policyCreateStruct->function)) {
663
            throw new InvalidArgumentValue('function', $policyCreateStruct->function, 'PolicyCreateStruct');
664
        }
665
666
        if ($policyCreateStruct->module === '*' && $policyCreateStruct->function !== '*') {
667
            throw new InvalidArgumentValue('module', $policyCreateStruct->module, 'PolicyCreateStruct');
668
        }
669
670
        if (!$this->permissionResolver->canUser('role', 'update', $role)) {
671
            throw new UnauthorizedException('role', 'update');
672
        }
673
674
        $loadedRole = $this->loadRole($role->id);
675
676
        $limitations = $policyCreateStruct->getLimitations();
677
        $limitationValidationErrors = $this->validatePolicy(
678
            $policyCreateStruct->module,
679
            $policyCreateStruct->function,
680
            $limitations
681
        );
682
        if (!empty($limitationValidationErrors)) {
683
            throw new LimitationValidationException($limitationValidationErrors);
684
        }
685
686
        $spiPolicy = $this->roleDomainMapper->buildPersistencePolicyObject(
687
            $policyCreateStruct->module,
688
            $policyCreateStruct->function,
689
            $limitations
690
        );
691
692
        $this->repository->beginTransaction();
693
        try {
694
            $this->userHandler->addPolicy($loadedRole->id, $spiPolicy);
695
            $this->repository->commit();
696
        } catch (Exception $e) {
697
            $this->repository->rollback();
698
            throw $e;
699
        }
700
701
        return $this->loadRole($loadedRole->id);
702
    }
703
704
    /**
705
     * Deletes a policy.
706
     *
707
     * @deprecated since 6.0, use {@link removePolicyByRoleDraft()} instead.
708
     *
709
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to remove a policy
710
     *
711
     * @param \eZ\Publish\API\Repository\Values\User\Policy $policy the policy to delete
712
     */
713 View Code Duplication
    public function deletePolicy(APIPolicy $policy)
714
    {
715
        $spiRole = $this->userHandler->loadRole($policy->roleId);
716
        $role = $this->roleDomainMapper->buildDomainRoleObject($spiRole);
717
718
        if (!$this->permissionResolver->canUser('role', 'update', $role)) {
719
            throw new UnauthorizedException('role', 'update');
720
        }
721
722
        $this->internalDeletePolicy($policy);
723
    }
724
725
    /**
726
     * Deletes a policy.
727
     *
728
     * Used by {@link removePolicy()} and {@link deletePolicy()}
729
     *
730
     * @param APIPolicy $policy
731
     *
732
     * @throws \Exception
733
     */
734 View Code Duplication
    protected function internalDeletePolicy(APIPolicy $policy)
735
    {
736
        $this->repository->beginTransaction();
737
        try {
738
            $this->userHandler->deletePolicy($policy->id, $policy->roleId);
739
            $this->repository->commit();
740
        } catch (Exception $e) {
741
            $this->repository->rollback();
742
            throw $e;
743
        }
744
    }
745
746
    /**
747
     * Updates the limitations of a policy. The module and function cannot be changed and
748
     * the limitations are replaced by the ones in $roleUpdateStruct.
749
     *
750
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to update a policy
751
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if limitation of the same type is repeated in policy update
752
     *                                                                        struct or if limitation is not allowed on module/function
753
     * @throws \eZ\Publish\API\Repository\Exceptions\LimitationValidationException if a limitation in the $policyUpdateStruct is not valid
754
     *
755
     * @param \eZ\Publish\API\Repository\Values\User\PolicyUpdateStruct $policyUpdateStruct
756
     * @param \eZ\Publish\API\Repository\Values\User\Policy $policy
757
     *
758
     * @return \eZ\Publish\API\Repository\Values\User\Policy
759
     */
760
    public function updatePolicy(APIPolicy $policy, APIPolicyUpdateStruct $policyUpdateStruct)
761
    {
762
        if (!is_string($policy->module)) {
763
            throw new InvalidArgumentValue('module', $policy->module, 'Policy');
764
        }
765
766
        if (!is_string($policy->function)) {
767
            throw new InvalidArgumentValue('function', $policy->function, 'Policy');
768
        }
769
770
        if (!$this->permissionResolver->canUser('role', 'update', $policy)) {
771
            throw new UnauthorizedException('role', 'update');
772
        }
773
774
        $limitations = $policyUpdateStruct->getLimitations();
775
        $limitationValidationErrors = $this->validatePolicy(
776
            $policy->module,
777
            $policy->function,
778
            $limitations
779
        );
780
        if (!empty($limitationValidationErrors)) {
781
            throw new LimitationValidationException($limitationValidationErrors);
782
        }
783
784
        $spiPolicy = $this->roleDomainMapper->buildPersistencePolicyObject(
785
            $policy->module,
786
            $policy->function,
787
            $limitations
788
        );
789
        $spiPolicy->id = $policy->id;
790
        $spiPolicy->roleId = $policy->roleId;
791
792
        $this->repository->beginTransaction();
793
        try {
794
            $this->userHandler->updatePolicy($spiPolicy);
795
            $this->repository->commit();
796
        } catch (Exception $e) {
797
            $this->repository->rollback();
798
            throw $e;
799
        }
800
801
        return $this->roleDomainMapper->buildDomainPolicyObject($spiPolicy);
802
    }
803
804
    /**
805
     * Loads a role for the given id.
806
     *
807
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to read this role
808
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a role with the given id was not found
809
     *
810
     * @param mixed $id
811
     *
812
     * @return \eZ\Publish\API\Repository\Values\User\Role
813
     */
814 View Code Duplication
    public function loadRole($id)
815
    {
816
        $spiRole = $this->userHandler->loadRole($id);
817
818
        $role = $this->roleDomainMapper->buildDomainRoleObject($spiRole);
819
820
        if (!$this->permissionResolver->canUser('role', 'read', $role)) {
821
            throw new UnauthorizedException('role', 'read');
822
        }
823
824
        return $role;
825
    }
826
827
    /**
828
     * Loads a role for the given identifier.
829
     *
830
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to read this role
831
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a role with the given name was not found
832
     *
833
     * @param string $identifier
834
     *
835
     * @return \eZ\Publish\API\Repository\Values\User\Role
836
     */
837 View Code Duplication
    public function loadRoleByIdentifier($identifier)
838
    {
839
        if (!is_string($identifier)) {
840
            throw new InvalidArgumentValue('identifier', $identifier);
841
        }
842
843
        $spiRole = $this->userHandler->loadRoleByIdentifier($identifier);
844
845
        $role = $this->roleDomainMapper->buildDomainRoleObject($spiRole);
846
847
        if (!$this->permissionResolver->canUser('role', 'read', $role)) {
848
            throw new UnauthorizedException('role', 'read');
849
        }
850
851
        return $role;
852
    }
853
854
    /**
855
     * Loads all roles, excluding the ones the current user is not allowed to read.
856
     *
857
     * @return \eZ\Publish\API\Repository\Values\User\Role[]
858
     */
859
    public function loadRoles()
860
    {
861
        $roles = array_map(
862
            function ($spiRole) {
863
                return $this->roleDomainMapper->buildDomainRoleObject($spiRole);
864
            },
865
            $this->userHandler->loadRoles()
866
        );
867
868
        return array_values(
869
            array_filter(
870
                $roles,
871
                function ($role) {
872
                    return $this->permissionResolver->canUser('role', 'read', $role);
873
                }
874
            )
875
        );
876
    }
877
878
    /**
879
     * Deletes the given role.
880
     *
881
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to delete this role
882
     *
883
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
884
     */
885 View Code Duplication
    public function deleteRole(APIRole $role)
886
    {
887
        if (!$this->permissionResolver->canUser('role', 'delete', $role)) {
888
            throw new UnauthorizedException('role', 'delete');
889
        }
890
891
        $loadedRole = $this->loadRole($role->id);
892
893
        $this->repository->beginTransaction();
894
        try {
895
            $this->userHandler->deleteRole($loadedRole->id);
896
            $this->repository->commit();
897
        } catch (Exception $e) {
898
            $this->repository->rollback();
899
            throw $e;
900
        }
901
    }
902
903
    /**
904
     * Loads all policies from roles which are assigned to a user or to user groups to which the user belongs.
905
     *
906
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a user with the given id was not found
907
     *
908
     * @param mixed $userId
909
     *
910
     * @return \eZ\Publish\API\Repository\Values\User\Policy[]
911
     */
912
    public function loadPoliciesByUserId($userId)
913
    {
914
        $spiPolicies = $this->userHandler->loadPoliciesByUserId($userId);
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\SPI\Persisten...:loadPoliciesByUserId() has been deprecated with message: Since 6.8, not currently in use as permission system needs to know about role assignment limitations.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
915
916
        $policies = [];
917
        foreach ($spiPolicies as $spiPolicy) {
918
            $policies[] = $this->roleDomainMapper->buildDomainPolicyObject($spiPolicy);
919
        }
920
921
        if (empty($policies)) {
922
            $this->userHandler->load($userId);
923
        }// For NotFoundException in case userId is invalid
924
925
        return $policies;
926
    }
927
928
    /**
929
     * Assigns a role to the given user group.
930
     *
931
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to assign a role
932
     * @throws \eZ\Publish\API\Repository\Exceptions\LimitationValidationException if $roleLimitation is not valid
933
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If assignment already exists
934
     *
935
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
936
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
937
     * @param \eZ\Publish\API\Repository\Values\User\Limitation\RoleLimitation $roleLimitation an optional role limitation (which is either a subtree limitation or section limitation)
938
     */
939 View Code Duplication
    public function assignRoleToUserGroup(APIRole $role, UserGroup $userGroup, RoleLimitation $roleLimitation = null)
940
    {
941
        if ($this->permissionResolver->canUser('role', 'assign', $userGroup, [$role]) !== true) {
942
            throw new UnauthorizedException('role', 'assign');
943
        }
944
945
        if ($roleLimitation === null) {
946
            $limitation = null;
947
        } else {
948
            $limitationValidationErrors = $this->limitationService->validateLimitation($roleLimitation);
949
            if (!empty($limitationValidationErrors)) {
950
                throw new LimitationValidationException($limitationValidationErrors);
951
            }
952
953
            $limitation = [$roleLimitation->getIdentifier() => $roleLimitation->limitationValues];
954
        }
955
956
        // Check if objects exists
957
        $spiRole = $this->userHandler->loadRole($role->id);
958
        $loadedUserGroup = $this->repository->getUserService()->loadUserGroup($userGroup->id);
959
960
        $limitation = $this->checkAssignmentAndFilterLimitationValues($loadedUserGroup->id, $spiRole, $limitation);
961
962
        $this->repository->beginTransaction();
963
        try {
964
            $this->userHandler->assignRole(
965
                $loadedUserGroup->id,
966
                $spiRole->id,
967
                $limitation
968
            );
969
            $this->repository->commit();
970
        } catch (Exception $e) {
971
            $this->repository->rollback();
972
            throw $e;
973
        }
974
    }
975
976
    /**
977
     * removes a role from the given user group.
978
     *
979
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to remove a role
980
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException  If the role is not assigned to the given user group
981
     *
982
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
983
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
984
     */
985 View Code Duplication
    public function unassignRoleFromUserGroup(APIRole $role, UserGroup $userGroup)
986
    {
987
        if ($this->permissionResolver->canUser('role', 'assign', $userGroup, [$role]) !== true) {
988
            throw new UnauthorizedException('role', 'assign');
989
        }
990
991
        $spiRoleAssignments = $this->userHandler->loadRoleAssignmentsByGroupId($userGroup->id);
992
        $isAssigned = false;
993
        foreach ($spiRoleAssignments as $spiRoleAssignment) {
994
            if ($spiRoleAssignment->roleId === $role->id) {
995
                $isAssigned = true;
996
                break;
997
            }
998
        }
999
1000
        if (!$isAssigned) {
1001
            throw new InvalidArgumentException(
1002
                '$userGroup',
1003
                'Role is not assigned to the given UserGroup'
1004
            );
1005
        }
1006
1007
        $this->repository->beginTransaction();
1008
        try {
1009
            $this->userHandler->unassignRole($userGroup->id, $role->id);
1010
            $this->repository->commit();
1011
        } catch (Exception $e) {
1012
            $this->repository->rollback();
1013
            throw $e;
1014
        }
1015
    }
1016
1017
    /**
1018
     * Assigns a role to the given user.
1019
     *
1020
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to assign a role
1021
     * @throws \eZ\Publish\API\Repository\Exceptions\LimitationValidationException if $roleLimitation is not valid
1022
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If assignment already exists
1023
     *
1024
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
1025
     * @param \eZ\Publish\API\Repository\Values\User\User $user
1026
     * @param \eZ\Publish\API\Repository\Values\User\Limitation\RoleLimitation $roleLimitation an optional role limitation (which is either a subtree limitation or section limitation)
1027
     */
1028 View Code Duplication
    public function assignRoleToUser(APIRole $role, User $user, RoleLimitation $roleLimitation = null)
1029
    {
1030
        if ($this->permissionResolver->canUser('role', 'assign', $user, [$role]) !== true) {
1031
            throw new UnauthorizedException('role', 'assign');
1032
        }
1033
1034
        if ($roleLimitation === null) {
1035
            $limitation = null;
1036
        } else {
1037
            $limitationValidationErrors = $this->limitationService->validateLimitation($roleLimitation);
1038
            if (!empty($limitationValidationErrors)) {
1039
                throw new LimitationValidationException($limitationValidationErrors);
1040
            }
1041
1042
            $limitation = [$roleLimitation->getIdentifier() => $roleLimitation->limitationValues];
1043
        }
1044
1045
        // Check if objects exists
1046
        $spiRole = $this->userHandler->loadRole($role->id);
1047
        $spiUser = $this->userHandler->load($user->id);
1048
1049
        $limitation = $this->checkAssignmentAndFilterLimitationValues($spiUser->id, $spiRole, $limitation);
1050
1051
        $this->repository->beginTransaction();
1052
        try {
1053
            $this->userHandler->assignRole(
1054
                $spiUser->id,
1055
                $spiRole->id,
1056
                $limitation
1057
            );
1058
            $this->repository->commit();
1059
        } catch (Exception $e) {
1060
            $this->repository->rollback();
1061
            throw $e;
1062
        }
1063
    }
1064
1065
    /**
1066
     * removes a role from the given user.
1067
     *
1068
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to remove a role
1069
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If the role is not assigned to the user
1070
     *
1071
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
1072
     * @param \eZ\Publish\API\Repository\Values\User\User $user
1073
     */
1074 View Code Duplication
    public function unassignRoleFromUser(APIRole $role, User $user)
1075
    {
1076
        if ($this->permissionResolver->canUser('role', 'assign', $user, [$role]) !== true) {
1077
            throw new UnauthorizedException('role', 'assign');
1078
        }
1079
1080
        $spiRoleAssignments = $this->userHandler->loadRoleAssignmentsByGroupId($user->id);
1081
        $isAssigned = false;
1082
        foreach ($spiRoleAssignments as $spiRoleAssignment) {
1083
            if ($spiRoleAssignment->roleId === $role->id) {
1084
                $isAssigned = true;
1085
                break;
1086
            }
1087
        }
1088
1089
        if (!$isAssigned) {
1090
            throw new InvalidArgumentException(
1091
                '$user',
1092
                'Role is not assigned to the given User'
1093
            );
1094
        }
1095
1096
        $this->repository->beginTransaction();
1097
        try {
1098
            $this->userHandler->unassignRole($user->id, $role->id);
1099
            $this->repository->commit();
1100
        } catch (Exception $e) {
1101
            $this->repository->rollback();
1102
            throw $e;
1103
        }
1104
    }
1105
1106
    /**
1107
     * Removes the given role assignment.
1108
     *
1109
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to remove a role assignment
1110
     *
1111
     * @param \eZ\Publish\API\Repository\Values\User\RoleAssignment $roleAssignment
1112
     */
1113 View Code Duplication
    public function removeRoleAssignment(RoleAssignment $roleAssignment)
1114
    {
1115
        if ($this->permissionResolver->canUser('role', 'assign', $roleAssignment) !== true) {
1116
            throw new UnauthorizedException('role', 'assign');
1117
        }
1118
1119
        $spiRoleAssignment = $this->userHandler->loadRoleAssignment($roleAssignment->id);
1120
1121
        $this->repository->beginTransaction();
1122
        try {
1123
            $this->userHandler->removeRoleAssignment($spiRoleAssignment->id);
1124
            $this->repository->commit();
1125
        } catch (Exception $e) {
1126
            $this->repository->rollback();
1127
            throw $e;
1128
        }
1129
    }
1130
1131
    /**
1132
     * Loads a role assignment for the given id.
1133
     *
1134
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to read this role
1135
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException If the role assignment was not found
1136
     *
1137
     * @param mixed $roleAssignmentId
1138
     *
1139
     * @return \eZ\Publish\API\Repository\Values\User\RoleAssignment
1140
     */
1141
    public function loadRoleAssignment($roleAssignmentId)
1142
    {
1143
        $spiRoleAssignment = $this->userHandler->loadRoleAssignment($roleAssignmentId);
1144
        $userService = $this->repository->getUserService();
1145
        $role = $this->loadRole($spiRoleAssignment->roleId);
1146
1147
        if (!$this->permissionResolver->canUser('role', 'read', $role)) {
1148
            throw new UnauthorizedException('role', 'read');
1149
        }
1150
1151
        $roleAssignment = null;
1152
1153
        // First check if the Role is assigned to a User
1154
        // If no User is found, see if it belongs to a UserGroup
1155
        try {
1156
            $user = $userService->loadUser($spiRoleAssignment->contentId);
1157
            $roleAssignment = $this->roleDomainMapper->buildDomainUserRoleAssignmentObject(
1158
                $spiRoleAssignment,
1159
                $user,
1160
                $role
1161
            );
1162
        } catch (APINotFoundException $e) {
1163
            try {
1164
                $userGroup = $userService->loadUserGroup($spiRoleAssignment->contentId);
1165
                $roleAssignment = $this->roleDomainMapper->buildDomainUserGroupRoleAssignmentObject(
1166
                    $spiRoleAssignment,
1167
                    $userGroup,
1168
                    $role
1169
                );
1170
            } catch (APINotFoundException $e) {
1171
                // Do nothing
1172
            }
1173
        }
1174
1175
        return $roleAssignment;
1176
    }
1177
1178
    /**
1179
     * Returns the assigned user and user groups to this role.
1180
     *
1181
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to read a role
1182
     *
1183
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
1184
     *
1185
     * @return \eZ\Publish\API\Repository\Values\User\RoleAssignment[]
1186
     */
1187
    public function getRoleAssignments(APIRole $role)
1188
    {
1189
        if (!$this->permissionResolver->canUser('role', 'read', $role)) {
1190
            throw new UnauthorizedException('role', 'read');
1191
        }
1192
1193
        $userService = $this->repository->getUserService();
1194
        $spiRoleAssignments = $this->userHandler->loadRoleAssignmentsByRoleId($role->id);
1195
        $roleAssignments = [];
1196
1197
        foreach ($spiRoleAssignments as $spiRoleAssignment) {
1198
            // First check if the Role is assigned to a User
1199
            // If no User is found, see if it belongs to a UserGroup
1200
            try {
1201
                $user = $userService->loadUser($spiRoleAssignment->contentId);
1202
                $roleAssignments[] = $this->roleDomainMapper->buildDomainUserRoleAssignmentObject(
1203
                    $spiRoleAssignment,
1204
                    $user,
1205
                    $role
1206
                );
1207
            } catch (APINotFoundException $e) {
1208
                try {
1209
                    $userGroup = $userService->loadUserGroup($spiRoleAssignment->contentId);
1210
                    $roleAssignments[] = $this->roleDomainMapper->buildDomainUserGroupRoleAssignmentObject(
1211
                        $spiRoleAssignment,
1212
                        $userGroup,
1213
                        $role
1214
                    );
1215
                } catch (APINotFoundException $e) {
1216
                    // Do nothing
1217
                }
1218
            }
1219
        }
1220
1221
        return $roleAssignments;
1222
    }
1223
1224
    /**
1225
     * @see \eZ\Publish\API\Repository\RoleService::getRoleAssignmentsForUser()
1226
     */
1227
    public function getRoleAssignmentsForUser(User $user, $inherited = false)
1228
    {
1229
        $roleAssignments = [];
1230
        $spiRoleAssignments = $this->userHandler->loadRoleAssignmentsByGroupId($user->id, $inherited);
1231
        foreach ($spiRoleAssignments as $spiRoleAssignment) {
1232
            $role = $this->loadRole($spiRoleAssignment->roleId);
1233
1234
            if (!$this->permissionResolver->canUser('role', 'read', $role)) {
1235
                continue;
1236
            }
1237
1238
            if (!$inherited || $spiRoleAssignment->contentId == $user->id) {
1239
                $roleAssignments[] = $this->roleDomainMapper->buildDomainUserRoleAssignmentObject(
1240
                    $spiRoleAssignment,
1241
                    $user,
1242
                    $role
1243
                );
1244
            } else {
1245
                $userGroup = $this->repository->getUserService()->loadUserGroup($spiRoleAssignment->contentId);
1246
                $roleAssignments[] = $this->roleDomainMapper->buildDomainUserGroupRoleAssignmentObject(
1247
                    $spiRoleAssignment,
1248
                    $userGroup,
1249
                    $role
1250
                );
1251
            }
1252
        }
1253
1254
        return $roleAssignments;
1255
    }
1256
1257
    /**
1258
     * Returns the roles assigned to the given user group, excluding the ones the current user is not allowed to read.
1259
     *
1260
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
1261
     *
1262
     * @return \eZ\Publish\API\Repository\Values\User\UserGroupRoleAssignment[]
1263
     */
1264
    public function getRoleAssignmentsForUserGroup(UserGroup $userGroup)
1265
    {
1266
        $roleAssignments = [];
1267
        $spiRoleAssignments = $this->userHandler->loadRoleAssignmentsByGroupId($userGroup->id);
1268
        foreach ($spiRoleAssignments as $spiRoleAssignment) {
1269
            $role = $this->loadRole($spiRoleAssignment->roleId);
1270
1271
            if ($this->permissionResolver->canUser('role', 'read', $role)) {
1272
                $roleAssignments[] = $this->roleDomainMapper->buildDomainUserGroupRoleAssignmentObject(
1273
                    $spiRoleAssignment,
1274
                    $userGroup,
1275
                    $role
1276
                );
1277
            }
1278
        }
1279
1280
        return $roleAssignments;
1281
    }
1282
1283
    /**
1284
     * Instantiates a role create class.
1285
     *
1286
     * @param string $name
1287
     *
1288
     * @return \eZ\Publish\API\Repository\Values\User\RoleCreateStruct
1289
     */
1290
    public function newRoleCreateStruct($name)
1291
    {
1292
        return new RoleCreateStruct(
1293
            [
1294
                'identifier' => $name,
1295
                'policies' => [],
1296
            ]
1297
        );
1298
    }
1299
1300
    /**
1301
     * Instantiates a role copy class.
1302
     *
1303
     * @param string $name
1304
     *
1305
     * @return \eZ\Publish\API\Repository\Values\User\RoleCopyStruct
1306
     */
1307
    public function newRoleCopyStruct($name)
1308
    {
1309
        return new RoleCopyStruct(
1310
            [
1311
                'newIdentifier' => $name,
1312
                'policies' => [],
1313
            ]
1314
        );
1315
    }
1316
1317
    /**
1318
     * Instantiates a policy create class.
1319
     *
1320
     * @param string $module
1321
     * @param string $function
1322
     *
1323
     * @return \eZ\Publish\API\Repository\Values\User\PolicyCreateStruct
1324
     */
1325
    public function newPolicyCreateStruct($module, $function)
1326
    {
1327
        return new PolicyCreateStruct(
1328
            [
1329
                'module' => $module,
1330
                'function' => $function,
1331
                'limitations' => [],
1332
            ]
1333
        );
1334
    }
1335
1336
    /**
1337
     * Instantiates a policy update class.
1338
     *
1339
     * @return \eZ\Publish\API\Repository\Values\User\PolicyUpdateStruct
1340
     */
1341
    public function newPolicyUpdateStruct()
1342
    {
1343
        return new PolicyUpdateStruct(
1344
            [
1345
                'limitations' => [],
1346
            ]
1347
        );
1348
    }
1349
1350
    /**
1351
     * Instantiates a policy update class.
1352
     *
1353
     * @return \eZ\Publish\API\Repository\Values\User\RoleUpdateStruct
1354
     */
1355
    public function newRoleUpdateStruct()
1356
    {
1357
        return new RoleUpdateStruct();
1358
    }
1359
1360
    /**
1361
     * Returns the LimitationType registered with the given identifier.
1362
     *
1363
     * Returns the correct implementation of API Limitation value object
1364
     * based on provided identifier
1365
     *
1366
     * @param string $identifier
1367
     *
1368
     * @return \eZ\Publish\SPI\Limitation\Type
1369
     *
1370
     * @throws \RuntimeException if there is no LimitationType with $identifier
1371
     */
1372
    public function getLimitationType($identifier)
1373
    {
1374
        return $this->limitationService->getLimitationType($identifier);
1375
    }
1376
1377
    /**
1378
     * Returns the LimitationType's assigned to a given module/function.
1379
     *
1380
     * Typically used for:
1381
     *  - Internal validation limitation value use on Policies
1382
     *  - Role admin gui for editing policy limitations incl list limitation options via valueSchema()
1383
     *
1384
     * @param string $module Legacy name of "controller", it's a unique identifier like "content"
1385
     * @param string $function Legacy name of a controller "action", it's a unique within the controller like "read"
1386
     *
1387
     * @return \eZ\Publish\SPI\Limitation\Type[]
1388
     *
1389
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException If module/function to limitation type mapping
1390
     *                                                                 refers to a non existing identifier.
1391
     */
1392
    public function getLimitationTypesByModuleFunction($module, $function)
1393
    {
1394
        if (empty($this->settings['policyMap'][$module][$function])) {
1395
            return [];
1396
        }
1397
1398
        $types = [];
1399
        try {
1400
            foreach (array_keys($this->settings['policyMap'][$module][$function]) as $identifier) {
1401
                $types[$identifier] = $this->limitationService->getLimitationType($identifier);
1402
            }
1403
        } catch (LimitationNotFoundException $e) {
1404
            throw new BadStateException(
1405
                "{$module}/{$function}",
1406
                "policyMap configuration is referring to non existing identifier: {$identifier}",
1407
                $e
1408
            );
1409
        }
1410
1411
        return $types;
1412
    }
1413
1414
    /**
1415
     * Validates Policies and Limitations in Role create struct.
1416
     *
1417
     * @uses ::validatePolicy()
1418
     *
1419
     * @param \eZ\Publish\API\Repository\Values\User\RoleCreateStruct $roleCreateStruct
1420
     *
1421
     * @return \eZ\Publish\Core\FieldType\ValidationError[][][]
1422
     */
1423 View Code Duplication
    protected function validateRoleCreateStruct(APIRoleCreateStruct $roleCreateStruct)
1424
    {
1425
        $allErrors = [];
1426
        foreach ($roleCreateStruct->getPolicies() as $key => $policyCreateStruct) {
1427
            $errors = $this->validatePolicy(
1428
                $policyCreateStruct->module,
1429
                $policyCreateStruct->function,
1430
                $policyCreateStruct->getLimitations()
1431
            );
1432
1433
            if (!empty($errors)) {
1434
                $allErrors[$key] = $errors;
1435
            }
1436
        }
1437
1438
        return $allErrors;
1439
    }
1440
1441
    /**
1442
     * Validates Policies and Limitations in Role copy struct.
1443
     *
1444
     * @uses ::validatePolicy()
1445
     *
1446
     * @param \eZ\Publish\API\Repository\Values\User\RoleCopyStruct $roleCopyStruct
1447
     *
1448
     * @return \eZ\Publish\Core\FieldType\ValidationError[][][]
1449
     */
1450 View Code Duplication
    protected function validateRoleCopyStruct(APIRoleCopyStruct $roleCopyStruct)
1451
    {
1452
        $allErrors = [];
1453
        foreach ($roleCopyStruct->getPolicies() as $key => $policyCopyStruct) {
1454
            $errors = $this->validatePolicy(
1455
                $policyCopyStruct->module,
1456
                $policyCopyStruct->function,
1457
                $policyCopyStruct->getLimitations()
1458
            );
1459
1460
            if (!empty($errors)) {
1461
                $allErrors[$key] = $errors;
1462
            }
1463
        }
1464
1465
        return $allErrors;
1466
    }
1467
1468
    /**
1469
     * Validates Policy context: Limitations on a module and function.
1470
     *
1471
     * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentException If the same limitation is repeated or if
1472
     *                                                                   limitation is not allowed on module/function
1473
     *
1474
     * @param string $module
1475
     * @param string $function
1476
     * @param \eZ\Publish\API\Repository\Values\User\Limitation[] $limitations
1477
     *
1478
     * @return \eZ\Publish\Core\FieldType\ValidationError[][]
1479
     */
1480
    protected function validatePolicy($module, $function, array $limitations)
1481
    {
1482
        if ($module !== '*' && $function !== '*' && !empty($limitations)) {
1483
            $limitationSet = [];
1484
            foreach ($limitations as $limitation) {
1485
                if (isset($limitationSet[$limitation->getIdentifier()])) {
1486
                    throw new InvalidArgumentException(
1487
                        'limitations',
1488
                        "'{$limitation->getIdentifier()}' was found several times among the limitations"
1489
                    );
1490
                }
1491
1492
                if (!isset($this->settings['policyMap'][$module][$function][$limitation->getIdentifier()])) {
1493
                    throw new InvalidArgumentException(
1494
                        'policy',
1495
                        "The limitation '{$limitation->getIdentifier()}' is not applicable on '{$module}/{$function}'"
1496
                    );
1497
                }
1498
1499
                $limitationSet[$limitation->getIdentifier()] = true;
1500
            }
1501
        }
1502
1503
        return $this->limitationService->validateLimitations($limitations);
1504
    }
1505
1506
    /**
1507
     * Validate that assignments not already exists and filter validations against existing.
1508
     *
1509
     * @param mixed $contentId
1510
     * @param SPIRole $spiRole
1511
     * @param array|null $limitation
1512
     *
1513
     * @return array[]|null Filtered version of $limitation
1514
     *
1515
     * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentException If assignment already exists
1516
     */
1517
    protected function checkAssignmentAndFilterLimitationValues($contentId, SPIRole $spiRole, array $limitation = null)
1518
    {
1519
        $spiRoleAssignments = $this->userHandler->loadRoleAssignmentsByGroupId($contentId);
1520
        foreach ($spiRoleAssignments as $spiAssignment) {
1521
            // Ignore assignments to other roles
1522
            if ($spiAssignment->roleId !== $spiRole->id) {
1523
                continue;
1524
            }
1525
1526
            // Throw if Role is already assigned without limitations
1527
            if ($spiAssignment->limitationIdentifier === null) {
1528
                throw new InvalidArgumentException(
1529
                    '$role',
1530
                    "Role '{$spiRole->id}' already assigned without limitations"
1531
                );
1532
            }
1533
1534
            // Ignore if we are going to assign without limitations
1535
            if ($limitation === null) {
1536
                continue;
1537
            }
1538
1539
            // Ignore if not assigned with same limitation identifier
1540
            if (!isset($limitation[$spiAssignment->limitationIdentifier])) {
1541
                continue;
1542
            }
1543
1544
            // Throw if Role is already assigned with all the same limitations
1545
            $newValues = array_diff($limitation[$spiAssignment->limitationIdentifier], $spiAssignment->values);
1546
            if (empty($newValues)) {
1547
                throw new InvalidArgumentException(
1548
                    '$role',
1549
                    "Role '{$spiRole->id}' already assigned with same '{$spiAssignment->limitationIdentifier}' value"
1550
                );
1551
            }
1552
1553
            // Continue using the filtered list of limitations
1554
            $limitation[$spiAssignment->limitationIdentifier] = $newValues;
1555
        }
1556
1557
        return $limitation;
1558
    }
1559
}
1560