Completed
Push — apply-code-style ( 1a43bf...37cc85 )
by
unknown
45:48
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
     *
76
     * @see testCreateAndPublishContent
77
     */
78
    public function providerForCreateAndPublishContent(): array
79
    {
80
        // $names (as admin), $allowedTranslationsList (editor limitations)
81
        return [
82
            [
83
                ['ger-DE' => 'German Folder'],
84
                ['ger-DE'],
85
            ],
86
            [
87
                ['ger-DE' => 'German Folder', 'eng-GB' => 'British Folder'],
88
                ['ger-DE', 'eng-GB'],
89
            ],
90
        ];
91
    }
92
93
    /**
94
     * Test creating and publishing a fresh Content item in a language restricted by LanguageLimitation.
95
     *
96
     * @param array $names
97
     * @param array $allowedTranslationsList
98
     *
99
     * @dataProvider providerForCreateAndPublishContent
100
     *
101
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
102
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
103
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
104
     */
105
    public function testCreateAndPublishContent(array $names, array $allowedTranslationsList): void
106
    {
107
        $repository = $this->getRepository();
108
        $repository->getPermissionResolver()->setCurrentUserReference(
109
            $this->createEditorUserWithLanguageLimitation($allowedTranslationsList)
110
        );
111
112
        $folder = $this->createFolder($names, 2);
113
114
        foreach ($names as $languageCode => $translatedName) {
115
            self::assertEquals(
116
                $translatedName,
117
                $folder->getField('name', $languageCode)->value->text
118
            );
119
        }
120
    }
121
122
    /**
123
     * @covers \eZ\Publish\API\Repository\PermissionResolver::canUser
124
     *
125
     * @dataProvider providerForCanUserWithLimitationTargets
126
     *
127
     * @param array $folderNames names of a folder to create as test content
128
     * @param array $allowedTranslationsList a list of language codes of translations a user is allowed to edit
129
     * @param \eZ\Publish\SPI\Limitation\Target[] $targets
130
     * @param bool $expectedCanUserResult
131
     *
132
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
133
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
134
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
135
     */
136
    public function testCanUserWithLimitationTargets(
137
        string $policyModule,
138
        string $policyFunction,
139
        array $folderNames,
140
        array $allowedTranslationsList,
141
        array $targets,
142
        bool $expectedCanUserResult
143
    ): void {
144
        $repository = $this->getRepository();
145
146
        // prepare test data as an admin
147
        $content = $this->createFolder($folderNames, 2);
148
149
        $permissionResolver = $repository->getPermissionResolver();
150
        $permissionResolver->setCurrentUserReference(
151
            $this->createEditorUserWithLanguageLimitation($allowedTranslationsList)
152
        );
153
154
        $actualCanUserResult = $permissionResolver->canUser(
155
            $policyModule,
156
            $policyFunction,
157
            $content->contentInfo,
158
            $targets
159
        );
160
161
        self::assertSame(
162
            $expectedCanUserResult,
163
            $actualCanUserResult,
164
            "canUser('{$policyModule}', '{$policyFunction}') returned unexpected result"
165
        );
166
    }
167
168
    /**
169
     * Data provider for testEditContentWithLimitationTargets.
170
     *
171
     * @return array
172
     *
173
     * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
174
     */
175
    public function providerForCanUserWithLimitationTargets(): array
