diff --git a/locales/default.pot b/locales/default.pot index b9127d174..5cb0f5451 100644 --- a/locales/default.pot +++ b/locales/default.pot @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: BEdita 4 \n" -"POT-Creation-Date: 2023-03-06 16:09:45 \n" +"POT-Creation-Date: 2023-04-12 07:11:02 \n" "MIME-Version: 1.0 \n" "Content-Transfer-Encoding: 8bit \n" "Language-Team: BEdita I18N & I10N Team \n" @@ -23,9 +23,6 @@ msgstr "" msgid "Add" msgstr "" -msgid "Add custom property" -msgstr "" - msgid "Add translation" msgstr "" @@ -263,9 +260,6 @@ msgstr "" msgid "Embed online content" msgstr "" -msgid "Empty request data" -msgstr "" - msgid "Empty translation \"id\"" msgstr "" @@ -512,15 +506,6 @@ msgstr "" msgid "No application" msgstr "" -msgid "No core properties" -msgstr "" - -msgid "No custom properties" -msgstr "" - -msgid "No inherited properties" -msgstr "" - msgid "No items found" msgstr "" @@ -617,6 +602,9 @@ msgstr "" msgid "Permanently delete" msgstr "" +msgid "Permissions" +msgstr "" + msgid "Person Title" msgstr "" @@ -1207,12 +1195,15 @@ msgstr "" msgid "Uri is not valid" msgstr "" -msgid "edit" +msgid "Canonical" msgstr "" msgid "Menu" msgstr "" +msgid "View" +msgstr "" + msgid "Search existing tags" msgstr "" @@ -1249,15 +1240,27 @@ msgstr "" msgid "Error while creating new object." msgstr "" -msgid "Inherited from" +msgid "You can modify the permissions" msgstr "" -msgid "Show" +msgid "You cannot modify the permissions" +msgstr "" + +msgid "Your roles" +msgstr "" + +msgid "Inherited" msgstr "" msgid "Hide" msgstr "" +msgid "Inherited from" +msgstr "" + +msgid "Show" +msgstr "" + msgid "Error on deleting property" msgstr "" @@ -1288,6 +1291,14 @@ msgstr "" msgid "GET" msgstr "" +#. * +#. * component used for ModulesPage -> View +#. * +#. * Handle Locations and reverse geocoding from addresses +#. +msgid "edit" +msgstr "" + #. * #. * Templates that uses this component (directly or indirectly): #. * ... diff --git a/locales/en_US/default.po b/locales/en_US/default.po index 12174cef8..f0374de9f 100644 --- a/locales/en_US/default.po +++ b/locales/en_US/default.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: BEdita Manager \n" -"POT-Creation-Date: 2023-03-06 16:09:45 \n" +"POT-Creation-Date: 2023-04-12 07:11:02 \n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: BEdita I18N & I10N Team \n" @@ -26,9 +26,6 @@ msgstr "" msgid "Add" msgstr "" -msgid "Add custom property" -msgstr "" - msgid "Add translation" msgstr "" @@ -266,9 +263,6 @@ msgstr "" msgid "Embed online content" msgstr "" -msgid "Empty request data" -msgstr "" - msgid "Empty translation \"id\"" msgstr "" @@ -515,15 +509,6 @@ msgstr "" msgid "No application" msgstr "" -msgid "No core properties" -msgstr "" - -msgid "No custom properties" -msgstr "" - -msgid "No inherited properties" -msgstr "" - msgid "No items found" msgstr "" @@ -620,6 +605,9 @@ msgstr "" msgid "Permanently delete" msgstr "" +msgid "Permissions" +msgstr "" + msgid "Person Title" msgstr "" @@ -1210,12 +1198,15 @@ msgstr "" msgid "Uri is not valid" msgstr "" -msgid "edit" +msgid "Canonical" msgstr "" msgid "Menu" msgstr "" +msgid "View" +msgstr "" + msgid "Search existing tags" msgstr "" @@ -1252,15 +1243,27 @@ msgstr "" msgid "Error while creating new object." msgstr "" -msgid "Inherited from" +msgid "You can modify the permissions" msgstr "" -msgid "Show" +msgid "You cannot modify the permissions" +msgstr "" + +msgid "Your roles" +msgstr "" + +msgid "Inherited" msgstr "" msgid "Hide" msgstr "" +msgid "Inherited from" +msgstr "" + +msgid "Show" +msgstr "" + msgid "Error on deleting property" msgstr "" @@ -1291,6 +1294,14 @@ msgstr "" msgid "GET" msgstr "" +#. * +#. * component used for ModulesPage -> View +#. * +#. * Handle Locations and reverse geocoding from addresses +#. +msgid "edit" +msgstr "" + #. * #. * Templates that uses this component (directly or indirectly): #. * ... diff --git a/locales/it_IT/default.po b/locales/it_IT/default.po index 1a8090d78..830f8b0db 100644 --- a/locales/it_IT/default.po +++ b/locales/it_IT/default.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: BEdita Manager \n" -"POT-Creation-Date: 2023-03-06 16:09:45 \n" +"POT-Creation-Date: 2023-04-12 07:11:02 \n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: BEdita I18N & I10N Team \n" @@ -26,9 +26,6 @@ msgstr "Azioni sugli oggetti selezionati" msgid "Add" msgstr "Aggiungi" -msgid "Add custom property" -msgstr "Aggiungi proprietà custom" - msgid "Add translation" msgstr "Aggiungi traduzione" @@ -266,9 +263,6 @@ msgstr "E-mail" msgid "Embed online content" msgstr "Incorpora contenuti online" -msgid "Empty request data" -msgstr "Nessuno dato in request" - msgid "Empty translation \"id\"" msgstr "Identificatore traduzione vuoto (\"id\")" @@ -515,15 +509,6 @@ msgstr "No" msgid "No application" msgstr "Nessuna applicazione" -msgid "No core properties" -msgstr "Nessuna proprietà di base" - -msgid "No custom properties" -msgstr "Nessuna proprietà personalizzata" - -msgid "No inherited properties" -msgstr "Nessuna proprietà ereditata" - msgid "No items found" msgstr "Nessun elemento trovato" @@ -620,6 +605,9 @@ msgstr "Richiesta di reimpostazione password inviata" msgid "Permanently delete" msgstr "Elimina definitivamente" +msgid "Permissions" +msgstr "Permessi" + msgid "Person Title" msgstr "Titolo Persona" @@ -1216,12 +1204,15 @@ msgstr "Indirizzo valido" msgid "Uri is not valid" msgstr "Indirizzo non valido" -msgid "edit" -msgstr "modifica" +msgid "Canonical" +msgstr "Canonica" msgid "Menu" msgstr "Menù" +msgid "View" +msgstr "Vedi" + msgid "Search existing tags" msgstr "Cerca tag esistenti" @@ -1258,15 +1249,27 @@ msgstr "Dato obbligatorio mancante \"${ required }\". Riprovare" msgid "Error while creating new object." msgstr "Si è verificato un errore durante la creazione del nuovo oggetto." +msgid "You can modify the permissions" +msgstr "Puoi modificare i permessi" + +msgid "You cannot modify the permissions" +msgstr "Non puoi modificare i permessi" + +msgid "Your roles" +msgstr "I tuoi ruoli" + +msgid "Inherited" +msgstr "Ereditato" + +msgid "Hide" +msgstr "Nascondi" + msgid "Inherited from" msgstr "Ereditato da" msgid "Show" msgstr "Mostra" -msgid "Hide" -msgstr "Nascondi" - msgid "Error on deleting property" msgstr "Errore nella cancellazione della proprietà" @@ -1297,6 +1300,14 @@ msgstr "Indirizzo" msgid "GET" msgstr "" +#. * +#. * component used for ModulesPage -> View +#. * +#. * Handle Locations and reverse geocoding from addresses +#. +msgid "edit" +msgstr "modifica" + #. * #. * Templates that uses this component (directly or indirectly): #. * ... diff --git a/resources/js/app/app.js b/resources/js/app/app.js index 6df49a4cc..c96336391 100644 --- a/resources/js/app/app.js +++ b/resources/js/app/app.js @@ -68,6 +68,8 @@ const _vueInstance = new Vue({ KeyValueList: () => import(/* webpackChunkName: "key-value-list" */'app/components/json-fields/key-value-list'), StringList: () => import(/* webpackChunkName: "string-list" */'app/components/json-fields/string-list'), Thumbnail:() => import(/* webpackChunkName: "thumbnail" */'app/components/thumbnail/thumbnail'), + Permission:() => import(/* webpackChunkName: "permission" */'app/components/permission/permission'), + Permissions:() => import(/* webpackChunkName: "permissions" */'app/components/permissions/permissions'), Icon, }, diff --git a/resources/js/app/components/object-property/object-property.vue b/resources/js/app/components/object-property/object-property.vue index 5e5b7ed87..51fbb039b 100644 --- a/resources/js/app/components/object-property/object-property.vue +++ b/resources/js/app/components/object-property/object-property.vue @@ -3,14 +3,14 @@
{{ prop.attributes.name }} -

