import base64 from lxml import html from unittest.mock import patch from odoo import exceptions from odoo.tools import mute_logger from odoo.tests.common import users from odoo.tests import Form, HttpCase, tagged, warmup from odoo.addons.mail.tests.common import MailCase from odoo.addons.marketing_card.controllers.marketing_card import SOCIAL_NETWORK_USER_AGENTS from .common import MarketingCardCommon, mock_image_render, VALID_JPEG def _extract_values_from_document(rendered_document): return { 'body': rendered_document.find('.//div[@id="body"]'), 'header': rendered_document.find('.//span[@id="header"]'), 'subheader': rendered_document.find('.//span[@id="subheader"]'), 'section': rendered_document.find('.//span[@id="section"]'), 'sub_section1': rendered_document.find('.//span[@id="sub_section1"]'), 'sub_section2': rendered_document.find('.//span[@id="sub_section2"]'), 'button': rendered_document.find('.//span[@id="button"]'), 'image1': rendered_document.find('.//img[@id="image1"]'), 'image2': rendered_document.find('.//img[@id="image2"]'), } class TestMarketingCardMail(MailCase, MarketingCardCommon): @users('marketing_card_user') @warmup @mute_logger('odoo.addons.mail.models.mail_mail') def test_campaign_send_mailing(self): campaign = self.campaign.with_user(self.env.user) self.env.user.sudo().groups_id += self.env.ref('mass_mailing.group_mass_mailing_user') partners = self.env['res.partner'].sudo().create([{'name': f'Part{n}', 'email': f'partn{n}@test.lan'} for n in range(7)]) mailing_context = campaign.action_share().get('context') | { 'default_email_from': 'test@test.lan', 'default_mailing_domain': [('id', 'in', partners.ids[:5])], 'default_reply_to': 'test@test.lan', } mailing = Form(self.env['mailing.mailing'].with_context(mailing_context)).save() mailing.body_html = mailing.body_arch # normally the js html_field would fill this in # sending mailing before generating cards sends to no-one self.assertTrue(mailing.card_requires_sync_count) with self.assertRaises(exceptions.UserError, msg="There are no recipients selected."): mailing._action_send_mail() with self.assertRaises(exceptions.UserError, msg="You should update all the cards before scheduling a mailing."): mailing.action_launch() # once cards are updated they can be sent with self.mock_image_renderer(): mailing.action_update_cards() self.assertEqual(len(self._wkhtmltoimage_bodies), 5) self.assertFalse(mailing.card_requires_sync_count) mailing.action_launch() mailing.action_cancel() # modifying the domain such that there are missing cards prevents sending again mailing.mailing_domain = [('id', 'in', partners.ids[1:6])] mailing._compute_card_requires_sync_count() self.assertTrue(mailing.card_requires_sync_count) with self.assertRaises(exceptions.UserError, msg="You should update all the cards before scheduling a mailing."): mailing.action_launch() # updating when the campaign was not modified only updates cards that need to be with self.mock_image_renderer(): mailing.action_update_cards() self.assertEqual(len(self._wkhtmltoimage_bodies), 1) self.assertFalse(mailing.card_requires_sync_count) # modifying the campaign should lead to all cards relevant being re-rendered campaign.content_header = "New Header" mailing._compute_card_requires_sync_count() self.assertTrue(mailing.card_requires_sync_count) with self.mock_image_renderer(): mailing.action_update_cards() self.assertEqual(len(self._wkhtmltoimage_bodies), 5) with self.mock_mail_gateway(), self.assertQueryCount(243): mailing._action_send_mail() cards = self.env['card.card'].search([('campaign_id', '=', campaign.id)]) self.assertEqual(len(cards), 6) self.assertEqual(len(cards.filtered(lambda card: not card.requires_sync)), 5) self.assertEqual(len(self._mails), 5) IrHttp = self.env['ir.http'] for sent_mail in self._mails: record_id = int(sent_mail['object_id'].split('-')[0]) card = cards.filtered(lambda card: card.res_id == record_id) self.assertEqual(len(card), 1) preview_url = f"{campaign.get_base_url()}/cards/{IrHttp._slug(card)}/preview" image_url = f"{campaign.get_base_url()}/cards/{IrHttp._slug(card)}/card.jpg" self.assertIn(f' """ campaign.body_html = arbitrary_qweb # Normally, this should raise an AccessError, as `body_html` is a related to `card_template_id.body` # and the user does not have the write rights on `card.template`. # However, the ORM doesn't forward the new value to the related, # and the new value is put in the cache without error. # In the real world, using the web client or the XMLRPC API, # a user would have to do this operation in two requests: # First set the body_html on the campaign, # Then trigger the render (e.g. with `action_update_cards`), # and the cache would have changed between the two operations, hence setting an arbitrary value on body_html # on a campaign wouldn't work. # Just ensure that the value is well not written in db, nor on the current campaign, nor on the related. # Force a cache invalidation to force a re-fetch from database campaign.invalidate_recordset(fnames=['body_html']) self.assertTrue(arbitrary_qweb not in campaign.body_html) self.assertTrue(arbitrary_qweb not in campaign.card_template_id.body) with self.assertRaisesRegex(exceptions.AccessError, 'You are not allowed to modify'): campaign.card_template_id.body = arbitrary_qweb def test_mail_render_security_render_field_write_access(self): """Check the rendered fields on card.campaign are not both rendered and writeable. See _check_access_right_dynamic_template override. """ CardCampaign = self.env['card.campaign'].with_user(self.marketing_card_manager) # Asserts all render fields are related to card.template, not stored on the campaign itself, and readonly # If one of the render fields doesn't fulfil this assumption, the `_unrestricted_rendering = True` must be # reconsidered for security reasons. self.assertTrue( all( field.related_field.model_name == 'card.template' and not field.store and field.readonly for field in CardCampaign._fields.values() if hasattr(field, 'render_engine') ) ) # Asserts the manager doesn't have write access to card.template self.assertFalse(CardCampaign.card_template_id.has_access('write'))