Completed
Push — EZP-31383-roles-copying ( f7f048...ab92ab )
by
unknown
29:41 queued 16:25
created

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