176
    {
177
        return [
178
            'Editing a content before translating it' => [
179
                'content',
180
                'edit',
181
                ['eng-GB' => 'BrE Folder'],
182
                ['ger-DE'],
183
                [
184
                    (new VersionBuilder())
185
                        ->translateToAnyLanguageOf(['ger-DE'])
186
                        ->build(),
187
                ],
188
                true,
189
            ],
190
            'Publishing the specific translation of a content item' => [
191
                'content',
192
                'publish',
193
                ['eng-GB' => 'BrE Folder', 'ger-DE' => 'DE Folder'],
194
                ['ger-DE'],
195
                [
196
                    (new VersionBuilder())
197
                        ->publishTranslations(['ger-DE'])
198
                        ->build(),
199
                ],
200
                true,
201
            ],
202
            'Not being able to edit a content before translating it' => [
203
                'content',
204
                'edit',
205
                ['eng-GB' => 'BrE Folder'],
206
                ['ger-DE'],
207
                [
208
                    (new VersionBuilder())
209
                        ->translateToAnyLanguageOf(['eng-GB'])
210
                        ->build(),
211
                ],
212
                false,
213
            ],
214
            'Not being able to publish the specific translation of a content item' => [
215
                'content',
216
                'publish',
217
                ['eng-GB' => 'BrE Folder', 'ger-DE' => 'DE Folder'],
218
                ['ger-DE'],
219
                [
220
                    (new VersionBuilder())
221
                        ->publishTranslations(['eng-GB'])
222
                        ->build(),
223
                ],
224
                false,
225
            ],
226
        ];
227
    }
228
229
    /**
230
     * Data provider for testPublishVersionWithLanguageLimitation.
231
     *
232
     * @return array
233
     *
234
     * @see testPublishVersionIsNotAllowedIfModifiedOtherTranslations
235
     * @see testPublishVersion
236
     */
237
    public function providerForPublishVersionWithLanguageLimitation(): array
238
    {
239
        // $names (as admin), $namesToUpdate (as editor), $allowedTranslationsList (editor limitations)
240
        return [
241
            [
242
                ['eng-US' => 'American Folder'],
243
                ['ger-DE' => 'Updated German Folder'],
244
                ['ger-DE'],
245
            ],
246
            [
247
                ['eng-US' => 'American Folder', 'ger-DE' => 'German Folder'],
248
                ['ger-DE' => 'Updated German Folder'],
249
                ['ger-DE'],
250
            ],
251
            [
252
                [
253
                    'eng-US' => 'American Folder',
254
                    'eng-GB' => 'British Folder',
255
                    'ger-DE' => 'German Folder',
256
                ],
257
                ['ger-DE' => 'Updated German Folder', 'eng-GB' => 'British Folder'],
258
                ['ger-DE', 'eng-GB'],
259
            ],
260
            [
261
                ['eng-US' => 'American Folder', 'ger-DE' => 'German Folder'],
262
                ['ger-DE' => 'Updated German Folder', 'eng-GB' => 'British Folder'],
263
                ['ger-DE', 'eng-GB'],
264
            ],
265
        ];
266
    }
267
268
    /**
269
     * Test publishing Version with translations restricted by LanguageLimitation.
270
     *
271
     * @param array $names
272
     * @param array $namesToUpdate
273
     * @param array $allowedTranslationsList
274
     *
275
     * @dataProvider providerForPublishVersionWithLanguageLimitation
276
     *
277
     * @covers \eZ\Publish\API\Repository\ContentService::createContentDraft
278
     * @covers \eZ\Publish\API\Repository\ContentService::updateContent
279
     * @covers \eZ\Publish\API\Repository\ContentService::publishVersion
280
     *
281
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
282
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
283
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
284
     * @throws \Exception
285
     */
286
    public function testPublishVersion(
287
        array $names,
288
        array $namesToUpdate,
289
        array $allowedTranslationsList
290
    ): void {
291
        $repository = $this->getRepository();
292
        $contentService = $repository->getContentService();
293
294
        $folder = $this->createFolder($names, 2);
295
296
        $repository->getPermissionResolver()->setCurrentUserReference(
297
            $this->createEditorUserWithLanguageLimitation($allowedTranslationsList)
298
        );
299
300
        $folderDraft = $contentService->createContentDraft($folder->contentInfo);
301
        $folderUpdateStruct = $contentService->newContentUpdateStruct();
302
        // set modified translation of Version to the first modified as multiple are not supported yet
303
        $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...
304
        foreach ($namesToUpdate as $languageCode => $translatedName) {
305
            $folderUpdateStruct->setField('name', $translatedName, $languageCode);
306
        }
307
        $folderDraft = $contentService->updateContent(
308
            $folderDraft->getVersionInfo(),
309
            $folderUpdateStruct
310
        );
311
        $contentService->publishVersion($folderDraft->getVersionInfo());
312
313
        $folder = $contentService->loadContent($folder->id);
314
        $updatedNames = array_merge($names, $namesToUpdate);
315
        foreach ($updatedNames as $languageCode => $expectedValue) {
316
            self::assertEquals(
317
                $expectedValue,
318
                $folder->getField('name', $languageCode)->value->text,
319
                "Unexpected Field value for {$languageCode}"
320
            );
321
        }
322
    }
