Completed
Push — EZP-31383-role-copying ( 805768 )
by
unknown
15:57 queued 12s
created

RoleService::validateRoleCopyStruct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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