Completed
Push — ezp-30882-thumbnail ( aacd67...c2bd81 )
by
unknown
19:25 queued 36s
created

RoleService::unassignRoleFromUser()   B

Complexity

Conditions 6
Paths 13

Size

Total Lines 31

Duplication

Lines 31
Ratio 100 %

Importance

Changes 0
Metric Value
cc 6
nc 13
nop 2
dl 31
loc 31
rs 8.8017
c 0
b 0
f 0

1 Method

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