From 1dbc9215c97a5c22dc7f34a4e3a64d19e1eac151 Mon Sep 17 00:00:00 2001 From: Ahmet Bora Date: Mon, 19 Aug 2024 18:57:57 +0300 Subject: [PATCH] Fix language permissions #6497 --- i18n/translations/en.json | 3 + panel/src/components/Views/LanguagesView.vue | 24 +++- src/Cms/Language.php | 29 +++- src/Cms/Permissions.php | 3 +- tests/Cms/Languages/LanguageTest.php | 143 +++++++++++++++++++ tests/Cms/Languages/LanguagesTest.php | 2 + 6 files changed, 199 insertions(+), 5 deletions(-) diff --git a/i18n/translations/en.json b/i18n/translations/en.json index 1b54fa899d..1500d5907a 100644 --- a/i18n/translations/en.json +++ b/i18n/translations/en.json @@ -100,9 +100,12 @@ "error.form.notSaved": "The form could not be saved", "error.language.code": "Please enter a valid code for the language", + "error.language.create.permission": "You are not allowed to create a language", + "error.language.delete.permission": "You are not allowed to delete the language", "error.language.duplicate": "The language already exists", "error.language.name": "Please enter a valid name for the language", "error.language.notFound": "The language could not be found", + "error.language.update.permission": "You are not allowed to update the language", "error.layout.validation.block": "There's an error in block {blockIndex} in layout {layoutIndex}", "error.layout.validation.settings": "There's an error in layout {index} settings", diff --git a/panel/src/components/Views/LanguagesView.vue b/panel/src/components/Views/LanguagesView.vue index 9ec4605e9d..4ac4007c05 100644 --- a/panel/src/components/Views/LanguagesView.vue +++ b/panel/src/components/Views/LanguagesView.vue @@ -7,6 +7,7 @@ @@ -30,14 +31,23 @@ v-if="secondaryLanguages.length" :items="secondaryLanguages" /> - + {{ $t("languages.secondary.empty") }} @@ -66,12 +76,17 @@ export default { icon: "globe" }, link: () => { + if (!this.$permissions.languages.update) { + return null; + } + this.$dialog(`languages/${language.id}/update`); }, options: [ { icon: "edit", text: this.$t("edit"), + disabled: !this.$permissions.languages.update, click() { this.$dialog(`languages/${language.id}/update`); } @@ -79,7 +94,10 @@ export default { { icon: "trash", text: this.$t("delete"), - disabled: language.default && this.languages.length !== 1, + disabled: ( + (language.default && this.languages.length !== 1) || + !this.$permissions.languages.delete + ), click() { this.$dialog(`languages/${language.id}/delete`); } diff --git a/src/Cms/Language.php b/src/Cms/Language.php index d15eb7c5ed..50adfa4bc8 100644 --- a/src/Cms/Language.php +++ b/src/Cms/Language.php @@ -201,8 +201,17 @@ protected static function converter(string $from, string $to): bool */ public static function create(array $props) { + $kirby = App::instance(); + $user = $kirby->user(); + + if ( + $user === null || + $user->role()->permissions()->for('languages', 'create') === false + ) { + throw new PermissionException(['key' => 'language.create.permission']); + } + $props['code'] = Str::slug($props['code'] ?? null); - $kirby = App::instance(); $languages = $kirby->languages(); // make the first language the default language @@ -238,10 +247,18 @@ public static function create(array $props) public function delete(): bool { $kirby = App::instance(); + $user = $kirby->user(); $languages = $kirby->languages(); $code = $this->code(); $isLast = $languages->count() === 1; + if ( + $user === null || + $user->role()->permissions()->for('languages', 'delete') === false + ) { + throw new PermissionException(['key' => 'language.delete.permission']); + } + if (F::remove($this->root()) !== true) { throw new Exception('The language could not be deleted'); } @@ -648,6 +665,16 @@ public function url(): string */ public function update(array $props = null) { + $kirby = App::instance(); + $user = $kirby->user(); + + if ( + $user === null || + $user->role()->permissions()->for('languages', 'update') === false + ) { + throw new PermissionException(['key' => 'language.update.permission']); + } + // don't change the language code unset($props['code']); diff --git a/src/Cms/Permissions.php b/src/Cms/Permissions.php index 4a0137965b..b27fc74a0e 100644 --- a/src/Cms/Permissions.php +++ b/src/Cms/Permissions.php @@ -44,7 +44,8 @@ class Permissions ], 'languages' => [ 'create' => true, - 'delete' => true + 'delete' => true, + 'update' => true ], 'pages' => [ 'changeSlug' => true, diff --git a/tests/Cms/Languages/LanguageTest.php b/tests/Cms/Languages/LanguageTest.php index f867243542..648aa02b6e 100644 --- a/tests/Cms/Languages/LanguageTest.php +++ b/tests/Cms/Languages/LanguageTest.php @@ -3,6 +3,7 @@ namespace Kirby\Cms; use Kirby\Data\Data; +use Kirby\Exception\PermissionException; use Kirby\Filesystem\Dir; use Kirby\Filesystem\F; use PHPUnit\Framework\TestCase; @@ -471,6 +472,8 @@ public function testBaseUrl($kirbyUrl, $url, $expected) public function testCreate() { + $this->app->impersonate('kirby'); + $language = Language::create([ 'code' => 'en' ]); @@ -482,8 +485,53 @@ public function testCreate() $this->assertSame('/en', $language->url()); } + /** + * @covers ::create + */ + public function testCreateNoPermissions() + { + $app = $this->app->clone([ + 'blueprints' => [ + 'users/editor' => [ + 'name' => 'editor', + 'permissions' => [ + 'languages' => [ + 'create' => false + ] + ] + ], + ], + 'users' => [ + ['email' => 'test@getkirby.com', 'role' => 'editor'] + ] + ]); + + $this->expectException(PermissionException::class); + $this->expectExceptionMessage('You are not allowed to create a language'); + + $app->impersonate('test@getkirby.com'); + Language::create([ + 'code' => 'en' + ]); + } + + /** + * @covers ::create + */ + public function testCreateWithoutLoggedUser() + { + $this->expectException(PermissionException::class); + $this->expectExceptionMessage('You are not allowed to create a language'); + + Language::create([ + 'code' => 'en' + ]); + } + public function testDelete() { + $this->app->impersonate('kirby'); + $language = Language::create([ 'code' => 'en' ]); @@ -491,10 +539,58 @@ public function testDelete() $this->assertTrue($language->delete()); } + /** + * @covers ::delete + */ + public function testDeleteNoPermissions() + { + $app = $this->app->clone([ + 'blueprints' => [ + 'users/editor' => [ + 'name' => 'editor', + 'permissions' => [ + 'languages' => [ + 'create' => true, + 'delete' => false + ] + ] + ], + ], + 'users' => [ + ['email' => 'test@getkirby.com', 'role' => 'editor'] + ] + ]); + + $this->expectException(PermissionException::class); + $this->expectExceptionMessage('You are not allowed to delete the language'); + + $app->impersonate('test@getkirby.com'); + $language = Language::create(['code' => 'en']); + $language->delete(); + } + + /** + * @covers ::delete + */ + public function testDeleteWithoutLoggedUser() + { + $this->app->impersonate('kirby'); + $language = Language::create(['code' => 'en']); + + $this->expectException(PermissionException::class); + $this->expectExceptionMessage('You are not allowed to delete the language'); + + // unimpersonate and test the method + $this->app->impersonate(); + $language->delete(); + } + public function testUpdate() { Dir::make($contentDir = $this->fixtures . '/content'); + $this->app->impersonate('kirby'); + $language = Language::create([ 'code' => 'en' ]); @@ -503,4 +599,51 @@ public function testUpdate() $this->assertSame('English', $language->name()); } + + /** + * @covers ::update + */ + public function testUpdateNoPermissions() + { + $app = $this->app->clone([ + 'blueprints' => [ + 'users/editor' => [ + 'name' => 'editor', + 'permissions' => [ + 'languages' => [ + 'create' => true, + 'update' => false + ] + ] + ], + ], + 'users' => [ + ['email' => 'test@getkirby.com', 'role' => 'editor'] + ] + ]); + + $this->expectException(PermissionException::class); + $this->expectExceptionMessage('You are not allowed to update the language'); + + $app->impersonate('test@getkirby.com'); + + $language = Language::create(['code' => 'en']); + $language->update(['name' => 'English']); + } + + /** + * @covers ::update + */ + public function testUpdateWithoutLoggedUser() + { + $this->app->impersonate('kirby'); + $language = Language::create(['code' => 'en']); + + $this->expectException(PermissionException::class); + $this->expectExceptionMessage('You are not allowed to update the language'); + + // unimpersonate and test the method + $this->app->impersonate(); + $language->update(['name' => 'English']); + } } diff --git a/tests/Cms/Languages/LanguagesTest.php b/tests/Cms/Languages/LanguagesTest.php index e7a5dc06ff..df1cb8fd3a 100644 --- a/tests/Cms/Languages/LanguagesTest.php +++ b/tests/Cms/Languages/LanguagesTest.php @@ -108,6 +108,8 @@ public function testMultipleDefault() public function testCreate() { + $this->app->impersonate('kirby'); + $language = $this->app->languages()->create([ 'code' => 'tr' ]);