Completed
Push — EZP-31383-roles-copying ( d0932a )
by
unknown
12:44
created

RoleService::loadRoleByIdentifier()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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