RoleService   F
last analyzed

Complexity

Total Complexity 117

Size/Duplication

Total Lines 1147
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 33

Importance

Changes 0
Metric Value
dl 0
loc 1147
rs 1.012
c 0
b 0
f 0
wmc 117
lcom 1
cbo 33

32 Methods

Rating   Name   Duplication   Size   Complexity  
A createRoleDraft() 0 27 4
A __construct() 0 14 1
B createRole() 0 40 7
A deleteRole() 0 17 3
A loadRoleDraft() 0 12 2
A loadRoleDraftByRoleId() 0 12 2
B updateRoleDraft() 0 50 9
B addPolicyByRoleDraft() 0 47 10
A removePolicyByRoleDraft() 0 14 3
B updatePolicyByRoleDraft() 0 51 7
A deleteRoleDraft() 0 13 2
A publishRoleDraft() 0 25 4
A loadRole() 0 12 2
A loadRoleByIdentifier() 0 12 2
A loadRoles() 0 18 1
A assignRoleToUserGroup() 0 36 5
A assignRoleToUser() 0 36 5
A removeRoleAssignment() 0 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 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() 0 17 3
B validatePolicy() 0 25 7
B checkAssignmentAndFilterLimitationValues() 0 45 7
A internalDeletePolicy() 0 11 2

How to fix   Complexity   

Complex Class

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
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
5
 * @license For full copyright and license information view LICENSE file distributed with this source code.
6
 */
7
namespace eZ\Publish\Core\Repository;
8
9
use Exception;
10
use eZ\Publish\API\Repository\Exceptions\NotFoundException as APINotFoundException;
11
use eZ\Publish\API\Repository\Repository as RepositoryInterface;
12
use eZ\Publish\API\Repository\RoleService as RoleServiceInterface;
13
use eZ\Publish\API\Repository\Values\User\Limitation\RoleLimitation;
14
use eZ\Publish\API\Repository\Values\User\Policy as APIPolicy;
15
use eZ\Publish\API\Repository\Values\User\PolicyCreateStruct as APIPolicyCreateStruct;
16
use eZ\Publish\API\Repository\Values\User\PolicyDraft;
17
use eZ\Publish\API\Repository\Values\User\PolicyUpdateStruct as APIPolicyUpdateStruct;
18
use eZ\Publish\API\Repository\Values\User\Role as APIRole;
19
use eZ\Publish\API\Repository\Values\User\RoleAssignment;
20
use eZ\Publish\API\Repository\Values\User\RoleCreateStruct as APIRoleCreateStruct;
21
use eZ\Publish\API\Repository\Values\User\RoleDraft as APIRoleDraft;
22
use eZ\Publish\API\Repository\Values\User\RoleUpdateStruct;
23
use eZ\Publish\API\Repository\Values\User\User;
24
use eZ\Publish\API\Repository\Values\User\UserGroup;
25
use eZ\Publish\Core\Base\Exceptions\BadStateException;
26
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
27
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue;
28
use eZ\Publish\Core\Base\Exceptions\LimitationValidationException;
29
use eZ\Publish\Core\Base\Exceptions\NotFound\LimitationNotFoundException;
30
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
31
use eZ\Publish\Core\Repository\Values\User\PolicyCreateStruct;
32
use eZ\Publish\Core\Repository\Values\User\PolicyUpdateStruct;
33
use eZ\Publish\Core\Repository\Values\User\Role;
34
use eZ\Publish\Core\Repository\Values\User\RoleCreateStruct;
35
use eZ\Publish\SPI\Limitation\Type;
36
use eZ\Publish\SPI\Persistence\User\Handler;
37
use eZ\Publish\SPI\Persistence\User\Role as SPIRole;
38
use eZ\Publish\SPI\Persistence\User\RoleUpdateStruct as SPIRoleUpdateStruct;
39
40
/**
41
 * This service provides methods for managing Roles and Policies.
42
 */