{{ t('Label') }}: {{ prop.attributes.label || '-' }}

-

{{ t('Type') }}: {{ prop.attributes.property_type_name }}

-

{{ t('Hidden') }}: - {{ t('Yes') }} - {{ t('No') }} +

{{ msgLabel }}: {{ prop.attributes.label || '-' }}

+

{{ msgType }}: {{ prop.attributes.property_type_name }}

+

{{ msgHidden }}: + {{ msgYes }} + {{ msgNo }}

- {{ t('Inherited from') }}: + {{ msgInheritedFrom }}: {{ prop.attributes.object_type_name }}

 

@@ -18,9 +18,9 @@
- - - + + +
@@ -44,6 +44,15 @@ export default { confirm: null, hidden: false, display: true, + msgDelete: t`Delete`, + msgHidden: t`Hidden`, + msgHide: t`Hide`, + msgInheritedFrom: t`Inherited from`, + msgLabel: t`Label`, + msgNo: t`No`, + msgShow: t`Show`, + msgType: t`Type`, + msgYes: t`Yes`, }; }, diff --git a/resources/js/app/components/permission/permission.vue b/resources/js/app/components/permission/permission.vue new file mode 100644 index 000000000..1c3bd8e93 --- /dev/null +++ b/resources/js/app/components/permission/permission.vue @@ -0,0 +1,53 @@ + + + diff --git a/resources/js/app/components/permissions/permissions.vue b/resources/js/app/components/permissions/permissions.vue new file mode 100644 index 000000000..6be124c66 --- /dev/null +++ b/resources/js/app/components/permissions/permissions.vue @@ -0,0 +1,75 @@ + + + diff --git a/resources/js/app/components/property-view/property-view.js b/resources/js/app/components/property-view/property-view.js index 7fa9032a9..3f6dbf1a7 100644 --- a/resources/js/app/components/property-view/property-view.js +++ b/resources/js/app/components/property-view/property-view.js @@ -37,6 +37,8 @@ export default { ObjectProperties: () => import(/* webpackChunkName: "string-list" */'app/components/object-property/object-properties'), ObjectPropertyAdd: () => import(/* webpackChunkName: "object-property-add" */'app/components/object-property/object-property-add'), Thumbnail:() => import(/* webpackChunkName: "thumbnail" */'app/components/thumbnail/thumbnail'), + Permission:() => import(/* webpackChunkName: "permission" */'app/components/permission/permission'), + Permissions:() => import(/* webpackChunkName: "permissions" */'app/components/permissions/permissions'), }, props: { diff --git a/resources/js/app/components/tree-view/tree-view.js b/resources/js/app/components/tree-view/tree-view.vue similarity index 68% rename from resources/js/app/components/tree-view/tree-view.js rename to resources/js/app/components/tree-view/tree-view.vue index ef870f5ea..54f43398d 100644 --- a/resources/js/app/components/tree-view/tree-view.js +++ b/resources/js/app/components/tree-view/tree-view.vue @@ -1,3 +1,67 @@ + + diff --git a/src/Controller/Component/PropertiesComponent.php b/src/Controller/Component/PropertiesComponent.php index 43bd54b8b..8dd7f6b11 100644 --- a/src/Controller/Component/PropertiesComponent.php +++ b/src/Controller/Component/PropertiesComponent.php @@ -311,6 +311,7 @@ public function associationsOptions(array $value): array 'Streams', 'Categories', 'Tags', + 'Permissions', ]; $fields = array_unique(array_merge($fields, $value)); foreach ($fields as $text) { diff --git a/src/View/Helper/PermsHelper.php b/src/View/Helper/PermsHelper.php index 2fa62472a..bacad3c1d 100644 --- a/src/View/Helper/PermsHelper.php +++ b/src/View/Helper/PermsHelper.php @@ -62,10 +62,7 @@ public function initialize(array $config): void */ public function canLock(): bool { - /** @var \Authentication\Identity $identity */ - $identity = $this->_View->get('user'); - - return in_array('admin', (array)$identity->get('roles')); + return $this->userIsAdmin(); } /** @@ -76,7 +73,7 @@ public function canLock(): bool */ public function canCreate(?string $module = null): bool { - return $this->isAllowed('POST', $module); + return $this->isAllowed('POST', $module) && $this->userIsAllowed($module); } /** @@ -90,7 +87,7 @@ public function canDelete(array $object): bool $locked = (bool)Hash::get($object, 'meta.locked', false); $module = (string)Hash::get($object, 'type'); - return !$locked && $this->isAllowed('DELETE', $module); + return !$locked && $this->isAllowed('DELETE', $module) && $this->userIsAllowed($module); } /** @@ -101,7 +98,7 @@ public function canDelete(array $object): bool */ public function canSave(?string $module = null): bool { - return $this->isAllowed('PATCH', $module); + return $this->isAllowed('PATCH', $module) && $this->userIsAllowed($module); } /** @@ -159,4 +156,49 @@ public function access(array $accessControl, string $roleName, string $moduleNam return in_array($moduleName, $readonlyModules) ? 'read' : 'write'; } + + /** + * Return true if authenticated user has role admin + * + * @return bool + */ + public function userIsAdmin(): bool + { + return in_array('admin', $this->userRoles()); + } + + /** + * Check permissions for user if object is a folder. + * + * @param string|null $module The module, if passed. + * @return bool + */ + public function userIsAllowed(?string $module): bool + { + $objectType = !empty($module) ? $module : $this->_View->get('objectType'); + if ($objectType !== 'folders' || $this->userIsAdmin()) { + return true; + } + + $object = $this->_View->get('object'); + $permsRoles = (array)Hash::get((array)$object, 'meta.perms.roles'); + if (empty($permsRoles)) { + return true; + } + + return !empty(array_intersect($permsRoles, (array)$this->userRoles())); + } + + /** + * Return authenticated user roles + * + * @return array + */ + public function userRoles(): array + { + /** @var \Authentication\Identity $identity */ + $identity = $this->_View->get('user'); + + return (array)$identity->get('roles'); + } } diff --git a/templates/Element/Form/permissions.twig b/templates/Element/Form/permissions.twig new file mode 100644 index 000000000..687992da1 --- /dev/null +++ b/templates/Element/Form/permissions.twig @@ -0,0 +1,18 @@ +{% if object.meta.perms is defined %} + +
+
+

