Completed
Push — ezp-30928-as_a_developer_i_wan... ( 0570c5 )
by
unknown
13:48
created

RoleService::addPolicy()   B

Complexity

Conditions 10
Paths 8

Size

Total Lines 47

Duplication

Lines 47
Ratio 100 %

Importance

Changes 0
Metric Value
cc 10
nc 8
nop 2
dl 47
loc 47
rs 7.2896
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
A RoleService::loadRoles() 0 18 1
A RoleService::deleteRole() 17 17 3

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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