323
324
    /**
325
     * Test that publishing version with changes to translations outside limitation values throws unauthorized exception.
326
     *
327
     * @param array $names
328
     *
329
     * @dataProvider providerForPublishVersionWithLanguageLimitation
330
     *
331
     * @covers \eZ\Publish\API\Repository\ContentService::createContentDraft
332
     * @covers \eZ\Publish\API\Repository\ContentService::updateContent
333
     * @covers \eZ\Publish\API\Repository\ContentService::publishVersion
334
     *
335
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
336
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
337
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
338
     */
339
    public function testPublishVersionIsNotAllowedIfModifiedOtherTranslations(array $names): void
340
    {
341
        $repository = $this->getRepository();
342
        $contentService = $repository->getContentService();
343
344
        $folder = $this->createFolder($names, 2);
345
        $folderDraft = $contentService->createContentDraft($folder->contentInfo);
346
        $folderUpdateStruct = $contentService->newContentUpdateStruct();
347
        $folderUpdateStruct->setField('name', 'Updated American Folder', 'eng-US');
348
        $folderDraft = $contentService->updateContent(
349
            $folderDraft->getVersionInfo(),
350
            $folderUpdateStruct
351
        );
352
353
        // switch context to the user not allowed to publish eng-US
354
        $repository->getPermissionResolver()->setCurrentUserReference(
355
            $this->createEditorUserWithLanguageLimitation(['ger-DE'])
356
        );
357
358
        $this->expectException(UnauthorizedException::class);
359
        $contentService->publishVersion($folderDraft->getVersionInfo());
360
    }
361
362
    /**
363
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
364
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
365
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
366
     */
367
    public function testPublishVersionTranslation(): void
368
    {
369
        $repository = $this->getRepository();
370
        $contentService = $repository->getContentService();
371
        $permissionResolver = $repository->getPermissionResolver();
372
373
        $draft = $this->createMultilingualFolderDraft($contentService);
374
375
        $contentUpdateStruct = $contentService->newContentUpdateStruct();
376
377
        $contentUpdateStruct->setField('name', 'Draft 1 DE', self::GER_DE);
378
379
        $contentService->updateContent($draft->versionInfo, $contentUpdateStruct);
380
381
        $admin = $permissionResolver->getCurrentUserReference();
382
        $permissionResolver->setCurrentUserReference($this->createEditorUserWithLanguageLimitation([self::GER_DE]));
383
384
        $contentService->publishVersion($draft->versionInfo, [self::GER_DE]);
385
386
        $permissionResolver->setCurrentUserReference($admin);
387
        $content = $contentService->loadContent($draft->contentInfo->id);
388
        $this->assertEquals(
389
            [
390
                self::ENG_US => 'Published US',
391
                self::GER_DE => 'Draft 1 DE',
392
            ],
393
            $content->fields['name']
394
        );
395
    }
396
397
    /**
398
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
399
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
400
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
401
     */
402
    public function testPublishVersionTranslationIsNotAllowed(): void
403
    {
404
        $repository = $this->getRepository();
405
        $contentService = $repository->getContentService();
406
        $permissionResolver = $repository->getPermissionResolver();
407
408
        $draft = $this->createMultilingualFolderDraft($contentService);
409
410
        $contentUpdateStruct = $contentService->newContentUpdateStruct();
411
412
        $contentUpdateStruct->setField('name', 'Draft 1 EN', self::ENG_US);
413
414
        $contentService->updateContent($draft->versionInfo, $contentUpdateStruct);
415
416
        $permissionResolver->setCurrentUserReference($this->createEditorUserWithLanguageLimitation([self::GER_DE]));
417
418
        $this->expectException(UnauthorizedException::class);
419
        $contentService->publishVersion($draft->versionInfo, [self::ENG_US]);
420
    }