{{ __('Permissions') }}

+
+ +
+ + +
+
+
+{% endif %} diff --git a/templates/Element/Form/trees.twig b/templates/Element/Form/trees.twig index 77730d370..d081c36bc 100644 --- a/templates/Element/Form/trees.twig +++ b/templates/Element/Form/trees.twig @@ -22,7 +22,8 @@ relation-name={{ relationName }} relation-label="{{ Layout.tr(relationName) }}" :object='{{ { id: object.id, type: object.type }|json_encode }}' - :multiple-choice={{ options.multiple }}> + :multiple-choice={{ options.multiple }} + :user-roles="{{ user.roles|json_encode}}"> {% do Form.unlockField('relations.' ~ relationName ~ '.replaceRelated') %} {{ Form.hidden('_changedParents', {'value': '0', 'id': 'changedParents'})|raw }} diff --git a/templates/Pages/Modules/index.twig b/templates/Pages/Modules/index.twig index 3c9515a83..177881cad 100644 --- a/templates/Pages/Modules/index.twig +++ b/templates/Pages/Modules/index.twig @@ -9,7 +9,7 @@
{% if treeView %} - + {% else %}
diff --git a/templates/Pages/Modules/view.twig b/templates/Pages/Modules/view.twig index b8e8ba3b2..806e5a509 100644 --- a/templates/Pages/Modules/view.twig +++ b/templates/Pages/Modules/view.twig @@ -43,6 +43,10 @@ {{ element('Form/core_properties') }} + {% if objectType == 'folders' %} + {{ element('Form/permissions') }} + {% endif %} + {{ element('Form/custom_left') }} {# calendar using `date_ranges` #} diff --git a/tests/TestCase/Controller/Component/PropertiesComponentTest.php b/tests/TestCase/Controller/Component/PropertiesComponentTest.php index 62ce54df7..1b30bf1f9 100644 --- a/tests/TestCase/Controller/Component/PropertiesComponentTest.php +++ b/tests/TestCase/Controller/Component/PropertiesComponentTest.php @@ -537,6 +537,7 @@ public function testAssociationsOptions(): void ['text' => 'Streams', 'value' => 'Streams'], ['text' => 'Categories', 'value' => 'Categories'], ['text' => 'Tags', 'value' => 'Tags'], + ['text' => 'Permissions', 'value' => 'Permissions'], ['text' => 'Dummy', 'value' => 'Dummy'], ]; $actual = $this->Properties->associationsOptions(['Dummy']); diff --git a/tests/TestCase/View/Helper/PermsHelperTest.php b/tests/TestCase/View/Helper/PermsHelperTest.php index d10ee2d31..233d9e51d 100644 --- a/tests/TestCase/View/Helper/PermsHelperTest.php +++ b/tests/TestCase/View/Helper/PermsHelperTest.php @@ -244,4 +244,71 @@ public function testAccess(array $accessControl, string $roleName, string $modul $actual = $this->Perms->access($accessControl, $roleName, $moduleName); static::assertSame($expected, $actual); } + + /** + * Test `userIsAdmin` + * + * @return void + * @covers ::userIsAdmin() + */ + public function testUserIsAdmin(): void + { + $this->Perms->getView()->set('user', new Identity([])); + $actual = $this->Perms->userIsAdmin(); + static::assertFalse($actual); + + $this->Perms->getView()->set('user', new Identity(['roles' => ['admin']])); + $actual = $this->Perms->userIsAdmin(); + static::assertTrue($actual); + } + + /** + * Test `userIsAllowed + * + * @return void + * @covers ::userIsAllowed() + */ + public function testUserIsAllowed(): void + { + // not folders + $this->Perms->getView()->set('objectType', 'documents'); + $actual = $this->Perms->userIsAllowed(null); + static::assertTrue($actual); + + // user admin + $this->Perms->getView()->set('objectType', 'folders'); + $this->Perms->getView()->set('user', new Identity(['roles' => ['admin']])); + $actual = $this->Perms->userIsAllowed(null); + static::assertTrue($actual); + + // empty meta.perms.roles + $this->Perms->getView()->set('object', ['meta' => []]); + $this->Perms->getView()->set('user', new Identity(['roles' => ['guest', 'manager', 'other']])); + $actual = $this->Perms->userIsAllowed(null); + static::assertTrue($actual); + + // has permission + $this->Perms->getView()->set('object', ['meta' => ['perms' => ['roles' => ['a', 'b', 'manager', 'c', 'd']]]]); + $actual = $this->Perms->userIsAllowed(null); + static::assertTrue($actual); + + // no permission + $this->Perms->getView()->set('object', ['meta' => ['perms' => ['roles' => ['a', 'b', 'c', 'd']]]]); + $actual = $this->Perms->userIsAllowed(null); + static::assertFalse($actual); + } + + /** + * Test `userRoles` + * + * @return void + * @covers ::userRoles() + */ + public function testUserRoles(): void + { + $expected = ['a', 'b', 'c']; + $this->Perms->getView()->set('user', new Identity(['roles' => $expected])); + $actual = $this->Perms->userRoles(); + static::assertSame($expected, $actual); + } }