Completed
Push — ezp_30981_content_info_proxy ( 5d5afd...47f956 )
by
unknown
15:40
created

testCanUserWithLimitationTargets()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 6
dl 0
loc 31
rs 9.424
c 0
b 0
f 0
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
declare(strict_types=1);
8
9
namespace eZ\Publish\API\Repository\Tests\Values\User\Limitation;
10
11
use eZ\Publish\API\Repository\ContentService;
12
use eZ\Publish\API\Repository\Exceptions\UnauthorizedException;
13
use eZ\Publish\API\Repository\Tests\BaseTest;
14
use eZ\Publish\API\Repository\Values\Content\Content;
15
use eZ\Publish\API\Repository\Values\User\Limitation\LanguageLimitation;
16
use eZ\Publish\API\Repository\Values\User\User;
17
use eZ\Publish\SPI\Limitation\Target\Builder\VersionBuilder;
18
19
/**
20
 * Test cases for ContentService APIs calls made by user with LanguageLimitation on chosen policies.
21
 *
22
 * @uses \eZ\Publish\API\Repository\Values\User\Limitation\LanguageLimitation
23
 *
24
 * @group integration
25
 * @group authorization
26
 * @group language-limited-content-mgm
27
 */
28
class LanguageLimitationTest extends BaseTest
29
{
30
    /** @var string */
31
    private const ENG_US = 'eng-US';
32
33
    /** @var string */
34
    private const ENG_GB = 'eng-GB';
35
36
    /** @var string */
37
    private const GER_DE = 'ger-DE';
38
39
    /**
40
     * Create editor who is allowed to modify only specific translations of a Content item.
41
     *
42
     * @param array $allowedTranslationsList list of translations (language codes) which editor can modify.
43
     * @param string $login
44
     *
45
     * @return \eZ\Publish\API\Repository\Values\User\User
46
     *
47
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
48
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
49
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
50
     */
51
    private function createEditorUserWithLanguageLimitation(
52
        array $allowedTranslationsList,
53
        string $login = 'editor'
54
    ): User {
55
        $limitations = [
56
            // limitation for specific translations
57
            new LanguageLimitation(['limitationValues' => $allowedTranslationsList]),
58
        ];
59
60
        return $this->createUserWithPolicies(
61
            $login,
62
            [
63
                ['module' => 'content', 'function' => 'read'],
64
                ['module' => 'content', 'function' => 'versionread'],
65
                ['module' => 'content', 'function' => 'view_embed'],
66
                ['module' => 'content', 'function' => 'create', 'limitations' => $limitations],
67
                ['module' => 'content', 'function' => 'edit', 'limitations' => $limitations],
68
                ['module' => 'content', 'function' => 'publish', 'limitations' => $limitations],
69
            ]
70
        );
71
    }
72
73
    /**
74
     * @return array
75
     * @see testCreateAndPublishContent
76
     */
77
    public function providerForCreateAndPublishContent(): array
78
    {
79
        // $names (as admin), $allowedTranslationsList (editor limitations)
80
        return [
81
            [
82
                ['ger-DE' => 'German Folder'],
83
                ['ger-DE'],
84
            ],
85
            [
86
                ['ger-DE' => 'German Folder', 'eng-GB' => 'British Folder'],
87
                ['ger-DE', 'eng-GB'],
88
            ],
89
        ];
90
    }
91
92
    /**
93
     * Test creating and publishing a fresh Content item in a language restricted by LanguageLimitation.
94
     *
95
     * @param array $names
96
     * @param array $allowedTranslationsList
97
     *
98
     * @dataProvider providerForCreateAndPublishContent
99
     *
100
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
101
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
102
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
103
     */
104
    public function testCreateAndPublishContent(array $names, array $allowedTranslationsList): void
105
    {
106
        $repository = $this->getRepository();
107
        $repository->getPermissionResolver()->setCurrentUserReference(
108
            $this->createEditorUserWithLanguageLimitation($allowedTranslationsList)
109
        );
110
111
        $folder = $this->createFolder($names, 2);
112
113
        foreach ($names as $languageCode => $translatedName) {
114
            self::assertEquals(
115
                $translatedName,
116
                $folder->getField('name', $languageCode)->value->text
117
            );
118
        }
119
    }
120
121
    /**
122
     * @covers \eZ\Publish\API\Repository\PermissionResolver::canUser
123
     *
124
     * @dataProvider providerForCanUserWithLimitationTargets
125
     *
126
     * @param array $folderNames names of a folder to create as test content
127
     * @param array $allowedTranslationsList a list of language codes of translations a user is allowed to edit
128
     * @param \eZ\Publish\SPI\Limitation\Target[] $targets
129
     * @param bool $expectedCanUserResult
130
     *
131
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
132
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
133
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
134
     */
135
    public function testCanUserWithLimitationTargets(
136
        string $policyModule,
137
        string $policyFunction,
138
        array $folderNames,
139
        array $allowedTranslationsList,
140
        array $targets,
141
        bool $expectedCanUserResult
142
    ): void {
143
        $repository = $this->getRepository();
144
145
        // prepare test data as an admin
146
        $content = $this->createFolder($folderNames, 2);
147
148
        $permissionResolver = $repository->getPermissionResolver();
149
        $permissionResolver->setCurrentUserReference(
150
            $this->createEditorUserWithLanguageLimitation($allowedTranslationsList)
151
        );
152
153
        $actualCanUserResult = $permissionResolver->canUser(
154
            $policyModule,
155
            $policyFunction,
156
            $content->contentInfo,
157
            $targets
158
        );
159
160
        self::assertSame(
161
            $expectedCanUserResult,
162
            $actualCanUserResult,
163
            "canUser('{$policyModule}', '{$policyFunction}') returned unexpected result"
164
        );
165
    }
166
167
    /**
168
     * Data provider for testEditContentWithLimitationTargets.
169
     *
170
     * @return array
171
     *
172
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
173
     */
174
    public function providerForCanUserWithLimitationTargets(): array
175
    {
176
        return [
177
            'Editing a content before translating it' => [
178
                'content',
179
                'edit',
180
                ['eng-GB' => 'BrE Folder'],
181
                ['ger-DE'],
182
                [
183
                    (new VersionBuilder())
184
                        ->translateToAnyLanguageOf(['ger-DE'])
185
                        ->build(),
186
                ],
187
                true,
188
            ],
189
            'Publishing the specific translation of a content item' => [
190
                'content',
191
                'publish',
192
                ['eng-GB' => 'BrE Folder', 'ger-DE' => 'DE Folder'],
193
                ['ger-DE'],
194
                [
195
                    (new VersionBuilder())
196
                        ->publishTranslations(['ger-DE'])
197
                        ->build(),
198
                ],
199
                true,
200
            ],
201
            'Not being able to edit a content before translating it' => [
202
                'content',
203
                'edit',
204
                ['eng-GB' => 'BrE Folder'],
205
                ['ger-DE'],
206
                [
207
                    (new VersionBuilder())
208
                        ->translateToAnyLanguageOf(['eng-GB'])
209
                        ->build(),
210
                ],
211
                false,
212
            ],
213
            'Not being able to publish the specific translation of a content item' => [
214
                'content',
215
                'publish',
216
                ['eng-GB' => 'BrE Folder', 'ger-DE' => 'DE Folder'],
217
                ['ger-DE'],
218
                [
219
                    (new VersionBuilder())
220
                        ->publishTranslations(['eng-GB'])
221
                        ->build(),
222
                ],
223
                false,
224
            ],
225
        ];
226
    }
227
228
    /**
229
     * Data provider for testPublishVersionWithLanguageLimitation.
230
     *
231
     * @return array
232
     * @see testPublishVersionIsNotAllowedIfModifiedOtherTranslations
233
     *
234
     * @see testPublishVersion
235
     */
236
    public function providerForPublishVersionWithLanguageLimitation(): array
237
    {
238
        // $names (as admin), $namesToUpdate (as editor), $allowedTranslationsList (editor limitations)
239
        return [
240
            [
241
                ['eng-US' => 'American Folder'],
242
                ['ger-DE' => 'Updated German Folder'],
243
                ['ger-DE'],
244
            ],
245
            [
246
                ['eng-US' => 'American Folder', 'ger-DE' => 'German Folder'],
247
                ['ger-DE' => 'Updated German Folder'],
248
                ['ger-DE'],
249
            ],
250
            [
251
                [
252
                    'eng-US' => 'American Folder',
253
                    'eng-GB' => 'British Folder',
254
                    'ger-DE' => 'German Folder',
255
                ],
256
                ['ger-DE' => 'Updated German Folder', 'eng-GB' => 'British Folder'],
257
                ['ger-DE', 'eng-GB'],
258
            ],
259
            [
260
                ['eng-US' => 'American Folder', 'ger-DE' => 'German Folder'],
261
                ['ger-DE' => 'Updated German Folder', 'eng-GB' => 'British Folder'],
262
                ['ger-DE', 'eng-GB'],
263
            ],
264
        ];
265
    }
266
267
    /**
268
     * Test publishing Version with translations restricted by LanguageLimitation.
269
     *
270
     * @param array $names
271
     * @param array $namesToUpdate
272
     * @param array $allowedTranslationsList
273
     *
274
     * @dataProvider providerForPublishVersionWithLanguageLimitation
275
     *
276
     * @covers \eZ\Publish\API\Repository\ContentService::createContentDraft
277
     * @covers \eZ\Publish\API\Repository\ContentService::updateContent
278
     * @covers \eZ\Publish\API\Repository\ContentService::publishVersion
279
     *
280
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
281
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
282
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
283
     * @throws \Exception
284
     */
285
    public function testPublishVersion(
286
        array $names,
287
        array $namesToUpdate,
288
        array $allowedTranslationsList
289
    ): void {
290
        $repository = $this->getRepository();
291
        $contentService = $repository->getContentService();
292
293
        $folder = $this->createFolder($names, 2);
294
295
        $repository->getPermissionResolver()->setCurrentUserReference(
296
            $this->createEditorUserWithLanguageLimitation($allowedTranslationsList)
297
        );
298
299
        $folderDraft = $contentService->createContentDraft($folder->contentInfo);
300
        $folderUpdateStruct = $contentService->newContentUpdateStruct();
301
        // set modified translation of Version to the first modified as multiple are not supported yet
302
        $folderUpdateStruct->initialLanguageCode = array_keys($namesToUpdate)[0];
0 ignored issues
show
Documentation Bug introduced by
It seems like array_keys($namesToUpdate)[0] can also be of type integer. However, the property $initialLanguageCode is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
303
        foreach ($namesToUpdate as $languageCode => $translatedName) {
304
            $folderUpdateStruct->setField('name', $translatedName, $languageCode);
305
        }
306
        $folderDraft = $contentService->updateContent(
307
            $folderDraft->getVersionInfo(),
308
            $folderUpdateStruct
309
        );
310
        $contentService->publishVersion($folderDraft->getVersionInfo());
311
312
        $folder = $contentService->loadContent($folder->id);
313
        $updatedNames = array_merge($names, $namesToUpdate);
314
        foreach ($updatedNames as $languageCode => $expectedValue) {
315
            self::assertEquals(
316
                $expectedValue,
317
                $folder->getField('name', $languageCode)->value->text,
318
                "Unexpected Field value for {$languageCode}"
319
            );
320
        }
321
    }
322
323
    /**
324
     * Test that publishing version with changes to translations outside limitation values throws unauthorized exception.
325
     *
326
     * @param array $names
327
     *
328
     * @dataProvider providerForPublishVersionWithLanguageLimitation
329
     *
330
     * @covers \eZ\Publish\API\Repository\ContentService::createContentDraft
331
     * @covers \eZ\Publish\API\Repository\ContentService::updateContent
332
     * @covers \eZ\Publish\API\Repository\ContentService::publishVersion
333
     *
334
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
335
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
336
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
337
     */
338
    public function testPublishVersionIsNotAllowedIfModifiedOtherTranslations(array $names): void
339
    {
340
        $repository = $this->getRepository();
341
        $contentService = $repository->getContentService();
342
343
        $folder = $this->createFolder($names, 2);
344
        $folderDraft = $contentService->createContentDraft($folder->contentInfo);
345
        $folderUpdateStruct = $contentService->newContentUpdateStruct();
346
        $folderUpdateStruct->setField('name', 'Updated American Folder', 'eng-US');
347
        $folderDraft = $contentService->updateContent(
348
            $folderDraft->getVersionInfo(),
349
            $folderUpdateStruct
350
        );
351
352
        // switch context to the user not allowed to publish eng-US
353
        $repository->getPermissionResolver()->setCurrentUserReference(
354
            $this->createEditorUserWithLanguageLimitation(['ger-DE'])
355
        );
356
357
        $this->expectException(UnauthorizedException::class);
358
        $contentService->publishVersion($folderDraft->getVersionInfo());
359
    }
360
361
    /**
362
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
363
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
364
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
365
     */
366
    public function testPublishVersionTranslation(): void
367
    {
368
        $repository = $this->getRepository();
369
        $contentService = $repository->getContentService();
370
        $permissionResolver = $repository->getPermissionResolver();
371
372
        $draft = $this->createMultilingualFolderDraft($contentService);
373
374
        $contentUpdateStruct = $contentService->newContentUpdateStruct();
375
376
        $contentUpdateStruct->setField('name', 'Draft 1 DE', self::GER_DE);
377
378
        $contentService->updateContent($draft->versionInfo, $contentUpdateStruct);
379
380
        $admin = $permissionResolver->getCurrentUserReference();
381
        $permissionResolver->setCurrentUserReference($this->createEditorUserWithLanguageLimitation([self::GER_DE]));
382
383
        $contentService->publishVersion($draft->versionInfo, [self::GER_DE]);
384
385
        $permissionResolver->setCurrentUserReference($admin);
386
        $content = $contentService->loadContent($draft->contentInfo->id);
387
        $this->assertEquals(
388
            [
389
                self::ENG_US => 'Published US',
390
                self::GER_DE => 'Draft 1 DE',
391
            ],
392
            $content->fields['name']
393
        );
394
    }
395
396
    /**
397
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
398
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
399
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
400
     */
401
    public function testPublishVersionTranslationIsNotAllowed(): void
402
    {
403
        $repository = $this->getRepository();
404
        $contentService = $repository->getContentService();
405
        $permissionResolver = $repository->getPermissionResolver();
406
407
        $draft = $this->createMultilingualFolderDraft($contentService);
408
409
        $contentUpdateStruct = $contentService->newContentUpdateStruct();
410
411
        $contentUpdateStruct->setField('name', 'Draft 1 EN', self::ENG_US);
412
413
        $contentService->updateContent($draft->versionInfo, $contentUpdateStruct);
414
415
        $permissionResolver->setCurrentUserReference($this->createEditorUserWithLanguageLimitation([self::GER_DE]));
416
417
        $this->expectException(UnauthorizedException::class);
418
        $contentService->publishVersion($draft->versionInfo, [self::ENG_US]);
419
    }
420
421
    /**
422
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
423
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
424
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
425
     */
426
    public function testPublishVersionTranslationIsNotAllowedWithTwoEditors(): void
427
    {
428
        $repository = $this->getRepository();
429
        $contentService = $repository->getContentService();
430
        $permissionResolver = $repository->getPermissionResolver();
431
432
        $editorDE = $this->createEditorUserWithLanguageLimitation([self::GER_DE], 'editor-de');
433
        $editorUS = $this->createEditorUserWithLanguageLimitation([self::ENG_US], 'editor-us');
434
435
        // German editor publishes content in German language
436
        $permissionResolver->setCurrentUserReference($editorDE);
437
438
        $folder = $this->createFolder([self::GER_DE => 'German Folder'], 2);
439
440
        // American editor creates and saves English draft
441
        $permissionResolver->setCurrentUserReference($editorUS);
442
443
        $folder = $contentService->loadContent($folder->id);
444
        $folderDraft = $contentService->createContentDraft($folder->contentInfo);
445
        $folderUpdateStruct = $contentService->newContentUpdateStruct();
446
        $folderUpdateStruct->setField('name', 'English Folder', self::ENG_US);
447
        $folderDraft = $contentService->updateContent(
448
            $folderDraft->versionInfo,
449
            $folderUpdateStruct
450
        );
451
452
        // German editor tries to publish English translation
453
        $permissionResolver->setCurrentUserReference($editorDE);
454
        $folderDraftVersionInfo = $contentService->loadVersionInfo(
455
            $folderDraft->contentInfo,
456
            $folderDraft->versionInfo->versionNo
457
        );
458
        self::assertTrue($folderDraftVersionInfo->isDraft());
459
        $this->expectException(UnauthorizedException::class);
460
        $this->expectExceptionMessage("The User does not have the 'publish' 'content' permission");
461
        $contentService->publishVersion($folderDraftVersionInfo, [self::ENG_US]);
462
    }
463
464
    /**
465
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
466
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
467
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
468
     */
469
    public function testPublishVersionTranslationWhenUserHasNoAccessToAllLanguages(): void
470
    {
471
        $repository = $this->getRepository();
472
        $contentService = $repository->getContentService();
473
        $permissionResolver = $repository->getPermissionResolver();
474
475
        $draft = $this->createMultilingualFolderDraft($contentService);
476
477
        $contentUpdateStruct = $contentService->newContentUpdateStruct();
478
479
        $contentUpdateStruct->setField('name', 'Draft 1 DE', self::GER_DE);
480
        $contentUpdateStruct->setField('name', 'Draft 1 GB', self::ENG_GB);
481
482
        $contentService->updateContent($draft->versionInfo, $contentUpdateStruct);
483
484
        $permissionResolver->setCurrentUserReference(
485
            $this->createEditorUserWithLanguageLimitation([self::GER_DE])
486
        );
487
        $this->expectException(UnauthorizedException::class);
488
        $this->expectExceptionMessage("The User does not have the 'publish' 'content' permission");
489
        $contentService->publishVersion($draft->versionInfo, [self::GER_DE, self::ENG_GB]);
490
    }
491
492
    /**
493
     * @param \eZ\Publish\API\Repository\ContentService $contentService
494
     *
495
     * @return \eZ\Publish\API\Repository\Values\Content\Content
496
     *
497
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
498
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
499
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
500
     */
501
    private function createMultilingualFolderDraft(ContentService $contentService): Content
502
    {
503
        $publishedContent = $this->createFolder(
504
            [
505
                self::ENG_US => 'Published US',
506
                self::GER_DE => 'Published DE',
507
            ],
508
            $this->generateId('location', 2)
509
        );
510
511
        return $contentService->createContentDraft($publishedContent->contentInfo);
512
    }
513
}
514