diff --git a/addons/mass_mailing/controllers/main.py b/addons/mass_mailing/controllers/main.py index b5463a9d623..aa1b03c9d0d 100644 --- a/addons/mass_mailing/controllers/main.py +++ b/addons/mass_mailing/controllers/main.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import base64 +import werkzeug from datetime import timedelta from markupsafe import Markup, escape @@ -24,12 +25,19 @@ class MassMailController(http.Controller): available for them; * hash_token is not required for generic page for logged user, aka if no mailing_id is given; + * hash_token is not required for mailing specific page if the user + is a mailing user; + * hash_token is not required for generic page for logged user, aka + if no mailing_id is given and if mailing_id is not required; * hash_token always requires the triplet mailing_id, email and document_id, as it indicates it comes from a mailing email and is used when comparing hashes; """ - if not hash_token and (mailing_id or request.env.user._is_public()): - raise BadRequest() + if not hash_token: + if request.env.user._is_public(): + raise BadRequest() + if mailing_id and not request.env.user.has_group('mass_mailing.group_mass_mailing_user'): + raise BadRequest() if hash_token and (not mailing_id or not email or not document_id): raise BadRequest() if mailing_id: @@ -130,13 +138,17 @@ class MassMailController(http.Controller): ) def _mailing_unsubscribe_from_document(self, mailing, document_id, email, hash_token): - message = Markup( - _( + if document_id: + message = Markup(_( 'Blocklist request from unsubscribe link of mailing %(mailing_link)s (document %(record_link)s)', **self._format_bl_request(mailing, document_id) - ) - ) - _blocklist_rec = request.env['mail.blacklist'].sudo()._add(email, message=message) + )) + else: + message = Markup(_( + 'Blocklist request from unsubscribe link of mailing %(mailing_link)s (direct link usage)', + **self._format_bl_request(mailing, document_id) + )) + _blocklist_rec = request.env['mail.blacklist'].sudo()._add(email, message=Markup('
%s
') % message) return request.render( 'mass_mailing.page_mailing_unsubscribe', @@ -308,7 +320,7 @@ class MassMailController(http.Controller): @http.route(['/unsubscribe_from_list'], type='http', website=True, multilang=False, auth='public', sitemap=False) def mailing_unsubscribe_placeholder_link(self, **post): """Dummy route so placeholder is not prefixed by language, MUST have multilang=False""" - raise BadRequest() + return request.redirect('/mailing/my', code=301, local=True) # ------------------------------------------------------------ # TRACKING @@ -372,12 +384,6 @@ class MassMailController(http.Controller): ) except NotFound as e: raise Unauthorized() from e - except (BadRequest, Unauthorized): - if not request.env.user.has_group('mass_mailing.group_mass_mailing_user'): - raise - mailing_sudo = request.env['mailing.mailing'].sudo().browse(mailing_id).exists() - if not mailing_sudo: - raise # do not force lang, will simply use user context document_id = int(document_id) if document_id and document_id.isdigit() else 0 @@ -388,10 +394,19 @@ class MassMailController(http.Controller): options={'post_process': False} )[document_id] # Update generic URLs (without parameters) to final ones - html_markupsafe = html_markupsafe.replace( - '/unsubscribe_from_list', - mailing_sudo._get_unsubscribe_url(email, document_id) - ) + if document_id: + html_markupsafe = html_markupsafe.replace( + '/unsubscribe_from_list', + mailing_sudo._get_unsubscribe_url(email, document_id) + ) + else: # when manually trying a /view on a mailing, not through email link + html_markupsafe = html_markupsafe.replace( + '/unsubscribe_from_list', + werkzeug.urls.url_join( + mailing_sudo.get_base_url(), + f'/mailing/{mailing_sudo.id}/unsubscribe', + ) + ) return request.render( 'mass_mailing.mailing_view', @@ -462,5 +477,7 @@ class MassMailController(http.Controller): mailing_model_name = request.env['ir.model']._get(mailing.mailing_model_real).display_name return { 'mailing_link': Markup(f'{escape(mailing.subject)}'), - 'record_link': Markup(f'{escape(mailing_model_name)}'), + 'record_link': Markup( + f'{escape(mailing_model_name)}' + ) if document_id else '', } diff --git a/addons/mass_mailing/tests/test_mailing_controllers.py b/addons/mass_mailing/tests/test_mailing_controllers.py index ba6e0fa1b1c..dc20221e358 100644 --- a/addons/mass_mailing/tests/test_mailing_controllers.py +++ b/addons/mass_mailing/tests/test_mailing_controllers.py @@ -255,6 +255,47 @@ class TestMailingControllers(TestMailingControllersCommon): ) self.assertEqual(msg_create.body, Markup('Mail Blacklist created
')) + def test_mailing_unsubscribe_from_document_tour_mailing_user(self): + """ Test portal unsubscribe on mailings performed on documents (not + mailing lists or contacts) using a generic '/unsubscribe' link allowing + mailing users to see and edit unsubcribe page. + + Tour effects + * unsubscribe from mailing based on a document = blocklist; + * add feedback (block list): Other reason, with 'My feedback' feedback; + * remove email from exclusion list; + * re-add email to exclusion list; + """ + # update user to link it with existing mailing contacts and allow the tour + # to run; test without and with mailing group + self.user_marketing.write({ + 'email': tools.formataddr(("Déboulonneur", "fleurus@example.com")), + 'groups_id': [(3, self.env.ref('mass_mailing.group_mass_mailing_user').id)], + }) + test_mailing = self.test_mailing_on_documents.with_env(self.env) + self.authenticate('user_marketing', 'user_marketing') + + # no group -> no direct access to /unsubscribe + res = self.url_open( + werkzeug.urls.url_join( + test_mailing.get_base_url(), + f'mailing/{test_mailing.id}/unsubscribe', + ) + ) + self.assertEqual(res.status_code, 400) + + # group -> direct access to /unsubscribe should wokr + self.user_marketing.write({ + 'groups_id': [(4, self.env.ref('mass_mailing.group_mass_mailing_user').id)], + }) + # launch unsubscription tour + with freeze_time(self._reference_now): + self.start_tour( + f"/mailing/{test_mailing.id}/unsubscribe", + "mailing_portal_unsubscribe_from_document_with_lists", + login=self.user_marketing.login, + ) + def test_mailing_unsubscribe_from_list_tour(self): """ Test portal unsubscribe on mailings performed on mailing lists. Their effect is to opt-out from the mailing list. diff --git a/addons/mass_mailing/views/mailing_templates_portal_unsubscribe.xml b/addons/mass_mailing/views/mailing_templates_portal_unsubscribe.xml index f472710543f..3ff601d12bf 100644 --- a/addons/mass_mailing/views/mailing_templates_portal_unsubscribe.xml +++ b/addons/mass_mailing/views/mailing_templates_portal_unsubscribe.xml @@ -159,6 +159,7 @@ +