From 16325f20b927f28ff03762687862dcaea9944da5 Mon Sep 17 00:00:00 2001 From: Wesley Barroso Lopes Date: Fri, 5 Jan 2024 21:54:11 -0300 Subject: [PATCH] Give Site Administrator permission to manage users (#1712) * Give Site Administrator permission to manage users Permission of related endpoints changed from cmf.ManagePortal to plone.app.controlpanel.UsersAndGroups. It was also necessary to give Manage users and plone.restapi: Access Plone user information permissions to the Site Administrator. * Does not allow a Site Administrator to set a Manager role * Does not allow a Site Administrator delete Manager * Test that the Site Administrator cannot add a Manager * Do not allow a Site Administrator user to add users to groups that have the Manager role * Do not allow the Site Administrator to set the Manager role for a group * Do not allow the Site Administrator to create groups with the Manager role * Does not allow an Site Administrator add group to group with Manager role * Do not allow Site Administrator to delete group with Manager role * Add can_assign and can_assign_add keys to the roles endpoint This is to inform the front-end whether role assignment should be allowed or not. It was necessary to create the can_assign_add key, because a user can only create another with the roles they have. But in editing he can assign other roles. * Add can_delete key to the users endpoint Used to backend hide remove user button if user cannot be removed by currently authenticated user * Add can_delete key to the groups endpoint Used to backend hide remove group button if group cannot be removed by currently authenticated user * Does not allow a Site Administrator to change a Manager's email and password * Update examples in documentation * Uses acl_users.userFolderDelUsers to delete users Therefore, it is not necessary to give Manage users permission to the Site Administrator. * Simplifies logic that defines whether the user can update roles * Show message on lack of permission errors * Set default roles as list This prevents '"Manager" in roles' breaking if roles was missing * Quote the plone.app.controlpanel.UsersAndGroups permission in changes * Remove can_assign_add key * Add upgrade step Upgrade step to give permission plone.restapi: Access Plone user information to Site Administrator * Define can_delete in group serializer * Define can_delete in users serializer * Shows the can_delete key in the users and groups serializer only if the user has "Plone Site Setup: Users and Groups" permission * Rename ManageUsers to PloneManageUsers * Uses PloneManageUsers variable instead of the string * Remove can_delete key from users/groups endpoints * Remove can_assign key from roles endpoint * remove unused function --------- Co-authored-by: David Glick --- news/1712.feature | 1 + .../locales/de/LC_MESSAGES/plone.restapi.po | 82 ++++++------ .../locales/es/LC_MESSAGES/plone.restapi.po | 84 ++++++------ .../locales/fr/LC_MESSAGES/plone.restapi.po | 82 ++++++------ src/plone/restapi/locales/plone.restapi.pot | 82 ++++++------ .../pt_BR/LC_MESSAGES/plone.restapi.po | 82 ++++++------ src/plone/restapi/permissions.py | 2 + .../restapi/profiles/default/metadata.xml | 2 +- .../restapi/profiles/default/rolemap.xml | 1 + src/plone/restapi/serializer/utils.py | 6 +- src/plone/restapi/services/groups/add.py | 14 +- .../restapi/services/groups/configure.zcml | 8 +- src/plone/restapi/services/groups/delete.py | 12 ++ src/plone/restapi/services/groups/update.py | 39 +++++- .../restapi/services/roles/configure.zcml | 2 +- src/plone/restapi/services/users/add.py | 3 +- .../restapi/services/users/configure.zcml | 2 +- src/plone/restapi/services/users/delete.py | 51 +++++-- src/plone/restapi/services/users/get.py | 5 +- src/plone/restapi/services/users/update.py | 35 ++++- .../translated_messages_addons.resp | 4 +- src/plone/restapi/tests/test_addons.py | 8 +- .../restapi/tests/test_serializer_group.py | 22 ++++ .../restapi/tests/test_serializer_user.py | 29 +++- .../restapi/tests/test_services_groups.py | 70 ++++++++++ .../{test_roles.py => test_services_roles.py} | 69 ++++++++++ .../restapi/tests/test_services_users.py | 124 ++++++++++++++++++ src/plone/restapi/tests/test_upgrades.py | 35 +++++ src/plone/restapi/upgrades/configure.zcml | 9 ++ src/plone/restapi/upgrades/to0007.py | 19 +++ 30 files changed, 763 insertions(+), 221 deletions(-) create mode 100644 news/1712.feature rename src/plone/restapi/tests/{test_roles.py => test_services_roles.py} (58%) create mode 100644 src/plone/restapi/upgrades/to0007.py diff --git a/news/1712.feature b/news/1712.feature new file mode 100644 index 0000000000..b3c588cf81 --- /dev/null +++ b/news/1712.feature @@ -0,0 +1 @@ +Give Site Administrator permission to manage users. To make this possible, we now check the "plone.app.controlpanel.UsersAndGroups" permission instead of "cmf.ManagePortal" in a lot of operations in the users and groups endpoints. @wesleybl diff --git a/src/plone/restapi/locales/de/LC_MESSAGES/plone.restapi.po b/src/plone/restapi/locales/de/LC_MESSAGES/plone.restapi.po index 4a2b3ccf9a..0a4c1ec545 100644 --- a/src/plone/restapi/locales/de/LC_MESSAGES/plone.restapi.po +++ b/src/plone/restapi/locales/de/LC_MESSAGES/plone.restapi.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2022-12-29 15:58+0000\n" +"POT-Creation-Date: 2023-09-25 20:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: Timo Stollenwerk \n" "Language-Team: German \n" @@ -14,11 +14,11 @@ msgstr "" "Preferred-Encodings: utf-8 latin1\n" "Domain: plone.restapi\n" -#: plone/restapi/services/email_send/post.py:81 +#: plone/restapi/services/email_send/post.py:88 msgid "${sender_fullname} via ${portal_title}" msgstr "${sender_fullname} via ${portal_title}" -#: plone/restapi/services/email_send/post.py:74 +#: plone/restapi/services/email_send/post.py:81 msgid "A portal user via ${portal_title}" msgstr "Ein Portal Nutzer via ${portal_title}" @@ -34,23 +34,23 @@ msgstr "" msgid "Adds sample workflows for testing" msgstr "" -#: plone/restapi/services/aliases/add.py:106 +#: plone/restapi/services/aliases/add.py:107 msgid "Alternative urls that point to themselves will cause an endless cycle of redirects." msgstr "" -#: plone/restapi/configure.zcml:115 +#: plone/restapi/configure.zcml:116 msgid "Blocks" msgstr "" -#: plone/restapi/configure.zcml:122 +#: plone/restapi/configure.zcml:123 msgid "Blocks (Editable Layout)" msgstr "" -#: plone/restapi/configure.zcml:122 +#: plone/restapi/configure.zcml:123 msgid "Enables Volto Blocks (editable layout) support" msgstr "" -#: plone/restapi/configure.zcml:115 +#: plone/restapi/configure.zcml:116 msgid "Enables Volto Blocks support" msgstr "" @@ -58,7 +58,7 @@ msgstr "" msgid "Enables blocks on the Document content type" msgstr "" -#: plone/restapi/services/contextnavigation/get.py:133 +#: plone/restapi/services/contextnavigation/get.py:136 msgid "Enter a valid scale name (see 'Image Handling' control panel) to override (e.g. icon, tile, thumb, mini, preview, ... ). Leave empty to use default (see 'Site' control panel)." msgstr "" @@ -67,15 +67,15 @@ msgid "Error in fields. ${errors_to_string}" msgstr "" #. Default: "The reset_token is expired." -#: plone/restapi/services/users/add.py:312 +#: plone/restapi/services/users/add.py:319 msgid "Expired Token" msgstr "" -#: plone/restapi/services/contextnavigation/get.py:126 +#: plone/restapi/services/contextnavigation/get.py:129 msgid "If enabled, the portlet will not show document type icons." msgstr "" -#: plone/restapi/services/contextnavigation/get.py:145 +#: plone/restapi/services/contextnavigation/get.py:148 msgid "If enabled, the portlet will not show thumbs." msgstr "" @@ -91,11 +91,11 @@ msgstr "" msgid "Layout" msgstr "" -#: plone/restapi/services/contextnavigation/get.py:195 +#: plone/restapi/services/contextnavigation/get.py:197 msgid "Navigation" msgstr "" -#: plone/restapi/services/contextnavigation/get.py:132 +#: plone/restapi/services/contextnavigation/get.py:135 msgid "Override thumb scale" msgstr "" @@ -123,23 +123,23 @@ msgstr "" msgid "Roles" msgstr "" -#: plone/restapi/services/users/add.py:350 +#: plone/restapi/services/users/add.py:357 msgid "See the user endpoint documentation for the valid parameters." msgstr "" -#: plone/restapi/services/contextnavigation/get.py:125 +#: plone/restapi/services/contextnavigation/get.py:128 msgid "Suppress Icons" msgstr "" -#: plone/restapi/services/contextnavigation/get.py:144 +#: plone/restapi/services/contextnavigation/get.py:147 msgid "Suppress thumbs" msgstr "" -#: plone/restapi/services/users/add.py:342 +#: plone/restapi/services/users/add.py:349 msgid "The password passed as 'old_password' is wrong." msgstr "" -#: plone/restapi/services/users/add.py:307 +#: plone/restapi/services/users/add.py:314 msgid "The reset_token is unknown/not valid." msgstr "" @@ -147,11 +147,11 @@ msgstr "" msgid "Volto Blocks" msgstr "" -#: plone/restapi/services/users/update.py:119 +#: plone/restapi/services/users/update.py:151 msgid "You are not authorized to perform this action" msgstr "" -#: plone/restapi/services/email_send/post.py:91 +#: plone/restapi/services/email_send/post.py:98 msgid "You are receiving this mail because ${sender_fullname} sent this message via the site ${portal_title}:" msgstr "Sie erhalten diese E-Mail, weil Ihnen ${sender_fullname} diese Nachricht über die Webseite ${portal_title} geschickt hat:" @@ -159,14 +159,22 @@ msgstr "Sie erhalten diese E-Mail, weil Ihnen ${sender_fullname} diese Nachricht msgid "You can't send both password and sendPasswordReset." msgstr "" -#: plone/restapi/services/users/add.py:322 +#: plone/restapi/services/users/add.py:329 msgid "You can't set a password without a password reset token." msgstr "" -#: plone/restapi/services/users/update.py:125 +#: plone/restapi/services/users/update.py:120 +msgid "You can't update roles of this user" +msgstr "" + +#: plone/restapi/services/users/update.py:157 msgid "You can't update the properties of this user" msgstr "" +#: plone/restapi/services/users/update.py:94 +msgid "You can't update this user" +msgstr "" + #: plone/restapi/services/users/add.py:284 msgid "You can't use 'reset_token' and 'old_password' together." msgstr "" @@ -179,72 +187,72 @@ msgstr "" msgid "You need AddPortalMember permission." msgstr "" -#: plone/restapi/services/users/add.py:329 +#: plone/restapi/services/users/add.py:336 msgid "You need to be logged in as the user '${username}' to set the password." msgstr "" #. Default: "Missing dependency" -#: plone/restapi/services/addons/addons.py:207 +#: plone/restapi/services/addons/addons.py:212 msgid "dependency_missing" msgstr "" #. Default: "If selected, the navigation tree will only show the current folder and its children at all times." -#: plone/restapi/services/contextnavigation/get.py:84 +#: plone/restapi/services/contextnavigation/get.py:87 msgid "help_current_folder_only" msgstr "" #. Default: "Whether or not to show the top, or 'root', node in the navigation tree. This is affected by the 'Start level' setting." -#: plone/restapi/services/contextnavigation/get.py:69 +#: plone/restapi/services/contextnavigation/get.py:72 msgid "help_include_top_node" msgstr "" #. Default: "You may search for and choose a folder to act as the root of the navigation tree. Leave blank to use the Plone site root." -#: plone/restapi/services/contextnavigation/get.py:58 +#: plone/restapi/services/contextnavigation/get.py:61 msgid "help_navigation_root" msgstr "" #. Default: "An integer value that specifies the number of folder levels below the site root that must be exceeded before the navigation tree will display. 0 means that the navigation tree should be displayed everywhere including pages in the root of the site. 1 means the tree only shows up inside folders located in the root and downwards, never showing at the top level." -#: plone/restapi/services/contextnavigation/get.py:96 +#: plone/restapi/services/contextnavigation/get.py:99 msgid "help_navigation_start_level" msgstr "" #. Default: "The title of the navigation tree." -#: plone/restapi/services/contextnavigation/get.py:49 +#: plone/restapi/services/contextnavigation/get.py:52 msgid "help_navigation_title" msgstr "" #. Default: "How many folders should be included before the navigation tree stops. 0 means no limit. 1 only includes the root folder." -#: plone/restapi/services/contextnavigation/get.py:113 +#: plone/restapi/services/contextnavigation/get.py:116 msgid "help_navigation_tree_depth" msgstr "" #. Default: "Only show the contents of the current folder." -#: plone/restapi/services/contextnavigation/get.py:80 +#: plone/restapi/services/contextnavigation/get.py:83 msgid "label_current_folder_only" msgstr "" #. Default: "Include top node" -#: plone/restapi/services/contextnavigation/get.py:68 +#: plone/restapi/services/contextnavigation/get.py:71 msgid "label_include_top_node" msgstr "" #. Default: "Root node" -#: plone/restapi/services/contextnavigation/get.py:57 +#: plone/restapi/services/contextnavigation/get.py:60 msgid "label_navigation_root_path" msgstr "" #. Default: "Start level" -#: plone/restapi/services/contextnavigation/get.py:95 +#: plone/restapi/services/contextnavigation/get.py:98 msgid "label_navigation_startlevel" msgstr "" #. Default: "Title" -#: plone/restapi/services/contextnavigation/get.py:48 +#: plone/restapi/services/contextnavigation/get.py:51 msgid "label_navigation_title" msgstr "" #. Default: "Navigation tree depth" -#: plone/restapi/services/contextnavigation/get.py:112 +#: plone/restapi/services/contextnavigation/get.py:115 msgid "label_navigation_tree_depth" msgstr "" diff --git a/src/plone/restapi/locales/es/LC_MESSAGES/plone.restapi.po b/src/plone/restapi/locales/es/LC_MESSAGES/plone.restapi.po index b957833eec..6510309a30 100644 --- a/src/plone/restapi/locales/es/LC_MESSAGES/plone.restapi.po +++ b/src/plone/restapi/locales/es/LC_MESSAGES/plone.restapi.po @@ -4,11 +4,10 @@ msgid "" msgstr "" "Project-Id-Version: Plone\n" -"POT-Creation-Date: 2019-12-09 10:14+0000\n" +"POT-Creation-Date: 2023-09-25 20:32+0000\n" "PO-Revision-Date: 2023-08-23 21:19-0400\n" "Last-Translator: Leonardo J. Caballero G. \n" "Language-Team: ES \n" -"Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -17,14 +16,15 @@ msgstr "" "Language-Name: Español\n" "Preferred-Encodings: utf-8 latin1\n" "Domain: plone.restapi\n" +"Language: es\n" "X-Generator: Poedit 3.3.2\n" "X-Is-Fallback-For: es-ar es-bo es-cl es-co es-cr es-do es-ec es-es es-sv es-gt es-hn es-mx es-ni es-pa es-py es-pe es-pr es-us es-uy es-ve\n" -#: plone/restapi/services/email_send/post.py:81 +#: plone/restapi/services/email_send/post.py:88 msgid "${sender_fullname} via ${portal_title}" msgstr "${sender_fullname} vía ${portal_title}" -#: plone/restapi/services/email_send/post.py:74 +#: plone/restapi/services/email_send/post.py:81 msgid "A portal user via ${portal_title}" msgstr "Un usuario de portal user vía ${portal_title}" @@ -40,23 +40,23 @@ msgstr "Añadir tipos de contenido de ejemplo para pruebas" msgid "Adds sample workflows for testing" msgstr "Añadir flujos de trabajo de muestra para realizar pruebas" -#: plone/restapi/services/aliases/add.py:106 +#: plone/restapi/services/aliases/add.py:107 msgid "Alternative urls that point to themselves will cause an endless cycle of redirects." msgstr "Las URL alternativas que apuntan a sí mismas provocarán un bucle interminable de redireccionamientos." -#: plone/restapi/configure.zcml:115 +#: plone/restapi/configure.zcml:116 msgid "Blocks" msgstr "Bloques" -#: plone/restapi/configure.zcml:122 +#: plone/restapi/configure.zcml:123 msgid "Blocks (Editable Layout)" msgstr "Bloques (Diseño editable)" -#: plone/restapi/configure.zcml:122 +#: plone/restapi/configure.zcml:123 msgid "Enables Volto Blocks (editable layout) support" msgstr "Habilita el soporte para bloques Volto (diseño editable)" -#: plone/restapi/configure.zcml:115 +#: plone/restapi/configure.zcml:116 msgid "Enables Volto Blocks support" msgstr "Habilitar soporte a Bloques Volto" @@ -64,7 +64,7 @@ msgstr "Habilitar soporte a Bloques Volto" msgid "Enables blocks on the Document content type" msgstr "Habilitar bloques en el tipo de contenido Documento" -#: plone/restapi/services/contextnavigation/get.py:133 +#: plone/restapi/services/contextnavigation/get.py:136 msgid "Enter a valid scale name (see 'Image Handling' control panel) to override (e.g. icon, tile, thumb, mini, preview, ... ). Leave empty to use default (see 'Site' control panel)." msgstr "Introducir un nombre de escala válido (consulte 'Manipulación de imágenes' en el panel de control) para anular (por ejemplo, icon, tile, thumb, mini, preview...). Déjelo en blanco para usar el valor predeterminado (consulte 'Sitio' en el panel de control)." @@ -73,15 +73,15 @@ msgid "Error in fields. ${errors_to_string}" msgstr "Errores en los campos. ${errors_to_string}" #. Default: "The reset_token is expired." -#: plone/restapi/services/users/add.py:312 +#: plone/restapi/services/users/add.py:319 msgid "Expired Token" msgstr "Token caducado" -#: plone/restapi/services/contextnavigation/get.py:126 +#: plone/restapi/services/contextnavigation/get.py:129 msgid "If enabled, the portlet will not show document type icons." msgstr "Si está habilitado, el portlet no mostrará los iconos de tipo de contenido." -#: plone/restapi/services/contextnavigation/get.py:145 +#: plone/restapi/services/contextnavigation/get.py:148 msgid "If enabled, the portlet will not show thumbs." msgstr "Si está habilitado, el portlet no mostrará miniaturas." @@ -97,11 +97,11 @@ msgstr "Si introduce 'Restablecer token' debe ingresar 'Nueva contraseña'" msgid "Layout" msgstr "Diseño" -#: plone/restapi/services/contextnavigation/get.py:195 +#: plone/restapi/services/contextnavigation/get.py:197 msgid "Navigation" msgstr "Navegación" -#: plone/restapi/services/contextnavigation/get.py:132 +#: plone/restapi/services/contextnavigation/get.py:135 msgid "Override thumb scale" msgstr "Anular escala de miniaturas" @@ -129,23 +129,23 @@ msgstr "Revertido para revisión ${version}" msgid "Roles" msgstr "Roles" -#: plone/restapi/services/users/add.py:350 +#: plone/restapi/services/users/add.py:357 msgid "See the user endpoint documentation for the valid parameters." msgstr "Consulte la documentación del usuario del terminal para conocer los parámetros válidos." -#: plone/restapi/services/contextnavigation/get.py:125 +#: plone/restapi/services/contextnavigation/get.py:128 msgid "Suppress Icons" msgstr "Suprimir iconos" -#: plone/restapi/services/contextnavigation/get.py:144 +#: plone/restapi/services/contextnavigation/get.py:147 msgid "Suppress thumbs" msgstr "Suprimir miniaturas" -#: plone/restapi/services/users/add.py:342 +#: plone/restapi/services/users/add.py:349 msgid "The password passed as 'old_password' is wrong." msgstr "La contraseña pasada como \"Contraseña anterior\" no es válida." -#: plone/restapi/services/users/add.py:307 +#: plone/restapi/services/users/add.py:314 msgid "The reset_token is unknown/not valid." msgstr "El reset_token es desconocido/no válido." @@ -153,11 +153,11 @@ msgstr "El reset_token es desconocido/no válido." msgid "Volto Blocks" msgstr "Bloques Volto" -#: plone/restapi/services/users/update.py:119 +#: plone/restapi/services/users/update.py:151 msgid "You are not authorized to perform this action" msgstr "No está autorizado a realizar esta acción." -#: plone/restapi/services/email_send/post.py:91 +#: plone/restapi/services/email_send/post.py:98 msgid "You are receiving this mail because ${sender_fullname} sent this message via the site ${portal_title}:" msgstr "Está recibiendo este correo porque ${sender_fullname} envió este mensaje a través del sitio ${portal_title}:" @@ -165,14 +165,22 @@ msgstr "Está recibiendo este correo porque ${sender_fullname} envió este mensa msgid "You can't send both password and sendPasswordReset." msgstr "No puede enviar la contraseña y 'Enviar un correo electrónico de confirmación con un enlace para establecer la contraseña'." -#: plone/restapi/services/users/add.py:322 +#: plone/restapi/services/users/add.py:329 msgid "You can't set a password without a password reset token." msgstr "No puede establecer una contraseña sin un token de restablecimiento de contraseña." -#: plone/restapi/services/users/update.py:125 +#: plone/restapi/services/users/update.py:120 +msgid "You can't update roles of this user" +msgstr "" + +#: plone/restapi/services/users/update.py:157 msgid "You can't update the properties of this user" msgstr "No puede actualizar las propiedades de este usuario." +#: plone/restapi/services/users/update.py:94 +msgid "You can't update this user" +msgstr "" + #: plone/restapi/services/users/add.py:284 msgid "You can't use 'reset_token' and 'old_password' together." msgstr "No puede utilizar 'Restablecer token' y 'Contraseña anterior' juntas." @@ -185,72 +193,72 @@ msgstr "Debe enviar una contraseña o 'Enviar un correo electrónico de confirma msgid "You need AddPortalMember permission." msgstr "Necesita el permiso AddPortalMember." -#: plone/restapi/services/users/add.py:329 +#: plone/restapi/services/users/add.py:336 msgid "You need to be logged in as the user '${username}' to set the password." msgstr "Debe iniciar sesión como usuario '${username}' para poder establecer la contraseña." #. Default: "Missing dependency" -#: plone/restapi/services/addons/addons.py:207 +#: plone/restapi/services/addons/addons.py:212 msgid "dependency_missing" msgstr "Dependencia faltante" #. Default: "If selected, the navigation tree will only show the current folder and its children at all times." -#: plone/restapi/services/contextnavigation/get.py:84 +#: plone/restapi/services/contextnavigation/get.py:87 msgid "help_current_folder_only" msgstr "Si se selecciona, el árbol de navegación siempre mostrará solo la carpeta actual y sus hijos." #. Default: "Whether or not to show the top, or 'root', node in the navigation tree. This is affected by the 'Start level' setting." -#: plone/restapi/services/contextnavigation/get.py:69 +#: plone/restapi/services/contextnavigation/get.py:72 msgid "help_include_top_node" msgstr "Si el nodo superior, o 'raíz', debe mostrarse o no en el árbol de navegación. Esto se ve afectado por la configuración del 'Nivel inicial'." #. Default: "You may search for and choose a folder to act as the root of the navigation tree. Leave blank to use the Plone site root." -#: plone/restapi/services/contextnavigation/get.py:58 +#: plone/restapi/services/contextnavigation/get.py:61 msgid "help_navigation_root" msgstr "Puede buscar y seleccionar una carpeta para que actúe como raíz del árbol de navegación. Déjelo en blanco para usar la raíz del sitio Plone." #. Default: "An integer value that specifies the number of folder levels below the site root that must be exceeded before the navigation tree will display. 0 means that the navigation tree should be displayed everywhere including pages in the root of the site. 1 means the tree only shows up inside folders located in the root and downwards, never showing at the top level." -#: plone/restapi/services/contextnavigation/get.py:96 +#: plone/restapi/services/contextnavigation/get.py:99 msgid "help_navigation_start_level" msgstr "Un valor entero que especifica el número de niveles de carpeta debajo de la raíz del sitio que se deben exceder antes de que se muestre el árbol de navegación. 0 significa que el árbol de navegación debe mostrarse en cualquier lugar, incluidas las páginas en la raíz del sitio. 1 significa que el árbol solo aparecerá dentro de las carpetas ubicadas en la raíz y debajo de ella, y nunca aparecerá en el nivel raíz." #. Default: "The title of the navigation tree." -#: plone/restapi/services/contextnavigation/get.py:49 +#: plone/restapi/services/contextnavigation/get.py:52 msgid "help_navigation_title" msgstr "El título del árbol de navegación." #. Default: "How many folders should be included before the navigation tree stops. 0 means no limit. 1 only includes the root folder." -#: plone/restapi/services/contextnavigation/get.py:113 +#: plone/restapi/services/contextnavigation/get.py:116 msgid "help_navigation_tree_depth" msgstr "Cuántas carpetas se deben incluir antes de que se detenga el árbol de navegación. 0 significa sin límite. 1 significa incluir solo la carpeta raíz." #. Default: "Only show the contents of the current folder." -#: plone/restapi/services/contextnavigation/get.py:80 +#: plone/restapi/services/contextnavigation/get.py:83 msgid "label_current_folder_only" msgstr "Muestra solo el contenido de la carpeta actual." #. Default: "Include top node" -#: plone/restapi/services/contextnavigation/get.py:68 +#: plone/restapi/services/contextnavigation/get.py:71 msgid "label_include_top_node" msgstr "Incluir nodo superior" #. Default: "Root node" -#: plone/restapi/services/contextnavigation/get.py:57 +#: plone/restapi/services/contextnavigation/get.py:60 msgid "label_navigation_root_path" msgstr "Nodo raíz" #. Default: "Start level" -#: plone/restapi/services/contextnavigation/get.py:95 +#: plone/restapi/services/contextnavigation/get.py:98 msgid "label_navigation_startlevel" msgstr "Nivel inicial" #. Default: "Title" -#: plone/restapi/services/contextnavigation/get.py:48 +#: plone/restapi/services/contextnavigation/get.py:51 msgid "label_navigation_title" msgstr "Título" #. Default: "Navigation tree depth" -#: plone/restapi/services/contextnavigation/get.py:112 +#: plone/restapi/services/contextnavigation/get.py:115 msgid "label_navigation_tree_depth" msgstr "Profundidad del árbol de navegación" diff --git a/src/plone/restapi/locales/fr/LC_MESSAGES/plone.restapi.po b/src/plone/restapi/locales/fr/LC_MESSAGES/plone.restapi.po index 7d1260a51e..9d25934588 100644 --- a/src/plone/restapi/locales/fr/LC_MESSAGES/plone.restapi.po +++ b/src/plone/restapi/locales/fr/LC_MESSAGES/plone.restapi.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2022-12-29 15:58+0000\n" +"POT-Creation-Date: 2023-09-25 20:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: Julien Chandelle \n" "Language-Team: French \n" @@ -14,11 +14,11 @@ msgstr "" "Preferred-Encodings: utf-8 latin1\n" "Domain: plone.restapi\n" -#: plone/restapi/services/email_send/post.py:81 +#: plone/restapi/services/email_send/post.py:88 msgid "${sender_fullname} via ${portal_title}" msgstr "${sender_fullname} via ${portal_title}" -#: plone/restapi/services/email_send/post.py:74 +#: plone/restapi/services/email_send/post.py:81 msgid "A portal user via ${portal_title}" msgstr "Un utilisateur du portail via ${portal_title}" @@ -34,23 +34,23 @@ msgstr "" msgid "Adds sample workflows for testing" msgstr "" -#: plone/restapi/services/aliases/add.py:106 +#: plone/restapi/services/aliases/add.py:107 msgid "Alternative urls that point to themselves will cause an endless cycle of redirects." msgstr "Les URLs alternatives qui pointent vers elles-mêmes provoqueront un cycle sans fin de redirections." -#: plone/restapi/configure.zcml:115 +#: plone/restapi/configure.zcml:116 msgid "Blocks" msgstr "Blocs" -#: plone/restapi/configure.zcml:122 +#: plone/restapi/configure.zcml:123 msgid "Blocks (Editable Layout)" msgstr "Blocs (mise en page modifiable)" -#: plone/restapi/configure.zcml:122 +#: plone/restapi/configure.zcml:123 msgid "Enables Volto Blocks (editable layout) support" msgstr "Active la prise en charge des blocs Volto (mise en page modifiable)" -#: plone/restapi/configure.zcml:115 +#: plone/restapi/configure.zcml:116 msgid "Enables Volto Blocks support" msgstr "Active la prise en charge des blocs Volto" @@ -58,7 +58,7 @@ msgstr "Active la prise en charge des blocs Volto" msgid "Enables blocks on the Document content type" msgstr "" -#: plone/restapi/services/contextnavigation/get.py:133 +#: plone/restapi/services/contextnavigation/get.py:136 msgid "Enter a valid scale name (see 'Image Handling' control panel) to override (e.g. icon, tile, thumb, mini, preview, ... ). Leave empty to use default (see 'Site' control panel)." msgstr "Saisissez un nom d'échelle valide (cf. « Gestion des images » dans le panneau de configuration) pour outrepasser (p. ex. icon, tile, thumb, mini, preview, etc.). Laisser vide pour utiliser les échelles par défaut (cf. « Site » dans le panneau de configuration)." @@ -67,15 +67,15 @@ msgid "Error in fields. ${errors_to_string}" msgstr "Erreur dans les champs. ${errors_to_string}" #. Default: "The reset_token is expired." -#: plone/restapi/services/users/add.py:312 +#: plone/restapi/services/users/add.py:319 msgid "Expired Token" msgstr "Le reset_token a expiré" -#: plone/restapi/services/contextnavigation/get.py:126 +#: plone/restapi/services/contextnavigation/get.py:129 msgid "If enabled, the portlet will not show document type icons." msgstr "Si activé, le portlet n'affichera pas les icônes de types de documents." -#: plone/restapi/services/contextnavigation/get.py:145 +#: plone/restapi/services/contextnavigation/get.py:148 msgid "If enabled, the portlet will not show thumbs." msgstr "Si activé, le portlet n'affichera pas les miniatures." @@ -91,11 +91,11 @@ msgstr "Si vous passez le paramètre 'reset_token', vous devez passer le paramè msgid "Layout" msgstr "Mise en page" -#: plone/restapi/services/contextnavigation/get.py:195 +#: plone/restapi/services/contextnavigation/get.py:197 msgid "Navigation" msgstr "Navigation" -#: plone/restapi/services/contextnavigation/get.py:132 +#: plone/restapi/services/contextnavigation/get.py:135 msgid "Override thumb scale" msgstr "Remplacer l'échelle de miniature" @@ -123,23 +123,23 @@ msgstr "Retour à la révision ${version}" msgid "Roles" msgstr "Rôles" -#: plone/restapi/services/users/add.py:350 +#: plone/restapi/services/users/add.py:357 msgid "See the user endpoint documentation for the valid parameters." msgstr "Consultez la documentation du endpoint user pour les paramètres valides." -#: plone/restapi/services/contextnavigation/get.py:125 +#: plone/restapi/services/contextnavigation/get.py:128 msgid "Suppress Icons" msgstr "Supprimer les icônes" -#: plone/restapi/services/contextnavigation/get.py:144 +#: plone/restapi/services/contextnavigation/get.py:147 msgid "Suppress thumbs" msgstr "Supprimer les miniatures" -#: plone/restapi/services/users/add.py:342 +#: plone/restapi/services/users/add.py:349 msgid "The password passed as 'old_password' is wrong." msgstr "Le mot de passe passé comme 'old_password' est erroné." -#: plone/restapi/services/users/add.py:307 +#: plone/restapi/services/users/add.py:314 msgid "The reset_token is unknown/not valid." msgstr "Le reset_token est inconnu/non valide." @@ -147,11 +147,11 @@ msgstr "Le reset_token est inconnu/non valide." msgid "Volto Blocks" msgstr "Blocs Volto" -#: plone/restapi/services/users/update.py:119 +#: plone/restapi/services/users/update.py:151 msgid "You are not authorized to perform this action" msgstr "Vous n'êtes pas autorisé à effectuer cette action" -#: plone/restapi/services/email_send/post.py:91 +#: plone/restapi/services/email_send/post.py:98 msgid "You are receiving this mail because ${sender_fullname} sent this message via the site ${portal_title}:" msgstr "Vous recevez cette e-mail, car ${sender_fullname} vous a envoyé ce message depuis ${portal_title}:" @@ -159,14 +159,22 @@ msgstr "Vous recevez cette e-mail, car ${sender_fullname} vous a envoyé ce mess msgid "You can't send both password and sendPasswordReset." msgstr "Vous ne pouvez pas envoyer à la fois le password et sendPasswordReset" -#: plone/restapi/services/users/add.py:322 +#: plone/restapi/services/users/add.py:329 msgid "You can't set a password without a password reset token." msgstr "Vous ne pouvez pas définir de mot de passe sans un jeton de réinitialisation de mot de passe." -#: plone/restapi/services/users/update.py:125 +#: plone/restapi/services/users/update.py:120 +msgid "You can't update roles of this user" +msgstr "" + +#: plone/restapi/services/users/update.py:157 msgid "You can't update the properties of this user" msgstr "Vous ne pouvez pas mettre à jour les propriétés de cet utilisateur" +#: plone/restapi/services/users/update.py:94 +msgid "You can't update this user" +msgstr "" + #: plone/restapi/services/users/add.py:284 msgid "You can't use 'reset_token' and 'old_password' together." msgstr "Vous ne pouvez pas utiliser 'reset_token' et 'old_password' ensemble." @@ -179,72 +187,72 @@ msgstr "Vous devez soit saisir un password ou sendPasswordReset" msgid "You need AddPortalMember permission." msgstr "Vous avez besoin de la permission AddPortalMember" -#: plone/restapi/services/users/add.py:329 +#: plone/restapi/services/users/add.py:336 msgid "You need to be logged in as the user '${username}' to set the password." msgstr "Vous devez être connecté an tant que '${username}' pour définir le mot de passe." #. Default: "Missing dependency" -#: plone/restapi/services/addons/addons.py:207 +#: plone/restapi/services/addons/addons.py:212 msgid "dependency_missing" msgstr "Dépendence manquante" #. Default: "If selected, the navigation tree will only show the current folder and its children at all times." -#: plone/restapi/services/contextnavigation/get.py:84 +#: plone/restapi/services/contextnavigation/get.py:87 msgid "help_current_folder_only" msgstr "Si cette option est sélectionnée, l'arborescence de navigation n'affichera à tout moment que le dossier actuel et ses enfants." #. Default: "Whether or not to show the top, or 'root', node in the navigation tree. This is affected by the 'Start level' setting." -#: plone/restapi/services/contextnavigation/get.py:69 +#: plone/restapi/services/contextnavigation/get.py:72 msgid "help_include_top_node" msgstr "Afficher ou non le nœud supérieur ou 'racine' dans l'arborescence de navigation. Ceci est affecté par le paramètre 'Niveau de démarrage'." #. Default: "You may search for and choose a folder to act as the root of the navigation tree. Leave blank to use the Plone site root." -#: plone/restapi/services/contextnavigation/get.py:58 +#: plone/restapi/services/contextnavigation/get.py:61 msgid "help_navigation_root" msgstr "Vous pouvez rechercher et choisir un dossier qui servira de racine à l'arborescence de navigation. Laissez vide pour utiliser la racine du site Plone." #. Default: "An integer value that specifies the number of folder levels below the site root that must be exceeded before the navigation tree will display. 0 means that the navigation tree should be displayed everywhere including pages in the root of the site. 1 means the tree only shows up inside folders located in the root and downwards, never showing at the top level." -#: plone/restapi/services/contextnavigation/get.py:96 +#: plone/restapi/services/contextnavigation/get.py:99 msgid "help_navigation_start_level" msgstr "Une nombre entière qui spécifie le nombre de niveaux de dossier sous la racine du site qui doit être dépassé avant que l'arborescence de navigation s'affiche. 0 signifie que l'arborescence de navigation doit être affichée partout, en ce compris les pages à la racine du site. 1 signifie que l'arborescence n'apparaît qu'à l'intérieur des dossiers situés à la racine et en dessous, sans jamais s'afficher au niveau supérieur." #. Default: "The title of the navigation tree." -#: plone/restapi/services/contextnavigation/get.py:49 +#: plone/restapi/services/contextnavigation/get.py:52 msgid "help_navigation_title" msgstr "Titre de l'arborescence de navigation." #. Default: "How many folders should be included before the navigation tree stops. 0 means no limit. 1 only includes the root folder." -#: plone/restapi/services/contextnavigation/get.py:113 +#: plone/restapi/services/contextnavigation/get.py:116 msgid "help_navigation_tree_depth" msgstr "Combien de dossiers doivent être inclus avant que l'arborescence de navigation ne s'arrête. 0 signifie aucune limite. 1 inclut uniquement le dossier racine." #. Default: "Only show the contents of the current folder." -#: plone/restapi/services/contextnavigation/get.py:80 +#: plone/restapi/services/contextnavigation/get.py:83 msgid "label_current_folder_only" msgstr "Afficher uniquement le contenu du dossier actuel." #. Default: "Include top node" -#: plone/restapi/services/contextnavigation/get.py:68 +#: plone/restapi/services/contextnavigation/get.py:71 msgid "label_include_top_node" msgstr "Inclure le nœud supérieur" #. Default: "Root node" -#: plone/restapi/services/contextnavigation/get.py:57 +#: plone/restapi/services/contextnavigation/get.py:60 msgid "label_navigation_root_path" msgstr "Noeud principal" #. Default: "Start level" -#: plone/restapi/services/contextnavigation/get.py:95 +#: plone/restapi/services/contextnavigation/get.py:98 msgid "label_navigation_startlevel" msgstr "Niveau de démarrage" #. Default: "Title" -#: plone/restapi/services/contextnavigation/get.py:48 +#: plone/restapi/services/contextnavigation/get.py:51 msgid "label_navigation_title" msgstr "Titre" #. Default: "Navigation tree depth" -#: plone/restapi/services/contextnavigation/get.py:112 +#: plone/restapi/services/contextnavigation/get.py:115 msgid "label_navigation_tree_depth" msgstr "Profondeur de l'arborescence de navigation" diff --git a/src/plone/restapi/locales/plone.restapi.pot b/src/plone/restapi/locales/plone.restapi.pot index 17bca71cfe..d1d2abf57a 100644 --- a/src/plone/restapi/locales/plone.restapi.pot +++ b/src/plone/restapi/locales/plone.restapi.pot @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2022-12-29 15:58+0000\n" +"POT-Creation-Date: 2023-09-25 20:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,11 +17,11 @@ msgstr "" "Preferred-Encodings: utf-8 latin1\n" "Domain: plone.restapi\n" -#: plone/restapi/services/email_send/post.py:81 +#: plone/restapi/services/email_send/post.py:88 msgid "${sender_fullname} via ${portal_title}" msgstr "" -#: plone/restapi/services/email_send/post.py:74 +#: plone/restapi/services/email_send/post.py:81 msgid "A portal user via ${portal_title}" msgstr "" @@ -37,23 +37,23 @@ msgstr "" msgid "Adds sample workflows for testing" msgstr "" -#: plone/restapi/services/aliases/add.py:106 +#: plone/restapi/services/aliases/add.py:107 msgid "Alternative urls that point to themselves will cause an endless cycle of redirects." msgstr "" -#: plone/restapi/configure.zcml:115 +#: plone/restapi/configure.zcml:116 msgid "Blocks" msgstr "" -#: plone/restapi/configure.zcml:122 +#: plone/restapi/configure.zcml:123 msgid "Blocks (Editable Layout)" msgstr "" -#: plone/restapi/configure.zcml:122 +#: plone/restapi/configure.zcml:123 msgid "Enables Volto Blocks (editable layout) support" msgstr "" -#: plone/restapi/configure.zcml:115 +#: plone/restapi/configure.zcml:116 msgid "Enables Volto Blocks support" msgstr "" @@ -61,7 +61,7 @@ msgstr "" msgid "Enables blocks on the Document content type" msgstr "" -#: plone/restapi/services/contextnavigation/get.py:133 +#: plone/restapi/services/contextnavigation/get.py:136 msgid "Enter a valid scale name (see 'Image Handling' control panel) to override (e.g. icon, tile, thumb, mini, preview, ... ). Leave empty to use default (see 'Site' control panel)." msgstr "" @@ -70,15 +70,15 @@ msgid "Error in fields. ${errors_to_string}" msgstr "" #. Default: "The reset_token is expired." -#: plone/restapi/services/users/add.py:312 +#: plone/restapi/services/users/add.py:319 msgid "Expired Token" msgstr "" -#: plone/restapi/services/contextnavigation/get.py:126 +#: plone/restapi/services/contextnavigation/get.py:129 msgid "If enabled, the portlet will not show document type icons." msgstr "" -#: plone/restapi/services/contextnavigation/get.py:145 +#: plone/restapi/services/contextnavigation/get.py:148 msgid "If enabled, the portlet will not show thumbs." msgstr "" @@ -94,11 +94,11 @@ msgstr "" msgid "Layout" msgstr "" -#: plone/restapi/services/contextnavigation/get.py:195 +#: plone/restapi/services/contextnavigation/get.py:197 msgid "Navigation" msgstr "" -#: plone/restapi/services/contextnavigation/get.py:132 +#: plone/restapi/services/contextnavigation/get.py:135 msgid "Override thumb scale" msgstr "" @@ -126,23 +126,23 @@ msgstr "" msgid "Roles" msgstr "" -#: plone/restapi/services/users/add.py:350 +#: plone/restapi/services/users/add.py:357 msgid "See the user endpoint documentation for the valid parameters." msgstr "" -#: plone/restapi/services/contextnavigation/get.py:125 +#: plone/restapi/services/contextnavigation/get.py:128 msgid "Suppress Icons" msgstr "" -#: plone/restapi/services/contextnavigation/get.py:144 +#: plone/restapi/services/contextnavigation/get.py:147 msgid "Suppress thumbs" msgstr "" -#: plone/restapi/services/users/add.py:342 +#: plone/restapi/services/users/add.py:349 msgid "The password passed as 'old_password' is wrong." msgstr "" -#: plone/restapi/services/users/add.py:307 +#: plone/restapi/services/users/add.py:314 msgid "The reset_token is unknown/not valid." msgstr "" @@ -150,11 +150,11 @@ msgstr "" msgid "Volto Blocks" msgstr "" -#: plone/restapi/services/users/update.py:119 +#: plone/restapi/services/users/update.py:151 msgid "You are not authorized to perform this action" msgstr "" -#: plone/restapi/services/email_send/post.py:91 +#: plone/restapi/services/email_send/post.py:98 msgid "You are receiving this mail because ${sender_fullname} sent this message via the site ${portal_title}:" msgstr "" @@ -162,14 +162,22 @@ msgstr "" msgid "You can't send both password and sendPasswordReset." msgstr "" -#: plone/restapi/services/users/add.py:322 +#: plone/restapi/services/users/add.py:329 msgid "You can't set a password without a password reset token." msgstr "" -#: plone/restapi/services/users/update.py:125 +#: plone/restapi/services/users/update.py:120 +msgid "You can't update roles of this user" +msgstr "" + +#: plone/restapi/services/users/update.py:157 msgid "You can't update the properties of this user" msgstr "" +#: plone/restapi/services/users/update.py:94 +msgid "You can't update this user" +msgstr "" + #: plone/restapi/services/users/add.py:284 msgid "You can't use 'reset_token' and 'old_password' together." msgstr "" @@ -182,72 +190,72 @@ msgstr "" msgid "You need AddPortalMember permission." msgstr "" -#: plone/restapi/services/users/add.py:329 +#: plone/restapi/services/users/add.py:336 msgid "You need to be logged in as the user '${username}' to set the password." msgstr "" #. Default: "Missing dependency" -#: plone/restapi/services/addons/addons.py:207 +#: plone/restapi/services/addons/addons.py:212 msgid "dependency_missing" msgstr "" #. Default: "If selected, the navigation tree will only show the current folder and its children at all times." -#: plone/restapi/services/contextnavigation/get.py:84 +#: plone/restapi/services/contextnavigation/get.py:87 msgid "help_current_folder_only" msgstr "" #. Default: "Whether or not to show the top, or 'root', node in the navigation tree. This is affected by the 'Start level' setting." -#: plone/restapi/services/contextnavigation/get.py:69 +#: plone/restapi/services/contextnavigation/get.py:72 msgid "help_include_top_node" msgstr "" #. Default: "You may search for and choose a folder to act as the root of the navigation tree. Leave blank to use the Plone site root." -#: plone/restapi/services/contextnavigation/get.py:58 +#: plone/restapi/services/contextnavigation/get.py:61 msgid "help_navigation_root" msgstr "" #. Default: "An integer value that specifies the number of folder levels below the site root that must be exceeded before the navigation tree will display. 0 means that the navigation tree should be displayed everywhere including pages in the root of the site. 1 means the tree only shows up inside folders located in the root and downwards, never showing at the top level." -#: plone/restapi/services/contextnavigation/get.py:96 +#: plone/restapi/services/contextnavigation/get.py:99 msgid "help_navigation_start_level" msgstr "" #. Default: "The title of the navigation tree." -#: plone/restapi/services/contextnavigation/get.py:49 +#: plone/restapi/services/contextnavigation/get.py:52 msgid "help_navigation_title" msgstr "" #. Default: "How many folders should be included before the navigation tree stops. 0 means no limit. 1 only includes the root folder." -#: plone/restapi/services/contextnavigation/get.py:113 +#: plone/restapi/services/contextnavigation/get.py:116 msgid "help_navigation_tree_depth" msgstr "" #. Default: "Only show the contents of the current folder." -#: plone/restapi/services/contextnavigation/get.py:80 +#: plone/restapi/services/contextnavigation/get.py:83 msgid "label_current_folder_only" msgstr "" #. Default: "Include top node" -#: plone/restapi/services/contextnavigation/get.py:68 +#: plone/restapi/services/contextnavigation/get.py:71 msgid "label_include_top_node" msgstr "" #. Default: "Root node" -#: plone/restapi/services/contextnavigation/get.py:57 +#: plone/restapi/services/contextnavigation/get.py:60 msgid "label_navigation_root_path" msgstr "" #. Default: "Start level" -#: plone/restapi/services/contextnavigation/get.py:95 +#: plone/restapi/services/contextnavigation/get.py:98 msgid "label_navigation_startlevel" msgstr "" #. Default: "Title" -#: plone/restapi/services/contextnavigation/get.py:48 +#: plone/restapi/services/contextnavigation/get.py:51 msgid "label_navigation_title" msgstr "" #. Default: "Navigation tree depth" -#: plone/restapi/services/contextnavigation/get.py:112 +#: plone/restapi/services/contextnavigation/get.py:115 msgid "label_navigation_tree_depth" msgstr "" diff --git a/src/plone/restapi/locales/pt_BR/LC_MESSAGES/plone.restapi.po b/src/plone/restapi/locales/pt_BR/LC_MESSAGES/plone.restapi.po index 1c5afcc61d..dd921dca1f 100644 --- a/src/plone/restapi/locales/pt_BR/LC_MESSAGES/plone.restapi.po +++ b/src/plone/restapi/locales/pt_BR/LC_MESSAGES/plone.restapi.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: plone.restapi\n" -"POT-Creation-Date: 2022-12-29 15:58+0000\n" +"POT-Creation-Date: 2023-09-25 20:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: Wesley Barroso Lopes\n" "Language-Team: \n" @@ -14,11 +14,11 @@ msgstr "" "Preferred-Encodings: utf-8 latin1\n" "Domain: plone.restapi\n" -#: plone/restapi/services/email_send/post.py:81 +#: plone/restapi/services/email_send/post.py:88 msgid "${sender_fullname} via ${portal_title}" msgstr "" -#: plone/restapi/services/email_send/post.py:74 +#: plone/restapi/services/email_send/post.py:81 msgid "A portal user via ${portal_title}" msgstr "Um usuário do portal via ${portal_title}" @@ -34,23 +34,23 @@ msgstr "Adiciona tipos de conteúdo de amostra para teste" msgid "Adds sample workflows for testing" msgstr "Adiciona workflows de amostra para teste" -#: plone/restapi/services/aliases/add.py:106 +#: plone/restapi/services/aliases/add.py:107 msgid "Alternative urls that point to themselves will cause an endless cycle of redirects." msgstr "URLs alternativas que apontam para si mesmas causarão um ciclo interminável de redirecionamentos." -#: plone/restapi/configure.zcml:115 +#: plone/restapi/configure.zcml:116 msgid "Blocks" msgstr "Blocos" -#: plone/restapi/configure.zcml:122 +#: plone/restapi/configure.zcml:123 msgid "Blocks (Editable Layout)" msgstr "Blocos (Layout Editável)" -#: plone/restapi/configure.zcml:122 +#: plone/restapi/configure.zcml:123 msgid "Enables Volto Blocks (editable layout) support" msgstr "Habilita o suporte a Blocos Volto (layout editável)" -#: plone/restapi/configure.zcml:115 +#: plone/restapi/configure.zcml:116 msgid "Enables Volto Blocks support" msgstr "Habilita o suporte a Blocos Volto" @@ -58,7 +58,7 @@ msgstr "Habilita o suporte a Blocos Volto" msgid "Enables blocks on the Document content type" msgstr "Habilita blocos no tipo de conteúdo Página" -#: plone/restapi/services/contextnavigation/get.py:133 +#: plone/restapi/services/contextnavigation/get.py:136 msgid "Enter a valid scale name (see 'Image Handling' control panel) to override (e.g. icon, tile, thumb, mini, preview, ... ). Leave empty to use default (see 'Site' control panel)." msgstr "Informe um nome de escala válido (ver 'Manipulação de Imagem' no painel de controle) para sobrescrever (por exemplo, icon, tile, thumb, mini, preview…). Deixe em branco para usar o valor padrão (ver 'Site' no painel de controle)." @@ -67,15 +67,15 @@ msgid "Error in fields. ${errors_to_string}" msgstr "Erros em campos. ${errors_to_string}" #. Default: "The reset_token is expired." -#: plone/restapi/services/users/add.py:312 +#: plone/restapi/services/users/add.py:319 msgid "Expired Token" msgstr "Token Expirádo" -#: plone/restapi/services/contextnavigation/get.py:126 +#: plone/restapi/services/contextnavigation/get.py:129 msgid "If enabled, the portlet will not show document type icons." msgstr "Caso ativado, o portlet não mostrará os ícones do tipo de conteúdo" -#: plone/restapi/services/contextnavigation/get.py:145 +#: plone/restapi/services/contextnavigation/get.py:148 msgid "If enabled, the portlet will not show thumbs." msgstr "Caso ativado, o portlet não mostrará miniaturas." @@ -91,11 +91,11 @@ msgstr "Se você informar 'Redefinir Token' você deve informar 'Nova Senha'" msgid "Layout" msgstr "Layout" -#: plone/restapi/services/contextnavigation/get.py:195 +#: plone/restapi/services/contextnavigation/get.py:197 msgid "Navigation" msgstr "Navegação" -#: plone/restapi/services/contextnavigation/get.py:132 +#: plone/restapi/services/contextnavigation/get.py:135 msgid "Override thumb scale" msgstr "Sobrescrever a escala da miniatura" @@ -123,23 +123,23 @@ msgstr "Revertido para revisão ${version}" msgid "Roles" msgstr "Papéis" -#: plone/restapi/services/users/add.py:350 +#: plone/restapi/services/users/add.py:357 msgid "See the user endpoint documentation for the valid parameters." msgstr "Consulte a documentação do endpoint user para obter os parâmetros válidos." -#: plone/restapi/services/contextnavigation/get.py:125 +#: plone/restapi/services/contextnavigation/get.py:128 msgid "Suppress Icons" msgstr "Suprimir ícones" -#: plone/restapi/services/contextnavigation/get.py:144 +#: plone/restapi/services/contextnavigation/get.py:147 msgid "Suppress thumbs" msgstr "Suprimir miniaturas" -#: plone/restapi/services/users/add.py:342 +#: plone/restapi/services/users/add.py:349 msgid "The password passed as 'old_password' is wrong." msgstr "A Senha passada como 'Senha Antiga' não é válida." -#: plone/restapi/services/users/add.py:307 +#: plone/restapi/services/users/add.py:314 msgid "The reset_token is unknown/not valid." msgstr "O reset_token é desconhecido/inválido." @@ -147,11 +147,11 @@ msgstr "O reset_token é desconhecido/inválido." msgid "Volto Blocks" msgstr "Blocos Volto" -#: plone/restapi/services/users/update.py:119 +#: plone/restapi/services/users/update.py:151 msgid "You are not authorized to perform this action" msgstr "Você não está autorizado a realizar esta ação" -#: plone/restapi/services/email_send/post.py:91 +#: plone/restapi/services/email_send/post.py:98 msgid "You are receiving this mail because ${sender_fullname} sent this message via the site ${portal_title}:" msgstr "Você está recebendo este e-mail porque ${sender_fullname} enviou esta mensagem através do site ${portal_title}:" @@ -159,14 +159,22 @@ msgstr "Você está recebendo este e-mail porque ${sender_fullname} enviou esta msgid "You can't send both password and sendPasswordReset." msgstr "Você não pode informar Senha e 'Envie um e-mail de confirmação com um link para definir a senha'." -#: plone/restapi/services/users/add.py:322 +#: plone/restapi/services/users/add.py:329 msgid "You can't set a password without a password reset token." msgstr "Você não pode definir uma senha sem um token de redefinição de senha." -#: plone/restapi/services/users/update.py:125 +#: plone/restapi/services/users/update.py:120 +msgid "You can't update roles of this user" +msgstr "Você não pode atualizar os papéis deste usuário" + +#: plone/restapi/services/users/update.py:157 msgid "You can't update the properties of this user" msgstr "Você não pode atualizar as propriedades deste usuário" +#: plone/restapi/services/users/update.py:94 +msgid "You can't update this user" +msgstr "Você não pode atualizar este usuário" + #: plone/restapi/services/users/add.py:284 msgid "You can't use 'reset_token' and 'old_password' together." msgstr "Você não pode usar 'Redefinir Token' e 'Senha Antiga' juntos." @@ -179,72 +187,72 @@ msgstr "Você deve informar uma Senha ou 'Envie um e-mail de confirmação com u msgid "You need AddPortalMember permission." msgstr "Você precisa da permissão AddPortalMember." -#: plone/restapi/services/users/add.py:329 +#: plone/restapi/services/users/add.py:336 msgid "You need to be logged in as the user '${username}' to set the password." msgstr "Você precisa estar logado como o usuário '${username}' para definir a senha." #. Default: "Missing dependency" -#: plone/restapi/services/addons/addons.py:207 +#: plone/restapi/services/addons/addons.py:212 msgid "dependency_missing" msgstr "Dependência ausente" #. Default: "If selected, the navigation tree will only show the current folder and its children at all times." -#: plone/restapi/services/contextnavigation/get.py:84 +#: plone/restapi/services/contextnavigation/get.py:87 msgid "help_current_folder_only" msgstr "Caso selecionado, a árvore de navegação irá sempre mostrar somente a pasta atual e seus filhos." #. Default: "Whether or not to show the top, or 'root', node in the navigation tree. This is affected by the 'Start level' setting." -#: plone/restapi/services/contextnavigation/get.py:69 +#: plone/restapi/services/contextnavigation/get.py:72 msgid "help_include_top_node" msgstr "Se o nodo topo, ou 'raiz', deve ser ou não exibido na árvore de navegação. Isso é afetado pela configuração do 'Nível inicial'." #. Default: "You may search for and choose a folder to act as the root of the navigation tree. Leave blank to use the Plone site root." -#: plone/restapi/services/contextnavigation/get.py:58 +#: plone/restapi/services/contextnavigation/get.py:61 msgid "help_navigation_root" msgstr "Você pode procurar por e selecionar uma pasta para agir como a raiz da árvore de navegação. Deixe em branco para usar a raiz do site Plone." #. Default: "An integer value that specifies the number of folder levels below the site root that must be exceeded before the navigation tree will display. 0 means that the navigation tree should be displayed everywhere including pages in the root of the site. 1 means the tree only shows up inside folders located in the root and downwards, never showing at the top level." -#: plone/restapi/services/contextnavigation/get.py:96 +#: plone/restapi/services/contextnavigation/get.py:99 msgid "help_navigation_start_level" msgstr "Um valor inteiro que especifica o número de níveis de pastas abaixo da raiz do site que deve ser excedido antes que a árvore de navegação seja mostrada. 0 significa que a árvore de navegação deve ser mostrada em qualquer lugar, incluindo páginas na raiz do site. 1 significa que a árvore somente aparecerá dentro de pastas localizadas na raiz e abaixo delas, nunca aparecendo no nível raiz." #. Default: "The title of the navigation tree." -#: plone/restapi/services/contextnavigation/get.py:49 +#: plone/restapi/services/contextnavigation/get.py:52 msgid "help_navigation_title" msgstr "O título da árvore de navegação." #. Default: "How many folders should be included before the navigation tree stops. 0 means no limit. 1 only includes the root folder." -#: plone/restapi/services/contextnavigation/get.py:113 +#: plone/restapi/services/contextnavigation/get.py:116 msgid "help_navigation_tree_depth" msgstr "Quantas pastas devem ser incluídas antes que a árvore de navegação pare. 0 significa sem limite. 1 significa incluir somente a pasta raiz." #. Default: "Only show the contents of the current folder." -#: plone/restapi/services/contextnavigation/get.py:80 +#: plone/restapi/services/contextnavigation/get.py:83 msgid "label_current_folder_only" msgstr "Apenas exibir o conteúdo da pasta atual." #. Default: "Include top node" -#: plone/restapi/services/contextnavigation/get.py:68 +#: plone/restapi/services/contextnavigation/get.py:71 msgid "label_include_top_node" msgstr "Incluir o nodo topo" #. Default: "Root node" -#: plone/restapi/services/contextnavigation/get.py:57 +#: plone/restapi/services/contextnavigation/get.py:60 msgid "label_navigation_root_path" msgstr "Nodo raiz" #. Default: "Start level" -#: plone/restapi/services/contextnavigation/get.py:95 +#: plone/restapi/services/contextnavigation/get.py:98 msgid "label_navigation_startlevel" msgstr "Nível inicial" #. Default: "Title" -#: plone/restapi/services/contextnavigation/get.py:48 +#: plone/restapi/services/contextnavigation/get.py:51 msgid "label_navigation_title" msgstr "Título" #. Default: "Navigation tree depth" -#: plone/restapi/services/contextnavigation/get.py:112 +#: plone/restapi/services/contextnavigation/get.py:115 msgid "label_navigation_tree_depth" msgstr "Profundidade da árvore de navegação" diff --git a/src/plone/restapi/permissions.py b/src/plone/restapi/permissions.py index 95cd445ed7..af1e36b361 100644 --- a/src/plone/restapi/permissions.py +++ b/src/plone/restapi/permissions.py @@ -2,3 +2,5 @@ # permissions. Granted to Anonymous (i.e. everyone) by default via rolemap.xml UseRESTAPI = "plone.restapi: Use REST API" + +PloneManageUsers = "Plone Site Setup: Users and Groups" diff --git a/src/plone/restapi/profiles/default/metadata.xml b/src/plone/restapi/profiles/default/metadata.xml index 81970e34ed..1e4872e498 100644 --- a/src/plone/restapi/profiles/default/metadata.xml +++ b/src/plone/restapi/profiles/default/metadata.xml @@ -1,4 +1,4 @@ - 0006 + 0007 diff --git a/src/plone/restapi/profiles/default/rolemap.xml b/src/plone/restapi/profiles/default/rolemap.xml index d6fcc0d439..b0d19ef9fb 100644 --- a/src/plone/restapi/profiles/default/rolemap.xml +++ b/src/plone/restapi/profiles/default/rolemap.xml @@ -9,6 +9,7 @@ + diff --git a/src/plone/restapi/serializer/utils.py b/src/plone/restapi/serializer/utils.py index e0c3bbdeff..4ac4e4ee7b 100644 --- a/src/plone/restapi/serializer/utils.py +++ b/src/plone/restapi/serializer/utils.py @@ -1,9 +1,9 @@ -from plone.dexterity.schema import lookup_fti from plone.app.uuid.utils import uuidToCatalogBrain +from plone.dexterity.schema import lookup_fti from plone.restapi.interfaces import IObjectPrimaryFieldTarget from zope.component import queryMultiAdapter -from zope.i18n import translate from zope.globalrequest import getRequest +from zope.i18n import translate import re @@ -43,7 +43,7 @@ def resolve_uid(path): def uid_to_url(path): - path, brain = resolve_uid(path) + path, _brain = resolve_uid(path) return path diff --git a/src/plone/restapi/services/groups/add.py b/src/plone/restapi/services/groups/add.py index ae4561cc19..e9b5ab5d11 100644 --- a/src/plone/restapi/services/groups/add.py +++ b/src/plone/restapi/services/groups/add.py @@ -1,6 +1,8 @@ +from AccessControl import getSecurityManager from plone.restapi.deserializer import json_body from plone.restapi.interfaces import ISerializeToJson from plone.restapi.services import Service +from Products.CMFCore.permissions import ManagePortal from Products.CMFCore.utils import getToolByName from zExceptions import BadRequest from zope.component import queryMultiAdapter @@ -13,6 +15,10 @@ class GroupsPost(Service): """Creates a new group.""" + @property + def is_zope_manager(self): + return getSecurityManager().checkPermission(ManagePortal, self.context) + def reply(self): portal = getSite() data = json_body(self.request) @@ -22,10 +28,16 @@ def reply(self): if not groupname: raise BadRequest("Property 'groupname' is required") + roles = data.get("roles", []) + + if not self.is_zope_manager and "Manager" in roles: + raise BadRequest( + "You don't have permission to create a group with the 'Manager' role" + ) + email = data.get("email", None) title = data.get("title", None) description = data.get("description", None) - roles = data.get("roles", None) groups = data.get("groups", None) users = data.get("users", []) diff --git a/src/plone/restapi/services/groups/configure.zcml b/src/plone/restapi/services/groups/configure.zcml index 491f2f9a47..8fc7b6e3ec 100644 --- a/src/plone/restapi/services/groups/configure.zcml +++ b/src/plone/restapi/services/groups/configure.zcml @@ -8,7 +8,7 @@ method="GET" factory=".get.GroupsGet" for="Products.CMFCore.interfaces.ISiteRoot" - permission="cmf.ManagePortal" + permission="plone.app.controlpanel.UsersAndGroups" name="@groups" /> @@ -16,7 +16,7 @@ method="PATCH" factory=".update.GroupsPatch" for="Products.CMFCore.interfaces.ISiteRoot" - permission="cmf.ManagePortal" + permission="plone.app.controlpanel.UsersAndGroups" name="@groups" /> @@ -24,7 +24,7 @@ method="POST" factory=".add.GroupsPost" for="Products.CMFCore.interfaces.ISiteRoot" - permission="cmf.ManagePortal" + permission="plone.app.controlpanel.UsersAndGroups" name="@groups" /> @@ -32,7 +32,7 @@ method="DELETE" factory=".delete.GroupsDelete" for="Products.CMFCore.interfaces.ISiteRoot" - permission="cmf.ManagePortal" + permission="plone.app.controlpanel.UsersAndGroups" name="@groups" /> diff --git a/src/plone/restapi/services/groups/delete.py b/src/plone/restapi/services/groups/delete.py index 395d33f3dc..7c13cd62b9 100644 --- a/src/plone/restapi/services/groups/delete.py +++ b/src/plone/restapi/services/groups/delete.py @@ -1,5 +1,8 @@ +from AccessControl import getSecurityManager from plone.restapi.services import Service +from Products.CMFCore.permissions import ManagePortal from Products.CMFCore.utils import getToolByName +from zExceptions import BadRequest from zExceptions import NotFound from zope.component.hooks import getSite from zope.interface import implementer @@ -19,6 +22,10 @@ def publishTraverse(self, request, name): self.params.append(name) return self + @property + def is_zope_manager(self): + return getSecurityManager().checkPermission(ManagePortal, self.context) + @property def _get_group_id(self): if len(self.params) != 1: @@ -38,6 +45,11 @@ def reply(self): if not group: raise NotFound("Trying to delete a non-existing group.") + if not self.is_zope_manager and "Manager" in group.getRoles(): + raise BadRequest( + "You don't have permission to delete a group with the Manager role" + ) + delete_successful = portal_groups.removeGroup(self._get_group_id) if delete_successful: return self.reply_no_content() diff --git a/src/plone/restapi/services/groups/update.py b/src/plone/restapi/services/groups/update.py index 9d17ebc77c..5cd3212aa9 100644 --- a/src/plone/restapi/services/groups/update.py +++ b/src/plone/restapi/services/groups/update.py @@ -1,5 +1,7 @@ +from AccessControl import getSecurityManager from plone.restapi.deserializer import json_body from plone.restapi.services import Service +from Products.CMFCore.permissions import ManagePortal from Products.CMFCore.utils import getToolByName from zExceptions import BadRequest from zope.component.hooks import getSite @@ -34,6 +36,36 @@ def __init__(self, context, request): super().__init__(context, request) self.params = [] + @property + def is_zope_manager(self): + return getSecurityManager().checkPermission(ManagePortal, self.context) + + def can_update(self, group, users, roles, groups): + # Manager can update + if self.is_zope_manager: + return True + # Does not allow an Site Administrator to add users to groups + # with the Manager role + current_group_roles = group.getRoles() + if "Manager" in current_group_roles and users: + return False + # Does not allow an Site Administrator set Manager to group + result_roles = True + if roles: + if "Manager" in roles: + result_roles = "Manager" in current_group_roles + else: + result_roles = "Manager" not in current_group_roles + # Does not allow an Site Administrator add group to group with Manager role + result_groups = True + if groups: + for assign_group_id in groups: + assign_group = self._get_group(assign_group_id) + if "Manager" in assign_group.getRoles(): + result_groups = False + break + return result_roles and result_groups + def publishTraverse(self, request, name): # Consume any path segments after /@groups as parameters self.params.append(name) @@ -57,9 +89,14 @@ def reply(self): if not group: raise BadRequest("Trying to update a non-existing group.") + users = data.get("users", {}) roles = data.get("roles", None) groups = data.get("groups", None) - users = data.get("users", {}) + + if not self.can_update(group, users, roles, groups): + raise BadRequest( + "You don't have permission to assign a 'Manager' role to a group." + ) # Disable CSRF protection if "IDisableCSRFProtection" in dir(plone.protect.interfaces): diff --git a/src/plone/restapi/services/roles/configure.zcml b/src/plone/restapi/services/roles/configure.zcml index cec643c321..4dfd989234 100644 --- a/src/plone/restapi/services/roles/configure.zcml +++ b/src/plone/restapi/services/roles/configure.zcml @@ -7,7 +7,7 @@ method="GET" factory=".get.RolesGet" for="Products.CMFPlone.interfaces.IPloneSiteRoot" - permission="cmf.ManagePortal" + permission="plone.app.controlpanel.UsersAndGroups" name="@roles" /> diff --git a/src/plone/restapi/services/users/add.py b/src/plone/restapi/services/users/add.py index a032be99e2..2750d401fc 100644 --- a/src/plone/restapi/services/users/add.py +++ b/src/plone/restapi/services/users/add.py @@ -4,6 +4,7 @@ from plone.restapi.bbb import ISecuritySchema from plone.restapi.deserializer import json_body from plone.restapi.interfaces import ISerializeToJson +from plone.restapi.permissions import PloneManageUsers from plone.restapi.services import Service from Products.CMFCore.permissions import AddPortalMember from Products.CMFCore.permissions import SetOwnPassword @@ -244,7 +245,7 @@ def _error(self, status, _type, msgid): @property def can_manage_users(self): sm = getSecurityManager() - return sm.checkPermission("plone.app.controlpanel.UsersAndGroups", self.context) + return sm.checkPermission(PloneManageUsers, self.context) @property def can_set_own_password(self): diff --git a/src/plone/restapi/services/users/configure.zcml b/src/plone/restapi/services/users/configure.zcml index e7d3505dec..b3c48ddaee 100644 --- a/src/plone/restapi/services/users/configure.zcml +++ b/src/plone/restapi/services/users/configure.zcml @@ -32,7 +32,7 @@ method="DELETE" factory=".delete.UsersDelete" for="Products.CMFCore.interfaces.ISiteRoot" - permission="cmf.ManagePortal" + permission="plone.app.controlpanel.UsersAndGroups" name="@users" /> diff --git a/src/plone/restapi/services/users/delete.py b/src/plone/restapi/services/users/delete.py index e7517f6655..bac99f22dd 100644 --- a/src/plone/restapi/services/users/delete.py +++ b/src/plone/restapi/services/users/delete.py @@ -1,6 +1,10 @@ +from AccessControl import getSecurityManager from plone.restapi.services import Service +from Products.CMFCore.interfaces import ISiteRoot +from Products.CMFCore.permissions import ManagePortal from Products.CMFCore.utils import getToolByName -from zope.component.hooks import getSite +from zExceptions import BadRequest +from zope.component import getUtility from zope.interface import implementer from zope.publisher.interfaces import IPublishTraverse @@ -15,6 +19,12 @@ class UsersDelete(Service): def __init__(self, context, request): super().__init__(context, request) self.params = [] + self.portal_membership = getToolByName(context, "portal_membership") + self.acl_users = getToolByName(context, "acl_users") + + @property + def is_zope_manager(self): + return getSecurityManager().checkPermission(ManagePortal, self.context) def publishTraverse(self, request, name): # Consume any path segments after /@users as parameters @@ -27,9 +37,19 @@ def _get_user_id(self): raise Exception("Must supply exactly one parameter (user id)") return self.params[0] + def _get_user(self, user_id): + return self.portal_membership.getMemberById(user_id) + def reply(self): - portal = getSite() - portal_membership = getToolByName(portal, "portal_membership") + user = self._get_user(self._get_user_id) + if not user: + return self.reply_no_content(status=404) + if not self.is_zope_manager: + current_roles = user.getRoles() + if "Manager" in current_roles: + raise BadRequest( + "You don't have permission to delete a user with 'Manager' role." + ) delete_memberareas = ( self.request.get("delete_memberareas", True) not in FALSE_VALUES @@ -39,12 +59,21 @@ def reply(self): self.request.get("delete_localroles", True) not in FALSE_VALUES ) - delete_successful = portal_membership.deleteMembers( - (self._get_user_id,), - delete_memberareas=delete_memberareas, - delete_localroles=delete_localroles, - ) - if delete_successful: - return self.reply_no_content() - else: + try: + self.acl_users.userFolderDelUsers((self._get_user_id,)) + except (AttributeError, NotImplementedError): return self.reply_no_content(status=404) + + if delete_memberareas: + # Delete member data in portal_memberdata. + mdtool = getToolByName(self.context, "portal_memberdata", None) + if mdtool is not None: + mdtool.deleteMemberData(self._get_user_id) + + if delete_localroles: + # Delete members' local roles. + self.portal_membership.deleteLocalRoles( + getUtility(ISiteRoot), (self._get_user_id,), reindex=1, recursive=1 + ) + + return self.reply_no_content() diff --git a/src/plone/restapi/services/users/get.py b/src/plone/restapi/services/users/get.py index 393237f740..e900b7de6f 100644 --- a/src/plone/restapi/services/users/get.py +++ b/src/plone/restapi/services/users/get.py @@ -7,6 +7,7 @@ from plone.namedfile.browser import USE_DENYLIST from plone.namedfile.utils import stream_data from plone.restapi.interfaces import ISerializeToJson +from plone.restapi.permissions import PloneManageUsers from plone.restapi.services import Service from Products.CMFCore.utils import getToolByName from Products.CMFPlone.utils import normalizeString @@ -177,11 +178,11 @@ def _get_filtered_users(self, query, groups_filter, search_term, limit): def has_permission_to_query(self): sm = getSecurityManager() - return sm.checkPermission("Manage portal", self.context) + return sm.checkPermission(PloneManageUsers, self.context) def has_permission_to_enumerate(self): sm = getSecurityManager() - return sm.checkPermission("Manage portal", self.context) + return sm.checkPermission(PloneManageUsers, self.context) def has_permission_to_access_user_info(self): sm = getSecurityManager() diff --git a/src/plone/restapi/services/users/update.py b/src/plone/restapi/services/users/update.py index 0e07d69d03..428727d1c3 100644 --- a/src/plone/restapi/services/users/update.py +++ b/src/plone/restapi/services/users/update.py @@ -4,7 +4,9 @@ from OFS.Image import Image from plone.restapi import _ from plone.restapi.bbb import ISecuritySchema +from plone.restapi.permissions import PloneManageUsers from plone.restapi.services import Service +from Products.CMFCore.permissions import ManagePortal from Products.CMFCore.permissions import SetOwnPassword from Products.CMFCore.utils import getToolByName from Products.CMFPlone.utils import set_own_login_name @@ -30,6 +32,20 @@ def __init__(self, context, request): super().__init__(context, request) self.params = [] + @property + def is_zope_manager(self): + return getSecurityManager().checkPermission(ManagePortal, self.context) + + def can_change_roles(self, target_roles, current_roles): + if self.is_zope_manager: + return True + return ("Manager" in current_roles) == ("Manager" in list(target_roles)) + + def can_change(self, current_roles): + if self.is_zope_manager: + return True + return "Manager" not in current_roles + def publishTraverse(self, request, name): # Consume any path segments after /@users as parameters self.params.append(name) @@ -67,7 +83,15 @@ def reply(self): security = getAdapter(self.context, ISecuritySchema) if self.can_manage_users: + current_roles = user.getRoles() for key, value in user_settings_to_update.items(): + if key in ["password", "email"]: + if not self.can_change(current_roles): + return self._error( + 403, + "Forbidden", + _("You can't update this user"), + ) if key == "password": self._change_user_password(user, value) elif key == "username": @@ -85,9 +109,16 @@ def reply(self): to_add = [key for key, enabled in roles.items() if enabled] to_remove = [key for key, enabled in roles.items() if not enabled] - target_roles = set(user.getRoles()) - set(to_remove) + target_roles = set(current_roles) - set(to_remove) target_roles = target_roles | set(to_add) + if not self.can_change_roles(target_roles, current_roles): + return self._error( + 403, + "Forbidden", + _("You can't update roles of this user"), + ) + acl_users = getToolByName(self.context, "acl_users") acl_users.userFolderEditUser( principal_id=user.id, @@ -130,7 +161,7 @@ def reply(self): @property def can_manage_users(self): sm = getSecurityManager() - return sm.checkPermission("plone.app.controlpanel.UsersAndGroups", self.context) + return sm.checkPermission(PloneManageUsers, self.context) @property def can_set_own_password(self): diff --git a/src/plone/restapi/tests/http-examples/translated_messages_addons.resp b/src/plone/restapi/tests/http-examples/translated_messages_addons.resp index 28fbcebaa4..6e7df00140 100644 --- a/src/plone/restapi/tests/http-examples/translated_messages_addons.resp +++ b/src/plone/restapi/tests/http-examples/translated_messages_addons.resp @@ -69,8 +69,8 @@ Content-Type: application/json "upgrade_info": { "available": false, "hasProfile": true, - "installedVersion": "0006", - "newVersion": "0006", + "installedVersion": "0007", + "newVersion": "0007", "required": false }, "version": "1.2.3" diff --git a/src/plone/restapi/tests/test_addons.py b/src/plone/restapi/tests/test_addons.py index e309e861df..02a7f6262b 100644 --- a/src/plone/restapi/tests/test_addons.py +++ b/src/plone/restapi/tests/test_addons.py @@ -120,7 +120,7 @@ def _get_upgrade_info(self): "available": True, "hasProfile": True, "installedVersion": "0002", - "newVersion": "0006", + "newVersion": "0007", "required": True, }, _get_upgrade_info(self), @@ -134,8 +134,8 @@ def _get_upgrade_info(self): { "available": False, "hasProfile": True, - "installedVersion": "0006", - "newVersion": "0006", + "installedVersion": "0007", + "newVersion": "0007", "required": False, }, _get_upgrade_info(self), @@ -189,7 +189,7 @@ def _get_upgrade_info(self): "available": True, "hasProfile": True, "installedVersion": "0002", - "newVersion": "0006", + "newVersion": "0007", "required": True, }, _get_upgrade_info(self), diff --git a/src/plone/restapi/tests/test_serializer_group.py b/src/plone/restapi/tests/test_serializer_group.py index 4be5e90ef3..41737e0c31 100644 --- a/src/plone/restapi/tests/test_serializer_group.py +++ b/src/plone/restapi/tests/test_serializer_group.py @@ -1,4 +1,5 @@ from plone import api +from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID from plone.restapi.interfaces import ISerializeToJson from plone.restapi.interfaces import ISerializeToJsonSummary @@ -58,3 +59,24 @@ def test_summary(self): self.assertEqual("Plone Team", group.get("title")) self.assertEqual("We are Plone", group.get("description")) self.assertNotIn("users", group) + + def test_serialize_group_with_member(self): + setRoles(self.portal, TEST_USER_ID, ["Member"]) + group = self.serialize(self.group) + self.assertEqual( + { + "@id": "http://nohost/plone/@groups/ploneteam", + "id": "ploneteam", + "groupname": "ploneteam", + "email": "ploneteam@plone.org", + "title": "Plone Team", + "description": "We are Plone", + "roles": ["Authenticated"], + "members": { + "@id": "http://nohost", + "items_total": 1, + "items": ["test_user_1_"], + }, + }, + group, + ) diff --git a/src/plone/restapi/tests/test_serializer_user.py b/src/plone/restapi/tests/test_serializer_user.py index e3971350f1..41ae20be6c 100644 --- a/src/plone/restapi/tests/test_serializer_user.py +++ b/src/plone/restapi/tests/test_serializer_user.py @@ -1,5 +1,7 @@ from DateTime import DateTime from plone import api +from plone.app.testing import setRoles +from plone.app.testing import TEST_USER_ID from plone.app.users.browser.schemaeditor import applySchema from plone.restapi.interfaces import ISerializeToJson from plone.restapi.testing import PLONE_RESTAPI_DX_INTEGRATION_TESTING @@ -49,7 +51,6 @@ def test_serialize_roles(self): self.assertNotIn("Anonymous", user["roles"]) def test_serialize_custom_member_schema(self): - from plone.app.users.browser.schemaeditor import applySchema member_schema = """ + + diff --git a/src/plone/restapi/upgrades/to0007.py b/src/plone/restapi/upgrades/to0007.py new file mode 100644 index 0000000000..7253e331ad --- /dev/null +++ b/src/plone/restapi/upgrades/to0007.py @@ -0,0 +1,19 @@ +from plone import api + +import logging + + +logger = logging.getLogger(__name__) + + +def site_administrator_permission(setup_context): + """Give permission plone.restapi: Access Plone user information to + Site Administrator""" + api.portal.get().manage_permission( + "plone.restapi: Access Plone user information", + roles=["Manager", "Site Administrator"], + acquire=1, + ) + logger.info( + "Give permission plone.restapi: Access Plone user information to Site Administrator" + )