421
422
    /**
423
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
424
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
425
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
426
     */
427
    public function testPublishVersionTranslationIsNotAllowedWithTwoEditors(): void
428
    {
429
        $repository = $this->getRepository();
430
        $contentService = $repository->getContentService();
431
        $permissionResolver = $repository->getPermissionResolver();
432
433
        $editorDE = $this->createEditorUserWithLanguageLimitation([self::GER_DE], 'editor-de');
434
        $editorUS = $this->createEditorUserWithLanguageLimitation([self::ENG_US], 'editor-us');
435
436
        // German editor publishes content in German language
437
        $permissionResolver->setCurrentUserReference($editorDE);
438
439
        $folder = $this->createFolder([self::GER_DE => 'German Folder'], 2);
440
441
        // American editor creates and saves English draft
442
        $permissionResolver->setCurrentUserReference($editorUS);
443
444
        $folder = $contentService->loadContent($folder->id);
445
        $folderDraft = $contentService->createContentDraft($folder->contentInfo);
446
        $folderUpdateStruct = $contentService->newContentUpdateStruct();
447
        $folderUpdateStruct->setField('name', 'English Folder', self::ENG_US);
448
        $folderDraft = $contentService->updateContent(
449
            $folderDraft->versionInfo,
450
            $folderUpdateStruct
451
        );
452
453
        // German editor tries to publish English translation
454
        $permissionResolver->setCurrentUserReference($editorDE);
455
        $folderDraftVersionInfo = $contentService->loadVersionInfo(
456
            $folderDraft->contentInfo,
457
            $folderDraft->versionInfo->versionNo
458
        );
459
        self::assertTrue($folderDraftVersionInfo->isDraft());
460
        $this->expectException(UnauthorizedException::class);
461
        $this->expectExceptionMessage("User does not have access to 'publish' 'content'");
462
        $contentService->publishVersion($folderDraftVersionInfo, [self::ENG_US]);
463
    }
464
465
    /**
466
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
467
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
468
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
469
     */
470
    public function testPublishVersionTranslationWhenUserHasNoAccessToAllLanguages(): void
471
    {
472
        $repository = $this->getRepository();
473
        $contentService = $repository->getContentService();
474
        $permissionResolver = $repository->getPermissionResolver();
475
476
        $draft = $this->createMultilingualFolderDraft($contentService);
477
478
        $contentUpdateStruct = $contentService->newContentUpdateStruct();
479
480
        $contentUpdateStruct->setField('name', 'Draft 1 DE', self::GER_DE);
481
        $contentUpdateStruct->setField('name', 'Draft 1 GB', self::ENG_GB);
482
483
        $contentService->updateContent($draft->versionInfo, $contentUpdateStruct);
484
485
        $permissionResolver->setCurrentUserReference(
486
            $this->createEditorUserWithLanguageLimitation([self::GER_DE])
487
        );
488
        $this->expectException(UnauthorizedException::class);
489
        $this->expectExceptionMessage("User does not have access to 'publish' 'content'");
490
        $contentService->publishVersion($draft->versionInfo, [self::GER_DE, self::ENG_GB]);
491
    }
492
493
    /**
494
     * @param \eZ\Publish\API\Repository\ContentService $contentService
495
     *
496
     * @return \eZ\Publish\API\Repository\Values\Content\Content
497
     *
498
     * @throws \eZ\Publish\API\Repository\Exceptions\ForbiddenException
499
     * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
500
     * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
501
     */
502
    private function createMultilingualFolderDraft(ContentService $contentService): Content
503
    {
504
        $publishedContent = $this->createFolder(
505
            [
506
                self::ENG_US => 'Published US',
507
                self::GER_DE => 'Published DE',
508
            ],
509
            $this->generateId('location', 2)
510
        );
511
512
        return $contentService->createContentDraft($publishedContent->contentInfo);
513
    }
514
}
515