From e503c8e3b445320b8bce36231aac22da894bf8b5 Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Wed, 19 Jun 2024 12:54:06 +0200 Subject: [PATCH 1/5] searchForMembers: always use utf-8 instead of checking default_charset. This code was still getting the ``IPropertiesTool`` utility. --- news/125.bugfix.1 | 3 +++ src/Products/PlonePAS/tools/membership.py | 10 +--------- 2 files changed, 4 insertions(+), 9 deletions(-) create mode 100644 news/125.bugfix.1 diff --git a/news/125.bugfix.1 b/news/125.bugfix.1 new file mode 100644 index 00000000..8c9bc333 --- /dev/null +++ b/news/125.bugfix.1 @@ -0,0 +1,3 @@ +searchForMembers: always use utf-8 instead of checking default_charset. +This code was still getting the ``IPropertiesTool`` utility. +[maurits] diff --git a/src/Products/PlonePAS/tools/membership.py b/src/Products/PlonePAS/tools/membership.py index 50310fbe..14259c11 100644 --- a/src/Products/PlonePAS/tools/membership.py +++ b/src/Products/PlonePAS/tools/membership.py @@ -12,7 +12,6 @@ from io import BytesIO from OFS.Image import Image from plone.protect.interfaces import IDisableCSRFProtection -from Products.CMFCore.interfaces import IPropertiesTool from Products.CMFCore.MembershipTool import MembershipTool as BaseTool from Products.CMFCore.permissions import ListPortalMembers from Products.CMFCore.permissions import ManagePortal @@ -32,7 +31,6 @@ from zExceptions import BadRequest from ZODB.POSException import ConflictError from zope import event -from zope.component import getUtility from zope.interface import alsoProvides from zope.interface import implementer @@ -44,15 +42,9 @@ default_portrait = "defaultUser.png" logger = logging.getLogger("PlonePAS") -_marker = dict() # type: ignore - -def _unicodify_structure(value, charset=_marker): +def _unicodify_structure(value, charset="utf-8"): """Convert value to unicode.""" - if charset is _marker: - ptool = getUtility(IPropertiesTool) - charset = ptool.getProperty("default_charset", None) - if isinstance(value, str): return charset and safe_unicode(value, charset) or safe_unicode(value) if isinstance(value, list): From b520c00f74f0932ac5341b6c92a26c4ded11fd31 Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Wed, 19 Jun 2024 12:57:05 +0200 Subject: [PATCH 2/5] Remove _unicodify_structure, we can just call safe_unicode. The other code paths in _unicodify_structure are never called. This method was added in Plone 5.0 for getting rid of CMFDefault. https://github.com/plone/Products.PlonePAS/commit/93c0b87125fdef8193ccac84506ea112977e490a --- src/Products/PlonePAS/tools/membership.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/Products/PlonePAS/tools/membership.py b/src/Products/PlonePAS/tools/membership.py index 14259c11..b219f396 100644 --- a/src/Products/PlonePAS/tools/membership.py +++ b/src/Products/PlonePAS/tools/membership.py @@ -43,21 +43,6 @@ logger = logging.getLogger("PlonePAS") -def _unicodify_structure(value, charset="utf-8"): - """Convert value to unicode.""" - if isinstance(value, str): - return charset and safe_unicode(value, charset) or safe_unicode(value) - if isinstance(value, list): - return [_unicodify_structure(val, charset) for val in value] - if isinstance(value, tuple): - return tuple(_unicodify_structure(entry, charset) for entry in value) - if isinstance(value, dict): - for key, val in value.items(): - value[key] = _unicodify_structure(val, charset) - return value - return value - - @implementer(membership.IMembershipTool) class MembershipTool(BaseTool): """PAS-based customization of MembershipTool.""" @@ -182,7 +167,7 @@ def searchForMembers(self, REQUEST=None, **kw): searchmap = REQUEST for key, value in searchmap.items(): if isinstance(value, str): - searchmap[key] = _unicodify_structure(value) + searchmap[key] = safe_unicode(value) else: searchmap = kw From 17e42df24b282aedcf7abc6579a84499b5334288 Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Wed, 19 Jun 2024 13:01:03 +0200 Subject: [PATCH 3/5] Restore searching for member when query uses bytes instead of text. --- news/125.bugfix.2 | 2 ++ src/Products/PlonePAS/tests/test_membershiptool.py | 2 +- src/Products/PlonePAS/tools/membership.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 news/125.bugfix.2 diff --git a/news/125.bugfix.2 b/news/125.bugfix.2 new file mode 100644 index 00000000..e03522bf --- /dev/null +++ b/news/125.bugfix.2 @@ -0,0 +1,2 @@ +Restore searching for member when query uses bytes instead of text. +[maurits] diff --git a/src/Products/PlonePAS/tests/test_membershiptool.py b/src/Products/PlonePAS/tests/test_membershiptool.py index b0d3480b..c380e73d 100644 --- a/src/Products/PlonePAS/tests/test_membershiptool.py +++ b/src/Products/PlonePAS/tests/test_membershiptool.py @@ -904,7 +904,7 @@ def testSearchByRequestObj(self): self.assertEqual(len(search(REQUEST=dict(name="jürgen"))), 1) - self.assertEqual(len(search(REQUEST=dict(name="jürgen"))), 1) + self.assertEqual(len(search(REQUEST=dict(name="jürgen".encode()))), 1) class TestDefaultUserAndPasswordNotChanged(unittest.TestCase): diff --git a/src/Products/PlonePAS/tools/membership.py b/src/Products/PlonePAS/tools/membership.py index b219f396..153b7763 100644 --- a/src/Products/PlonePAS/tools/membership.py +++ b/src/Products/PlonePAS/tools/membership.py @@ -166,7 +166,7 @@ def searchForMembers(self, REQUEST=None, **kw): if REQUEST is not None: searchmap = REQUEST for key, value in searchmap.items(): - if isinstance(value, str): + if isinstance(value, bytes): searchmap[key] = safe_unicode(value) else: searchmap = kw From 6ab3cd29a1392dca74712e5d5a9558b9765f8181 Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Wed, 19 Jun 2024 13:38:05 +0200 Subject: [PATCH 4/5] Deprecate our utils.safe_unicode in favour of safe_text from plone.base. That one actually works. :-) Our version did nothing, due to some mistakes in porting to Python 3. --- src/Products/PlonePAS/utils.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Products/PlonePAS/utils.py b/src/Products/PlonePAS/utils.py index c259c66e..d3987e2d 100644 --- a/src/Products/PlonePAS/utils.py +++ b/src/Products/PlonePAS/utils.py @@ -3,6 +3,14 @@ from Products.PluggableAuthService.interfaces.plugins import IGroupsPlugin import urllib +import zope.deferredimport + + +zope.deferredimport.deprecated( + "You should use plone.base.utils.safe_text instead. " + "This alias will be removed in Plone 7.0", + safe_unicode="plone.base.utils:safe_text", +) def unique(iterable): @@ -178,18 +186,6 @@ def getGroupsForPrincipal(principal, plugins, request=None): return list(groups) -def safe_unicode(value, encoding="utf-8"): - """Converts a value to unicode, even it is already a unicode string.""" - if isinstance(value, str): - return value - elif isinstance(value, str): - try: - value = str(value, encoding) - except UnicodeDecodeError: - value = value.decode("utf-8", "replace") - return value - - # Imported from Products.CMFCore.MemberdataTool as it has now been removed. class CleanupTemp: """Used to cleanup _v_temps at the end of the request.""" From f0ba7d71fd89d99fd85f9460fe94386a51043150 Mon Sep 17 00:00:00 2001 From: Maurits van Rees Date: Wed, 19 Jun 2024 13:40:57 +0200 Subject: [PATCH 5/5] Use plone.base.utils.safe_text ourselves. --- src/Products/PlonePAS/plugins/property.py | 6 +++--- src/Products/PlonePAS/tools/membership.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Products/PlonePAS/plugins/property.py b/src/Products/PlonePAS/plugins/property.py index 775e2c45..f2a4f76f 100644 --- a/src/Products/PlonePAS/plugins/property.py +++ b/src/Products/PlonePAS/plugins/property.py @@ -6,11 +6,11 @@ from AccessControl.class_init import InitializeClass from App.special_dtml import DTMLFile from BTrees.OOBTree import OOBTree +from plone.base.utils import safe_text from Products.CMFCore.utils import getToolByName from Products.PlonePAS.interfaces.plugins import IMutablePropertiesPlugin from Products.PlonePAS.sheet import MutablePropertySheet from Products.PlonePAS.sheet import validateValue -from Products.PlonePAS.utils import safe_unicode from Products.PluggableAuthService.interfaces.plugins import IPropertiesPlugin from Products.PluggableAuthService.interfaces.plugins import IUserEnumerationPlugin from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin @@ -219,9 +219,9 @@ def testMemberData(self, memberdata, criteria, exact_match=False): return False if isStringType(testvalue): - testvalue = safe_unicode(testvalue.lower()) + testvalue = safe_text(testvalue.lower()) if isStringType(value): - value = safe_unicode(value.lower()) + value = safe_text(value.lower()) if exact_match: if value != testvalue: diff --git a/src/Products/PlonePAS/tools/membership.py b/src/Products/PlonePAS/tools/membership.py index 153b7763..d25e897c 100644 --- a/src/Products/PlonePAS/tools/membership.py +++ b/src/Products/PlonePAS/tools/membership.py @@ -11,6 +11,7 @@ from DateTime import DateTime from io import BytesIO from OFS.Image import Image +from plone.base.utils import safe_text from plone.protect.interfaces import IDisableCSRFProtection from Products.CMFCore.MembershipTool import MembershipTool as BaseTool from Products.CMFCore.permissions import ListPortalMembers @@ -26,7 +27,6 @@ from Products.PlonePAS.events import UserLoggedOutEvent from Products.PlonePAS.interfaces import membership from Products.PlonePAS.utils import cleanId -from Products.PlonePAS.utils import safe_unicode from Products.PlonePAS.utils import scale_image from zExceptions import BadRequest from ZODB.POSException import ConflictError @@ -167,7 +167,7 @@ def searchForMembers(self, REQUEST=None, **kw): searchmap = REQUEST for key, value in searchmap.items(): if isinstance(value, bytes): - searchmap[key] = safe_unicode(value) + searchmap[key] = safe_text(value) else: searchmap = kw