From e5a1372591e2bc2dd4778fac96b17305a0307493 Mon Sep 17 00:00:00 2001 From: Mauro Amico Date: Thu, 13 Jun 2024 12:33:11 +0200 Subject: [PATCH] SEE_OWN_ANONYMOUS_BOOKINGS --- CHANGES.rst | 4 +- README.rst | 4 + .../restapi/services/bookings/search.py | 32 ++- .../tests/test_prenotazioni_search.py | 199 ++++++++++++++++++ 4 files changed, 234 insertions(+), 5 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 28b90504..c122f729 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,9 @@ Changelog 2.7.3 (unreleased) ------------------ -- Nothing changed yet. +- With an experimental envionment `SEE_OWN_ANONYMOUS_BOOKINGS` set to `True`, the endpoint will return + also the bookings created by anonymous users with the same fiscalcode of the authenticated user. + [mamico] 2.7.2 (2024-06-03) diff --git a/README.rst b/README.rst index 732163c2..44293ee2 100644 --- a/README.rst +++ b/README.rst @@ -611,6 +611,10 @@ Response:: } } +If a user is authenticated and, he is not a site operator, returns all own bookings. + +With an experimental envionment `SEE_OWN_ANONYMOUS_BOOKINGS` set to `True`, the endpoint will return +also the bookings created by anonymous users with the same fiscalcode of the authenticated user. @booking-notify --------------- diff --git a/src/redturtle/prenotazioni/restapi/services/bookings/search.py b/src/redturtle/prenotazioni/restapi/services/bookings/search.py index 78eefc4b..084712b6 100644 --- a/src/redturtle/prenotazioni/restapi/services/bookings/search.py +++ b/src/redturtle/prenotazioni/restapi/services/bookings/search.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import logging +import os from DateTime import DateTime from plone import api @@ -12,6 +13,11 @@ from redturtle.prenotazioni.interfaces import ISerializeToPrenotazioneSearchableItem logger = logging.getLogger(__name__) +SEE_OWN_ANONYMOUS_BOOKINGS = os.environ.get("SEE_OWN_ANONYMOUS_BOOKINGS") in [ + "True", + "true", + "1", +] @implementer(IPublishTraverse) @@ -84,26 +90,44 @@ def query(self): return query def reply(self): + # XXX: `fullobjects` the correct behavior should be to use different serializers fullobjects = self.request.form.get("fullobjects", False) == "1" response = {"id": self.context.absolute_url() + "/@bookings"} query = self.query() - # XXX: `fullobjects` the correct behavior should be to use different serializers items = [] - for i in api.portal.get_tool("portal_catalog")(**query): + if query.get("fiscalcode") and SEE_OWN_ANONYMOUS_BOOKINGS: + # brains = api.content.find(**query, unrestricted=True) + brains = api.portal.get_tool("portal_catalog").unrestrictedSearchResults( + **query + ) + unrestricted = True + else: + # brains = api.content.find(**query) + brains = api.portal.get_tool("portal_catalog")(**query) + unrestricted = False + for brain in brains: # TEMP: errors with broken catalog entries try: items.append( getMultiAdapter( - (i.getObject(), self.request), + (self.wrappedGetObject(brain, unrestricted), self.request), ISerializeToPrenotazioneSearchableItem, )(fullobjects=fullobjects) ) except: # noqa: E722 - logger.exception("error with %s", i.getPath()) + logger.exception("error with %s", brain.getPath()) response["items"] = items response["items_total"] = len(response["items"]) return response + @staticmethod + def wrappedGetObject(brain, unrestricted=False): + if unrestricted: + # with api.env.adopt_user(brain.Creator): + with api.env.adopt_roles("Manager"): + return brain.getObject() + return brain.getObject() + class BookingsSearchFolder(BookingsSearch): def query(self): diff --git a/src/redturtle/prenotazioni/tests/test_prenotazioni_search.py b/src/redturtle/prenotazioni/tests/test_prenotazioni_search.py index 930f1f2e..2838c9a3 100644 --- a/src/redturtle/prenotazioni/tests/test_prenotazioni_search.py +++ b/src/redturtle/prenotazioni/tests/test_prenotazioni_search.py @@ -503,3 +503,202 @@ def test_download_xlsx_sort(self): [r[0].value for r in data["Sheet 1"].rows][1:], sorted([r[0].value for r in data["Sheet 1"].rows][1:], reverse=True), ) + + +class TestPrenotazioniUserSearch(unittest.TestCase): + """Test the restapi search endpoint (/@bookings)""" + + layer = REDTURTLE_PRENOTAZIONI_API_FUNCTIONAL_TESTING + + def setUp(self): + self.app = self.layer["app"] + self.portal = self.layer["portal"] + self.portal_url = self.portal.absolute_url() + self.testing_fiscal_code = "TESTINGFISCALCODE" + self.testing_booking_date = parser.parse("2023-04-28 16:00:00") + self.booking_expiration_date = parser.parse("2023-04-28 16:00:00") + timedelta( + days=100 + ) + + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + + api.user.create( + email="john@example.org", + username="TESTINGFISCALCODE", + password="testingpassowrd", + properties={"fiscalcode": "TESTINGFISCALCODE"}, + ) + + self.api_session = RelativeSession(self.portal_url) + self.api_session.headers.update({"Accept": "application/json"}) + self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) + + self.anon_session = RelativeSession(self.portal_url) + self.anon_session.headers.update({"Accept": "application/json"}) + self.anon_session.auth = None + + self.user_session = RelativeSession(self.portal_url) + self.user_session.headers.update({"Accept": "application/json"}) + self.user_session.auth = ("TESTINGFISCALCODE", "testingpassowrd") + + self.browser = Browser(self.layer["app"]) + + self.folder_prenotazioni = api.content.create( + container=self.portal, + type="PrenotazioniFolder", + title="Prenota foo", + description="", + daData=date.today(), + gates=["Gate A"], + ) + api.content.transition(obj=self.folder_prenotazioni, transition="publish") + + obj = api.content.create( + type="PrenotazioneType", + title="Type A", + duration=30, + container=self.folder_prenotazioni, + gates=["all"], + requirements=RichTextValue( + "You need to bring your own food", "text/plain", "text/html" + ), + ) + api.content.transition(obj=obj, transition="publish") + + week_table = deepcopy(self.folder_prenotazioni.week_table) + week_table[0]["morning_start"] = "0700" + week_table[0]["morning_end"] = "1000" + self.folder_prenotazioni.week_table = week_table + + self.folder_prenotazioni2 = api.content.create( + container=self.portal, + type="PrenotazioniFolder", + title="Prenota bar", + description="", + daData=date.today(), + gates=["Gate A"], + ) + api.content.transition(obj=self.folder_prenotazioni2, transition="publish") + + obj = api.content.create( + type="PrenotazioneType", + title="Type A", + duration=10, + container=self.folder_prenotazioni2, + gates=["all"], + ) + api.content.transition(obj=obj, transition="publish") + + obj = api.content.create( + type="PrenotazioneType", + title="Type B", + duration=20, + container=self.folder_prenotazioni2, + gates=["all"], + ) + api.content.transition(obj=obj, transition="publish") + + week_table = deepcopy(self.folder_prenotazioni2.week_table) + week_table[0]["morning_start"] = "0700" + week_table[0]["morning_end"] = "1000" + self.folder_prenotazioni2.week_table = week_table + + transaction.commit() + + def tearDown(self): + self.api_session.close() + self.anon_session.close() + self.user_session.close() + + def test_search_own_bookings(self): + + # booking_date = "{}T09:00:00+00:00".format( + # (date.today() + timedelta(1)).strftime("%Y-%m-%d") + # ) + res = self.anon_session.get( + f"{self.folder_prenotazioni.absolute_url()}/@available-slots" + ) + booking_date = res.json()["items"][0] + # anonymous user 1 + res = self.add_booking( + self.anon_session, + booking_date=booking_date, + booking_type="Type A", + fields=[ + {"name": "title", "value": "Mario Rossi"}, + {"name": "email", "value": "mario.rossi@example"}, + {"name": "fiscalcode", "value": "ABCDEF12G34H567I"}, + ], + ) + self.assertEqual(res.status_code, 200) + # anonymous user 2 (not the same fiscalcode, pretend to be john) + res = self.anon_session.get( + f"{self.folder_prenotazioni.absolute_url()}/@available-slots" + ) + booking_date = res.json()["items"][0] + res = self.add_booking( + self.anon_session, + booking_date=booking_date, + booking_type="Type A", + fields=[ + {"name": "title", "value": "John Rossi"}, + {"name": "email", "value": "john@example"}, + {"name": "fiscalcode", "value": "TESTINGFISCALCODE"}, + ], + ) + self.assertEqual(res.status_code, 200) + # john + res = self.anon_session.get( + f"{self.folder_prenotazioni.absolute_url()}/@available-slots" + ) + booking_date = res.json()["items"][0] + res = self.add_booking( + self.user_session, + booking_date=booking_date, + booking_type="Type A", + fields=[ + {"name": "title", "value": "John Rossi"}, + {"name": "email", "value": "john@example"}, + # {"name": "fiscalcode", "value": "TESTINGFISCALCODE"}, + ], + ) + self.assertEqual(res.status_code, 200) + + # anonimo non può vedere le prenotazioni + res = self.anon_session.get( + f"{self.portal.absolute_url()}/@bookings/TESTINGFISCALCODE" + ) + self.assertEqual(res.status_code, 401) + + # il manager vede tutte le prenotazioni (3) + res = self.api_session.get(f"{self.portal.absolute_url()}/@bookings") + self.assertEqual(res.status_code, 200) + self.assertEqual(res.json()["items_total"], 3) + + # la prenotazione fatta da anonimo con il codicefiscale di john non è visibile + res = self.user_session.get(f"{self.portal.absolute_url()}/@bookings") + self.assertEqual(res.status_code, 200) + self.assertEqual(res.json()["items_total"], 1) + + # XXX: monkeypatching per vedere le prenotazioni anonime + from redturtle.prenotazioni.restapi.services.bookings import search + + search.SEE_OWN_ANONYMOUS_BOOKINGS = True + res = self.user_session.get(f"{self.portal.absolute_url()}/@bookings") + self.assertEqual(res.status_code, 200) + self.assertEqual(res.json()["items_total"], 2) + search.SEE_OWN_ANONYMOUS_BOOKINGS = False + + # TODO: verificare che siano le prenotazioni giuste + + # utility methods + + def add_booking(self, api_session, booking_date, booking_type, fields): + return api_session.post( + f"{self.folder_prenotazioni.absolute_url()}/@booking", + json={ + "booking_date": booking_date, + "booking_type": booking_type, + "fields": fields, + }, + )