43
class RoleService implements RoleServiceInterface
44
{
45
    /** @var \eZ\Publish\API\Repository\Repository */
46
    protected $repository;
47
48
    /** @var \eZ\Publish\SPI\Persistence\User\Handler */
49
    protected $userHandler;
50
51
    /** @var \eZ\Publish\Core\Repository\Permission\LimitationService */
52
    protected $limitationService;
53
54
    /** @var \eZ\Publish\Core\Repository\Helper\RoleDomainMapper */
55
    protected $roleDomainMapper;
56
57
    /** @var array */
58
    protected $settings;
59
60
    /** @var \eZ\Publish\API\Repository\PermissionResolver */
61
    private $permissionResolver;
62
63
    /**
64
     * Setups service with reference to repository object that created it & corresponding handler.
65
     *
66
     * @param \eZ\Publish\API\Repository\Repository $repository
67
     * @param \eZ\Publish\SPI\Persistence\User\Handler $userHandler
68
     * @param \eZ\Publish\Core\Repository\Permission\LimitationService $limitationService
69
     * @param \eZ\Publish\Core\Repository\Helper\RoleDomainMapper $roleDomainMapper
70
     * @param array $settings
71
     */
72
    public function __construct(
73
        RepositoryInterface $repository,
74
        Handler $userHandler,
75
        Permission\LimitationService $limitationService,
76
        Helper\RoleDomainMapper $roleDomainMapper,
77
        array $settings = []
78
    ) {
79
        $this->repository = $repository;
80
        $this->userHandler = $userHandler;
81
        $this->limitationService = $limitationService;
82
        $this->roleDomainMapper = $roleDomainMapper;
83
        $this->settings = $settings;
84
        $this->permissionResolver = $repository->getPermissionResolver();
85
    }
86
87
    /**
88
     * Creates a new RoleDraft.
89
     *
90
     * @since 6.0
91
     *
92
     * @param \eZ\Publish\API\Repository\Values\User\RoleCreateStruct $roleCreateStruct
93
     *
94
     * @return \eZ\Publish\API\Repository\Values\User\RoleDraft
95
     *
96
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
97
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException 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\UnauthorizedException if the authenticated user is not allowed to create a RoleDraft
100
     * @throws \eZ\Publish\API\Repository\Exceptions\LimitationValidationException if a policy limitation in the $roleCreateStruct is not valid
101
     */
102
    public function createRole(APIRoleCreateStruct $roleCreateStruct): APIRoleDraft
103
    {
104
        if (!is_string($roleCreateStruct->identifier) || empty($roleCreateStruct->identifier)) {
105
            throw new InvalidArgumentValue('identifier', $roleCreateStruct->identifier, 'RoleCreateStruct');
106
        }
107
108
        if (!$this->permissionResolver->canUser('role', 'create', $roleCreateStruct)) {
109
            throw new UnauthorizedException('role', 'create');
110
        }
111
112
        try {
113
            $existingRole = $this->loadRoleByIdentifier($roleCreateStruct->identifier);
114
115
            throw new InvalidArgumentException(
116
                '$roleCreateStruct',
117
                "A Role '{$existingRole->id}' with identifier '{$roleCreateStruct->identifier}' " .
118
                'already exists'
119
            );
120
        } catch (APINotFoundException $e) {
121
            // Do nothing
122
        }
123
124
        $limitationValidationErrors = $this->validateRoleCreateStruct($roleCreateStruct);
125
        if (!empty($limitationValidationErrors)) {
126
            throw new LimitationValidationException($limitationValidationErrors);
127
        }
128
129
        $spiRoleCreateStruct = $this->roleDomainMapper->buildPersistenceRoleCreateStruct($roleCreateStruct);
130
131
        $this->repository->beginTransaction();
132
        try {
133
            $spiRole = $this->userHandler->createRole($spiRoleCreateStruct);
134
            $this->repository->commit();
135
        } catch (Exception $e) {
136
            $this->repository->rollback();
137
            throw $e;
138
        }
139
140
        return $this->roleDomainMapper->buildDomainRoleDraftObject($spiRole);
141
    }
142
143
    /**
144
     * Creates a new RoleDraft for an existing Role.
145
     *
146
     * @since 6.0
147
     *
148
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
149
     *
150
     * @return \eZ\Publish\API\Repository\Values\User\RoleDraft
151
     *
152
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
153
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the Role already has a RoleDraft that will need to be removed first
154
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to create a RoleDraft
155
     */
156
    public function createRoleDraft(APIRole $role): APIRoleDraft
157
    {
158
        if (!$this->permissionResolver->canUser('role', 'create', $role)) {
159
            throw new UnauthorizedException('role', 'create');
160
        }
161
162
        try {
163
            $this->userHandler->loadRole($role->id, Role::STATUS_DRAFT);
164
165
            // Throw exception, so platformui et al can do conflict management. Follow-up: EZP-24719
166
            throw new InvalidArgumentException(
167
                '$role',
168
                "Cannot create a draft for Role '{$role->identifier}' because another draft exists"
169
            );
170
        } catch (APINotFoundException $e) {
171
            $this->repository->beginTransaction();
172
            try {
173
                $spiRole = $this->userHandler->createRoleDraft($role->id);
174
                $this->repository->commit();
175
            } catch (Exception $e) {
176
                $this->repository->rollback();
177
                throw $e;
178
            }
179
        }
180
181
        return $this->roleDomainMapper->buildDomainRoleDraftObject($spiRole);
182
    }
183
184
    /**
185
     * Loads a RoleDraft for the given id.
186
     *
187
     * @since 6.0
188
     *
189
     * @param int $id
190
     *
191
     * @return \eZ\Publish\API\Repository\Values\User\RoleDraft
192
     *
193
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
194
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
195
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a RoleDraft with the given id was not found
196
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to create a RoleDraft
197
     */
198
    public function loadRoleDraft(int $id): APIRoleDraft
199
    {
200
        $spiRole = $this->userHandler->loadRole($id, Role::STATUS_DRAFT);
201
202
        $role = $this->roleDomainMapper->buildDomainRoleDraftObject($spiRole);
203
204
        if (!$this->permissionResolver->canUser('role', 'read', $role)) {
205
            throw new UnauthorizedException('role', 'read');
206
        }
207
208
        return $role;
209
    }
210
211
    /**
212
     * Loads a RoleDraft by the ID of the role it was created from.
213
     *
214
     * @param int $roleId ID of the role the draft was created from.
215
     *
216
     * @return \eZ\Publish\API\Repository\Values\User\RoleDraft
217
     *
218
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
219
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
220
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a RoleDraft with the given id was not found
221
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to read this role
222
     */
223
    public function loadRoleDraftByRoleId(int $roleId): APIRoleDraft
224
    {
225
        $spiRole = $this->userHandler->loadRoleDraftByRoleId($roleId);
226
227
        $role = $this->roleDomainMapper->buildDomainRoleDraftObject($spiRole);
228
229
        if (!$this->permissionResolver->canUser('role', 'read', $role)) {
230
            throw new UnauthorizedException('role', 'read');
231
        }
232
233
        return $role;
234
    }
235
236
    /**
237
     * Updates the properties of a RoleDraft.
238
     *
239
     * @since 6.0
240
     *
241
     * @param \eZ\Publish\API\Repository\Values\User\RoleDraft $roleDraft
242
     * @param \eZ\Publish\API\Repository\Values\User\RoleUpdateStruct $roleUpdateStruct
243
     *
244
     * @return \eZ\Publish\API\Repository\Values\User\RoleDraft
245
     *
246
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
247
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the identifier of the RoleDraft already exists
248
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
249
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to update a RoleDraft
250
     */
251
    public function updateRoleDraft(APIRoleDraft $roleDraft, RoleUpdateStruct $roleUpdateStruct): APIRoleDraft
252
    {
253
        if ($roleUpdateStruct->identifier !== null && !is_string($roleUpdateStruct->identifier)) {
254
            throw new InvalidArgumentValue('identifier', $roleUpdateStruct->identifier, 'RoleUpdateStruct');
255
        }
256
257
        $loadedRoleDraft = $this->loadRoleDraft($roleDraft->id);
258
259
        if (!$this->permissionResolver->canUser('role', 'update', $loadedRoleDraft)) {
260
            throw new UnauthorizedException('role', 'update');
261
        }
262
263
        if ($roleUpdateStruct->identifier !== null) {
264
            try {
265
                /* Throw exception if:
266
                 * - A published role with the same identifier exists, AND
267
                 * - The ID of the published role does not match the original ID of the draft
268
                */
269
                $existingSPIRole = $this->userHandler->loadRoleByIdentifier($roleUpdateStruct->identifier);
270
                $SPIRoleDraft = $this->userHandler->loadRole($loadedRoleDraft->id, Role::STATUS_DRAFT);
271
                if ($existingSPIRole->id != $SPIRoleDraft->originalId) {
272
                    throw new InvalidArgumentException(
273
                        '$roleUpdateStruct',
274
                        "A Role '{$existingSPIRole->id}' with identifier '{$roleUpdateStruct->identifier}' " .
275
                        'already exists'
276
                    );
277
                }
278
            } catch (APINotFoundException $e) {
279
                // Do nothing
280
            }
281
        }
282
283
        $this->repository->beginTransaction();
284
        try {
285
            $this->userHandler->updateRole(
286
                new SPIRoleUpdateStruct(
287
                    [
288
                        'id' => $loadedRoleDraft->id,
289
                        'identifier' => $roleUpdateStruct->identifier ?: $loadedRoleDraft->identifier,
290
                    ]
291
                )
292
            );
293
            $this->repository->commit();
294
        } catch (Exception $e) {
295
            $this->repository->rollback();
296
            throw $e;
297
        }
298
299
        return $this->loadRoleDraft($loadedRoleDraft->id);
300
    }
301
302
    /**
303
     * Adds a new policy to the RoleDraft.
304
     *
305
     * @since 6.0
306
     *
307
     * @param \eZ\Publish\API\Repository\Values\User\RoleDraft $roleDraft
308
     * @param \eZ\Publish\API\Repository\Values\User\PolicyCreateStruct $policyCreateStruct
309
     *
310
     * @return \eZ\Publish\API\Repository\Values\User\RoleDraft
311
     *
312
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
313
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if limitation of the same type is repeated in policy create
314
     *                                                                        struct or if limitation is not allowed on module/function
315
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
316
     * @throws \eZ\Publish\API\Repository\Exceptions\LimitationValidationException if a limitation in the $policyCreateStruct is not valid
317
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to add a policy
318
     */
319
    public function addPolicyByRoleDraft(APIRoleDraft $roleDraft, APIPolicyCreateStruct $policyCreateStruct): APIRoleDraft
320
    {
321
        if (!is_string($policyCreateStruct->module) || empty($policyCreateStruct->module)) {
322
            throw new InvalidArgumentValue('module', $policyCreateStruct->module, 'PolicyCreateStruct');
323
        }
324
325
        if (!is_string($policyCreateStruct->function) || empty($policyCreateStruct->function)) {
326
            throw new InvalidArgumentValue('function', $policyCreateStruct->function, 'PolicyCreateStruct');
327
        }
328
329
        if ($policyCreateStruct->module === '*' && $policyCreateStruct->function !== '*') {
330
            throw new InvalidArgumentValue('module', $policyCreateStruct->module, 'PolicyCreateStruct');
331
        }
332
333
        if (!$this->permissionResolver->canUser('role', 'update', $roleDraft)) {
334
            throw new UnauthorizedException('role', 'update');
335
        }
336
337
        $loadedRoleDraft = $this->loadRoleDraft($roleDraft->id);
338
339
        $limitations = $policyCreateStruct->getLimitations();
340
        $limitationValidationErrors = $this->validatePolicy(
341
            $policyCreateStruct->module,
342
            $policyCreateStruct->function,
343
            $limitations
344
        );
345
        if (!empty($limitationValidationErrors)) {
346
            throw new LimitationValidationException($limitationValidationErrors);
347
        }
348
349
        $spiPolicy = $this->roleDomainMapper->buildPersistencePolicyObject(
350
            $policyCreateStruct->module,
351
            $policyCreateStruct->function,
352
            $limitations
353
        );
354
355
        $this->repository->beginTransaction();
356
        try {
357
            $this->userHandler->addPolicyByRoleDraft($loadedRoleDraft->id, $spiPolicy);
358
            $this->repository->commit();
359
        } catch (Exception $e) {
360
            $this->repository->rollback();
361
            throw $e;
362
        }
363
364
        return $this->loadRoleDraft($loadedRoleDraft->id);
365
    }
366
367
    /**
368
     * Removes a policy from a RoleDraft.
369
     *
370
     * @since 6.0
371
     *
372
     * @param \eZ\Publish\API\Repository\Values\User\RoleDraft $roleDraft
373
     * @param \eZ\Publish\API\Repository\Values\User\PolicyDraft $policyDraft the policy to remove from the RoleDraft
374
     *
375
     * @return \eZ\Publish\API\Repository\Values\User\RoleDraft
376
     *
377
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
378
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if policy does not belong to the given RoleDraft
379
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
380
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to remove a policy
381
     */
382
    public function removePolicyByRoleDraft(APIRoleDraft $roleDraft, PolicyDraft $policyDraft): APIRoleDraft
383
    {
384
        if (!$this->permissionResolver->canUser('role', 'update', $roleDraft)) {
385
            throw new UnauthorizedException('role', 'update');
386
        }
387
388
        if ($policyDraft->roleId != $roleDraft->id) {
389
            throw new InvalidArgumentException('$policy', 'The Policy does not belong to the given Role');
390
        }
391
392
        $this->internalDeletePolicy($policyDraft);
393
394
        return $this->loadRoleDraft($roleDraft->id);
395
    }
396
397
    /**
398
     * Updates the limitations of a policy. The module and function cannot be changed and
399
     * the limitations are replaced by the ones in $roleUpdateStruct.
400
     *
401
     * @since 6.0
402
     *
403
     * @param \eZ\Publish\API\Repository\Values\User\RoleDraft $roleDraft
404
     * @param \eZ\Publish\API\Repository\Values\User\PolicyDraft $policy
405
     * @param \eZ\Publish\API\Repository\Values\User\PolicyUpdateStruct $policyUpdateStruct
406
     *
407
     * @return \eZ\Publish\API\Repository\Values\User\PolicyDraft
408
     *
409
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
410
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if limitation of the same type is repeated in policy update
411
     *                                                                        struct or if limitation is not allowed on module/function
412
     * @throws \eZ\Publish\API\Repository\Exceptions\LimitationValidationException if a limitation in the $policyUpdateStruct is not valid
413
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to update a policy
414
     */
415
    public function updatePolicyByRoleDraft(
416
        APIRoleDraft $roleDraft,
417
        PolicyDraft $policy,
418
        APIPolicyUpdateStruct $policyUpdateStruct
419
    ): PolicyDraft {
420
        if (!is_string($policy->module)) {
421
            throw new InvalidArgumentValue('module', $policy->module, 'Policy');
422
        }
423
424
        if (!is_string($policy->function)) {
425
            throw new InvalidArgumentValue('function', $policy->function, 'Policy');
426
        }
427
428
        if (!$this->permissionResolver->canUser('role', 'update', $roleDraft)) {
429
            throw new UnauthorizedException('role', 'update');
430
        }
431
432
        if ($policy->roleId !== $roleDraft->id) {
433
            throw new InvalidArgumentException('$policy', 'does not belong to the provided role draft');
434
        }
435
436
        $limitations = $policyUpdateStruct->getLimitations();
437
        $limitationValidationErrors = $this->validatePolicy(
438
            $policy->module,
439
            $policy->function,
440
            $limitations
441
        );
442
        if (!empty($limitationValidationErrors)) {
443
            throw new LimitationValidationException($limitationValidationErrors);
444
        }
445
446
        $spiPolicy = $this->roleDomainMapper->buildPersistencePolicyObject(
447
            $policy->module,
448
            $policy->function,
449
            $limitations
450
        );
451
        $spiPolicy->id = $policy->id;
452
        $spiPolicy->roleId = $policy->roleId;
453
        $spiPolicy->originalId = $policy->originalId;
454
455
        $this->repository->beginTransaction();
456
        try {
457
            $this->userHandler->updatePolicy($spiPolicy);
458
            $this->repository->commit();
459
        } catch (Exception $e) {
460
            $this->repository->rollback();
461
            throw $e;
462
        }
463
464
        return $this->roleDomainMapper->buildDomainPolicyObject($spiPolicy);
465
    }
466
467
    /**
468
     * Deletes the given RoleDraft.
469
     *
470
     * @since 6.0
471
     *
472
     * @param \eZ\Publish\API\Repository\Values\User\RoleDraft $roleDraft
473
     *
474
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
475
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
476
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
477
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to delete this RoleDraft
478
     */
479
    public function deleteRoleDraft(APIRoleDraft $roleDraft): void
480
    {
481
        $loadedRoleDraft = $this->loadRoleDraft($roleDraft->id);
482
483
        $this->repository->beginTransaction();
484
        try {
485
            $this->userHandler->deleteRole($loadedRoleDraft->id, Role::STATUS_DRAFT);
486
            $this->repository->commit();
487
        } catch (Exception $e) {
488
            $this->repository->rollback();
489
            throw $e;
490
        }
491
    }
492
493
    /**
494
     * Publishes a given RoleDraft.
495
     *
496
     * @since 6.0
497
     *
498
     * @param \eZ\Publish\API\Repository\Values\User\RoleDraft $roleDraft
499
     *
500
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException if the role draft cannot be loaded
501
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
502
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to publish this RoleDraft
503
     */
504
    public function publishRoleDraft(APIRoleDraft $roleDraft): void
505
    {
506
        if (!$this->permissionResolver->canUser('role', 'update', $roleDraft)) {
507
            throw new UnauthorizedException('role', 'update');
508
        }
509
510
        try {
511
            $loadedRoleDraft = $this->loadRoleDraft($roleDraft->id);
512
        } catch (APINotFoundException $e) {
513
            throw new BadStateException(
514
                '$roleDraft',
515
                'The Role does not have a draft.',
516
                $e
517
            );
518
        }
519
520
        $this->repository->beginTransaction();
521
        try {
522
            $this->userHandler->publishRoleDraft($loadedRoleDraft->id);
523
            $this->repository->commit();
524
        } catch (Exception $e) {
525
            $this->repository->rollback();
526
            throw $e;
527
        }
528
    }
529
530
    /**
531
     * Loads a role for the given id.
532
     *
533
     * @param int $id
534
     *
535
     * @return \eZ\Publish\Core\Repository\Values\User\Role
536
     *
537
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
538
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
539
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a role with the given id was not found
540
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to read this role
541
     */
542
    public function loadRole(int $id): APIRole
543
    {
544
        $spiRole = $this->userHandler->loadRole($id);
545
546
        $role = $this->roleDomainMapper->buildDomainRoleObject($spiRole);
547
548
        if (!$this->permissionResolver->canUser('role', 'read', $role)) {
549
            throw new UnauthorizedException('role', 'read');
550
        }
551
552
        return $role;
553
    }
554
555
    /**
556
     * Loads a role for the given identifier.
557
     *
558
     * @param string $identifier
559
     *
560
     * @return \eZ\Publish\API\Repository\Values\User\Role
561
     *
562
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
563
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
564
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException if a role with the given name was not found
565
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to read this role
566
     */
567
    public function loadRoleByIdentifier(string $identifier): APIRole
568
    {
569
        $spiRole = $this->userHandler->loadRoleByIdentifier($identifier);
570
571
        $role = $this->roleDomainMapper->buildDomainRoleObject($spiRole);
572
573
        if (!$this->permissionResolver->canUser('role', 'read', $role)) {
574
            throw new UnauthorizedException('role', 'read');
575
        }
576
577
        return $role;
578
    }
579
580
    /**
581
     * Loads all roles, excluding the ones the current user is not allowed to read.
582
     *
583
     * @return \eZ\Publish\API\Repository\Values\User\Role[]
584
     */
585
    public function loadRoles(): iterable
586
    {
587
        $roles = array_map(
588
            function ($spiRole) {
589
                return $this->roleDomainMapper->buildDomainRoleObject($spiRole);
590
            },
591
            $this->userHandler->loadRoles()
592
        );
593
594
        return array_values(
595
            array_filter(
596
                $roles,
597
                function ($role) {
598
                    return $this->permissionResolver->canUser('role', 'read', $role);
599
                }
600
            )
601
        );
602
    }
603
604
    /**
605
     * Deletes the given role.
606
     *
607
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
608
     *
609
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
610
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
611
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
612
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to delete this role
613
     */
614
    public function deleteRole(APIRole $role): void
615
    {
616
        if (!$this->permissionResolver->canUser('role', 'delete', $role)) {
617
            throw new UnauthorizedException('role', 'delete');
618
        }
619
620
        $loadedRole = $this->loadRole($role->id);
621
622
        $this->repository->beginTransaction();
623
        try {
624
            $this->userHandler->deleteRole($loadedRole->id);
625
            $this->repository->commit();
626
        } catch (Exception $e) {
627
            $this->repository->rollback();
628
            throw $e;
629
        }
630
    }
631
632
    /**
633
     * Assigns a role to the given user group.
634
     *
635
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
636
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
637
     * @param \eZ\Publish\API\Repository\Values\User\Limitation\RoleLimitation $roleLimitation an optional role limitation (which is either a subtree limitation or section limitation)
638
     *
639
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
640
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If assignment already exists
641
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
642
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to assign a role
643
     * @throws \eZ\Publish\API\Repository\Exceptions\LimitationValidationException if $roleLimitation is not valid
644
     */
645
    public function assignRoleToUserGroup(APIRole $role, UserGroup $userGroup, RoleLimitation $roleLimitation = null): void
646
    {
647
        if ($this->permissionResolver->canUser('role', 'assign', $userGroup, [$role]) !== true) {
648
            throw new UnauthorizedException('role', 'assign');
649
        }
650
651
        if ($roleLimitation === null) {
652
            $limitation = null;
653
        } else {
654
            $limitationValidationErrors = $this->limitationService->validateLimitation($roleLimitation);
655
            if (!empty($limitationValidationErrors)) {
656
                throw new LimitationValidationException($limitationValidationErrors);
657
            }
658
659
            $limitation = [$roleLimitation->getIdentifier() => $roleLimitation->limitationValues];
660
        }
661
662
        // Check if objects exists
663
        $spiRole = $this->userHandler->loadRole($role->id);
664
        $loadedUserGroup = $this->repository->getUserService()->loadUserGroup($userGroup->id);
665
666
        $limitation = $this->checkAssignmentAndFilterLimitationValues($loadedUserGroup->id, $spiRole, $limitation);
667
668
        $this->repository->beginTransaction();
669
        try {
670
            $this->userHandler->assignRole(
671
                $loadedUserGroup->id,
672
                $spiRole->id,
673
                $limitation
674
            );
675
            $this->repository->commit();
676
        } catch (Exception $e) {
677
            $this->repository->rollback();
678
            throw $e;
679
        }
680
    }
681
682
    /**
683
     * Assigns a role to the given user.
684
     *
685
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
686
     * @param \eZ\Publish\API\Repository\Values\User\User $user
687
     * @param \eZ\Publish\API\Repository\Values\User\Limitation\RoleLimitation $roleLimitation an optional role limitation (which is either a subtree limitation or section limitation)
688
     *
689
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
690
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If assignment already exists
691
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
692
     * @throws \eZ\Publish\API\Repository\Exceptions\LimitationValidationException if $roleLimitation is not valid
693
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to assign a role
694
     */
695
    public function assignRoleToUser(APIRole $role, User $user, RoleLimitation $roleLimitation = null): void
696
    {
697
        if ($this->permissionResolver->canUser('role', 'assign', $user, [$role]) !== true) {
698
            throw new UnauthorizedException('role', 'assign');
699
        }
700
701
        if ($roleLimitation === null) {
702
            $limitation = null;
703
        } else {
704
            $limitationValidationErrors = $this->limitationService->validateLimitation($roleLimitation);
705
            if (!empty($limitationValidationErrors)) {
706
                throw new LimitationValidationException($limitationValidationErrors);
707
            }
708
709
            $limitation = [$roleLimitation->getIdentifier() => $roleLimitation->limitationValues];
710
        }
711
712
        // Check if objects exists
713
        $spiRole = $this->userHandler->loadRole($role->id);
714
        $spiUser = $this->userHandler->load($user->id);
715
716
        $limitation = $this->checkAssignmentAndFilterLimitationValues($spiUser->id, $spiRole, $limitation);
717
718
        $this->repository->beginTransaction();
719
        try {
720
            $this->userHandler->assignRole(
721
                $spiUser->id,
722
                $spiRole->id,
723
                $limitation
724
            );
725
            $this->repository->commit();
726
        } catch (Exception $e) {
727
            $this->repository->rollback();
728
            throw $e;
729
        }
730
    }
731
732
    /**
733
     * Removes the given role assignment.
734
     *
735
     * @param \eZ\Publish\API\Repository\Values\User\RoleAssignment $roleAssignment
736
     *
737
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
738
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
739
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
740
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to remove a role assignment
741
     */
742
    public function removeRoleAssignment(RoleAssignment $roleAssignment): void
743
    {
744
        if ($this->permissionResolver->canUser('role', 'assign', $roleAssignment) !== true) {
745
            throw new UnauthorizedException('role', 'assign');
746
        }
747
748
        $spiRoleAssignment = $this->userHandler->loadRoleAssignment($roleAssignment->id);
749
750
        $this->repository->beginTransaction();
751
        try {
752
            $this->userHandler->removeRoleAssignment($spiRoleAssignment->id);
753
            $this->repository->commit();
754
        } catch (Exception $e) {
755
            $this->repository->rollback();
756
            throw $e;
757
        }
758
    }
759
760
    /**
761
     * Loads a role assignment for the given id.
762
     *
763
     * @param int $roleAssignmentId
764
     *
765
     * @return \eZ\Publish\API\Repository\Values\User\RoleAssignment
766
     *
767
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
768
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
769
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException If the role assignment was not found
770
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to read this role
771
     */
772
    public function loadRoleAssignment(int $roleAssignmentId): RoleAssignment
773
    {
774
        $spiRoleAssignment = $this->userHandler->loadRoleAssignment($roleAssignmentId);
775
        $userService = $this->repository->getUserService();
776
        $role = $this->loadRole($spiRoleAssignment->roleId);
777
778
        if (!$this->permissionResolver->canUser('role', 'read', $role)) {
779
            throw new UnauthorizedException('role', 'read');
780
        }
781
782
        $roleAssignment = null;
783
784
        // First check if the Role is assigned to a User
785
        // If no User is found, see if it belongs to a UserGroup
786
        try {
787
            $user = $userService->loadUser($spiRoleAssignment->contentId);
788
            $roleAssignment = $this->roleDomainMapper->buildDomainUserRoleAssignmentObject(
789
                $spiRoleAssignment,
790
                $user,
791
                $role
792
            );
793
        } catch (APINotFoundException $e) {
794
            try {
795
                $userGroup = $userService->loadUserGroup($spiRoleAssignment->contentId);
796
                $roleAssignment = $this->roleDomainMapper->buildDomainUserGroupRoleAssignmentObject(
797
                    $spiRoleAssignment,
798
                    $userGroup,
799
                    $role
800
                );
801
            } catch (APINotFoundException $e) {
802
                // Do nothing
803
            }
804
        }
805
806
        return $roleAssignment;
807
    }
808
809
    /**
810
     * Returns the assigned user and user groups to this role.
811
     *
812
     * @param \eZ\Publish\API\Repository\Values\User\Role $role
813
     *
814
     * @return \eZ\Publish\API\Repository\Values\User\RoleAssignment[]
815
     *
816
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
817
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
818
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the authenticated user is not allowed to read a role
819
     */
820
    public function getRoleAssignments(APIRole $role): iterable
821
    {
822
        if (!$this->permissionResolver->canUser('role', 'read', $role)) {
823
            throw new UnauthorizedException('role', 'read');
824
        }
825
826
        $userService = $this->repository->getUserService();
827
        $spiRoleAssignments = $this->userHandler->loadRoleAssignmentsByRoleId($role->id);
828
        $roleAssignments = [];
829
830
        foreach ($spiRoleAssignments as $spiRoleAssignment) {
831
            // First check if the Role is assigned to a User
832
            // If no User is found, see if it belongs to a UserGroup
833
            try {
834
                $user = $userService->loadUser($spiRoleAssignment->contentId);
835
                $roleAssignments[] = $this->roleDomainMapper->buildDomainUserRoleAssignmentObject(
836
                    $spiRoleAssignment,
837
                    $user,
838
                    $role
839
                );
840
            } catch (APINotFoundException $e) {
841
                try {
842
                    $userGroup = $userService->loadUserGroup($spiRoleAssignment->contentId);
843
                    $roleAssignments[] = $this->roleDomainMapper->buildDomainUserGroupRoleAssignmentObject(
844
                        $spiRoleAssignment,
845
                        $userGroup,
846
                        $role
847
                    );
848
                } catch (APINotFoundException $e) {
849
                    // Do nothing
850
                }
851
            }
852
        }
853
854
        return $roleAssignments;
855
    }
856
857
    /**
858
     * @see \eZ\Publish\API\Repository\RoleService::getRoleAssignmentsForUser()
859
     *
860
     * @param \eZ\Publish\API\Repository\Values\User\User $user
861
     * @param bool $inherited
862
     *
863
     * @return iterable
864
     *
865
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
866
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
867
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
868
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
869
     */
870
    public function getRoleAssignmentsForUser(User $user, bool $inherited = false): iterable
871
    {
872
        $roleAssignments = [];
873
        $spiRoleAssignments = $this->userHandler->loadRoleAssignmentsByGroupId($user->id, $inherited);
874
        foreach ($spiRoleAssignments as $spiRoleAssignment) {
875
            $role = $this->loadRole($spiRoleAssignment->roleId);
876
877
            if (!$this->permissionResolver->canUser('role', 'read', $role)) {
878
                continue;
879
            }
880
881
            if (!$inherited || $spiRoleAssignment->contentId == $user->id) {
882
                $roleAssignments[] = $this->roleDomainMapper->buildDomainUserRoleAssignmentObject(
883
                    $spiRoleAssignment,
884
                    $user,
885
                    $role
886
                );
887
            } else {
888
                $userGroup = $this->repository->getUserService()->loadUserGroup($spiRoleAssignment->contentId);
889
                $roleAssignments[] = $this->roleDomainMapper->buildDomainUserGroupRoleAssignmentObject(
890
                    $spiRoleAssignment,
891
                    $userGroup,
892
                    $role
893
                );
894
            }
895
        }
896
897
        return $roleAssignments;
898
    }
899
900
    /**
901
     * Returns the roles assigned to the given user group, excluding the ones the current user is not allowed to read.
902
     *
903
     * @param \eZ\Publish\API\Repository\Values\User\UserGroup $userGroup
904
     *
905
     * @return \eZ\Publish\API\Repository\Values\User\UserGroupRoleAssignment[]
906
     *
907
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
908
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
909
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
910
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
911
     */
912
    public function getRoleAssignmentsForUserGroup(UserGroup $userGroup): iterable
913
    {
914
        $roleAssignments = [];
915
        $spiRoleAssignments = $this->userHandler->loadRoleAssignmentsByGroupId($userGroup->id);
916
        foreach ($spiRoleAssignments as $spiRoleAssignment) {
917
            $role = $this->loadRole($spiRoleAssignment->roleId);
918
919
            if ($this->permissionResolver->canUser('role', 'read', $role)) {
920
                $roleAssignments[] = $this->roleDomainMapper->buildDomainUserGroupRoleAssignmentObject(
921
                    $spiRoleAssignment,
922
                    $userGroup,
923
                    $role
924
                );
925
            }
926
        }
927
928
        return $roleAssignments;
929
    }
930
931
    /**
932
     * Instantiates a role create class.
933
     *
934
     * @param string $name
935
     *
936
     * @return \eZ\Publish\API\Repository\Values\User\RoleCreateStruct
937
     */
938
    public function newRoleCreateStruct(string $name): APIRoleCreateStruct
939
    {
940
        return new RoleCreateStruct(
941
            [
942
                'identifier' => $name,
943
                'policies' => [],
944
            ]
945
        );
946
    }
947
948
    /**
949
     * Instantiates a policy create class.
950
     *
951
     * @param string $module
952
     * @param string $function
953
     *
954
     * @return \eZ\Publish\API\Repository\Values\User\PolicyCreateStruct
955
     */
956
    public function newPolicyCreateStruct(string $module, string $function): APIPolicyCreateStruct
957
    {
958
        return new PolicyCreateStruct(
959
            [
960
                'module' => $module,
961
                'function' => $function,
962
                'limitations' => [],
963
            ]
964
        );
965
    }
966
967
    /**
968
     * Instantiates a policy update class.
969
     *
970
     * @return \eZ\Publish\API\Repository\Values\User\PolicyUpdateStruct
971
     */
972
    public function newPolicyUpdateStruct(): APIPolicyUpdateStruct
973
    {
974
        return new PolicyUpdateStruct(
975
            [
976
                'limitations' => [],
977
            ]
978
        );
979
    }
980
981
    /**
982
     * Instantiates a policy update class.
983
     *
984
     * @return \eZ\Publish\API\Repository\Values\User\RoleUpdateStruct
985
     */
986
    public function newRoleUpdateStruct(): RoleUpdateStruct
987
    {
988
        return new RoleUpdateStruct();
989
    }
990
991
    /**
992
     * Returns the LimitationType registered with the given identifier.
993
     *
994
     * Returns the correct implementation of API Limitation value object
995
     * based on provided identifier
996
     *
997
     * @param string $identifier
998
     *
999
     * @return \eZ\Publish\SPI\Limitation\Type
1000
     *
1001
     * @throws \RuntimeException if there is no LimitationType with $identifier
1002
     */
1003
    public function getLimitationType(string $identifier): Type
1004
    {
1005
        return $this->limitationService->getLimitationType($identifier);
1006
    }
1007
1008
    /**
1009
     * Returns the LimitationType's assigned to a given module/function.
1010
     *
1011
     * Typically used for:
1012
     *  - Internal validation limitation value use on Policies
1013
     *  - Role admin gui for editing policy limitations incl list limitation options via valueSchema()
1014
     *
1015
     * @param string $module Legacy name of "controller", it's a unique identifier like "content"
1016
     * @param string $function Legacy name of a controller "action", it's a unique within the controller like "read"
1017
     *
1018
     * @return \eZ\Publish\SPI\Limitation\Type[]
1019
     *
1020
     * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException If module/function to limitation type mapping
1021
     *                                                                 refers to a non existing identifier.
1022
     */
1023
    public function getLimitationTypesByModuleFunction(string $module, string $function): iterable
1024
    {
1025
        if (empty($this->settings['policyMap'][$module][$function])) {
1026
            return [];
1027
        }
1028
1029
        $types = [];
1030
        try {
1031
            foreach (array_keys($this->settings['policyMap'][$module][$function]) as $identifier) {
1032
                $types[$identifier] = $this->limitationService->getLimitationType($identifier);
1033
            }
1034
        } catch (LimitationNotFoundException $e) {
1035
            throw new BadStateException(
1036
                "{$module}/{$function}",
1037
                "policyMap configuration is referring to a non-existent identifier: {$identifier}",
1038
                $e
1039
            );
1040
        }
1041
1042
        return $types;
1043
    }
1044
1045
    /**
1046
     * Validates Policies and Limitations in Role create struct.
1047
     *
1048
     * @uses ::validatePolicy()
1049
     *
1050
     * @param \eZ\Publish\API\Repository\Values\User\RoleCreateStruct $roleCreateStruct
1051
     *
1052
     * @return \eZ\Publish\Core\FieldType\ValidationError[][][]
1053
     *
1054
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
1055
     */
1056
    protected function validateRoleCreateStruct(APIRoleCreateStruct $roleCreateStruct): iterable
1057
    {
1058
        $allErrors = [];
1059
        foreach ($roleCreateStruct->getPolicies() as $key => $policyCreateStruct) {
1060
            $errors = $this->validatePolicy(
1061
                $policyCreateStruct->module,
1062
                $policyCreateStruct->function,
1063
                $policyCreateStruct->getLimitations()
1064
            );
1065
1066
            if (!empty($errors)) {
1067
                $allErrors[$key] = $errors;
1068
            }
1069
        }
1070
1071
        return $allErrors;
1072
    }
1073
1074
    /**
1075
     * Validates Policy context: Limitations on a module and function.
1076
     *
1077
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If the same limitation is repeated or if
1078
     *                                                                   limitation is not allowed on module/function
1079
     *
1080
     * @param string $module
1081
     * @param string $function
1082
     * @param \eZ\Publish\API\Repository\Values\User\Limitation[] $limitations
1083
     *
1084
     * @return \eZ\Publish\Core\FieldType\ValidationError[][]
1085
     */
1086
    protected function validatePolicy(string $module, string $function, array $limitations): iterable
1087
    {
1088
        if ($module !== '*' && $function !== '*' && !empty($limitations)) {
1089
            $limitationSet = [];
1090
            foreach ($limitations as $limitation) {
1091
                if (isset($limitationSet[$limitation->getIdentifier()])) {
1092
                    throw new InvalidArgumentException(
1093
                        'limitations',
1094
                        "{$limitation->getIdentifier()}' was found multiple times among Limitations"
1095
                    );
1096
                }
1097
1098
                if (!isset($this->settings['policyMap'][$module][$function][$limitation->getIdentifier()])) {
1099
                    throw new InvalidArgumentException(
1100
                        'policy',
1101
                        "Limitation '{$limitation->getIdentifier()}' is not applicable on '{$module}/{$function}'"
1102
                    );
1103
                }
1104
1105
                $limitationSet[$limitation->getIdentifier()] = true;
1106
            }
1107
        }
1108
1109
        return $this->limitationService->validateLimitations($limitations);
1110
    }
1111
1112
    /**
1113
     * Validate that assignments not already exists and filter validations against existing.
1114
     *
1115
     * @param int $contentId
1116
     * @param \eZ\Publish\SPI\Persistence\User\Role $spiRole
1117
     * @param array|null $limitation
1118
     *
1119
     * @return array[]|null Filtered version of $limitation
1120
     *
1121
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If assignment already exists
1122
     */
1123
    protected function checkAssignmentAndFilterLimitationValues(
1124
        int $contentId,
1125
        SPIRole $spiRole,
1126
        ?array $limitation = null
1127
    ): ?array {
1128
        $spiRoleAssignments = $this->userHandler->loadRoleAssignmentsByGroupId($contentId);
1129
        foreach ($spiRoleAssignments as $spiAssignment) {
1130
            // Ignore assignments to other roles
1131
            if ($spiAssignment->roleId !== $spiRole->id) {
1132
                continue;
1133
            }
1134
1135
            // Throw if Role is already assigned without limitations
1136
            if ($spiAssignment->limitationIdentifier === null) {
1137
                throw new InvalidArgumentException(
1138
                    '$role',
1139
                    "Role '{$spiRole->id}' already assigned without Limitations"
1140
                );
1141
            }
1142
1143
            // Ignore if we are going to assign without limitations
1144
            if ($limitation === null) {
1145
                continue;
1146
            }
1147
1148
            // Ignore if not assigned with same limitation identifier
1149
            if (!isset($limitation[$spiAssignment->limitationIdentifier])) {
1150
                continue;
1151
            }
1152
1153
            // Throw if Role is already assigned with all the same limitations
1154
            $newValues = array_diff($limitation[$spiAssignment->limitationIdentifier], $spiAssignment->values);
1155
            if (empty($newValues)) {
1156
                throw new InvalidArgumentException(
1157
                    '$role',
1158
                    "Role '{$spiRole->id}' is already assigned with the same '{$spiAssignment->limitationIdentifier}' value"
1159
                );
1160
            }
1161
1162
            // Continue using the filtered list of limitations
1163
            $limitation[$spiAssignment->limitationIdentifier] = $newValues;
1164
        }
1165
1166
        return $limitation;
1167
    }
1168
1169
    /**
1170
     * Deletes a policy.
1171
     *
1172
     * Used by {@link removePolicy()} and {@link deletePolicy()}
1173
     *
1174
     * @param \eZ\Publish\API\Repository\Values\User\Policy $policy
1175
     *
1176
     * @throws \Exception
1177
     */
1178
    protected function internalDeletePolicy(APIPolicy $policy): void
1179
    {
1180
        $this->repository->beginTransaction();
1181
        try {
1182
            $this->userHandler->deletePolicy($policy->id, $policy->roleId);
1183
            $this->repository->commit();
1184
        } catch (Exception $e) {
1185
            $this->repository->rollback();
1186
            throw $e;
1187
        }
1188
    }
1189
}
1190