Example #1
0
    def test__func__send_email_notification__ok__nominal_case(self, app_config, mailhog):
        smtp_config = SmtpConfiguration(
            app_config.EMAIL__NOTIFICATION__SMTP__SERVER,
            app_config.EMAIL__NOTIFICATION__SMTP__PORT,
            app_config.EMAIL__NOTIFICATION__SMTP__USER,
            app_config.EMAIL__NOTIFICATION__SMTP__PASSWORD,
            app_config.EMAIL__NOTIFICATION__SMTP__USE_IMPLICIT_SSL,
        )
        sender = EmailSender(app_config, smtp_config, True)
        html = """\
        <html>
          <head></head>
          <body>
            <p>test__func__send_email__ok__nominal_case</p>
          </body>
        </html>
        """.replace(
            " ", ""
        ).replace(
            "\n", ""
        )
        msg = EmailNotificationMessage(
            subject="test__func__send_email__ok__nominal_case",
            from_header=EmailAddress("", "test_send_mail@localhost"),
            to_header=EmailAddress("", "receiver_test_send_mail@localhost"),
            reply_to=EmailAddress("", "replyto@localhost"),
            references=EmailAddress("", "references@localhost"),
            lang="en",
            body_html=html,
        )
        sender.send_mail(msg)
        sender.disconnect()

        # check mail received
        response = mailhog.get_mailhog_mails()
        headers = response[0]["Content"]["Headers"]
        assert headers["From"][0] == "test_send_mail@localhost"
        assert headers["To"][0] == "receiver_test_send_mail@localhost"
        assert headers["Subject"][0] == "test__func__send_email__ok__nominal_case"
        assert headers["Content-Language"][0] == "en"
        assert headers["Message-ID"]
        assert headers["Date"]
        assert headers["Reply-to"][0] == "replyto@localhost"
        assert headers["References"][0] == "references@localhost"
        assert headers["X-Auto-Response-Suppress"][0] == "All"
        assert headers["Auto-Submitted"][0] == "auto-generated"
        assert response[0]["MIME"]["Parts"][0]["Body"]
        assert response[0]["MIME"]["Parts"][1]["Body"]
Example #2
0
    def _get_sender(self, user: User = None) -> EmailAddress:
        """
        Return sender EmailAdress object, which permit to get rfc compliant address:
        "Bob Dylan (via Tracim) <*****@*****.**>"
        :param user: user to extract display name
        :return: sender string
        """

        email_template = self.config.EMAIL__NOTIFICATION__FROM__EMAIL
        mail_sender_name = self.config.EMAIL__NOTIFICATION__FROM__DEFAULT_LABEL
        if user:
            mail_sender_name = "{name} via Tracim".format(
                name=user.display_name)
            email_address = email_template.replace("{user_id}",
                                                   str(user.user_id))
            # INFO - D.A. - 2017-08-04
            # We use email_template.replace() instead of .format() because this
            # method is more robust to errors in config file.
            #
            # For example, if the email is info+{userid}@tracim.fr
            # email.format(user_id='bob') will raise an exception
            # email.replace('{user_id}', 'bob') will just ignore {userid}
        else:
            email_address = email_template.replace("{user_id}", "0")

        return EmailAddress(label=mail_sender_name, email=email_address)
Example #3
0
 def test_unit__email_address_address__ok__empty_label(self):
     john_address = EmailAddress(label="", email="*****@*****.**")
     assert john_address.domain == "domainame.ndl"
     assert john_address.label == ""
     assert john_address.email == "*****@*****.**"
     assert john_address.force_angle_bracket is False
     assert john_address.address == "*****@*****.**"
Example #4
0
 def test_unit__email_address_address__ok__nominal_case(self):
     john_address = EmailAddress(label="John Doe",
                                 email="*****@*****.**")
     assert john_address.domain == "domainame.ndl"
     assert john_address.label == "John Doe"
     assert john_address.email == "*****@*****.**"
     assert john_address.force_angle_bracket is False
     assert john_address.address == "John Doe <*****@*****.**>"
Example #5
0
 def test_unit__email_address_address__ok__force_angle_brackets(self):
     john_address = EmailAddress(label="John Doe",
                                 email="*****@*****.**",
                                 force_angle_bracket=True)
     assert john_address.domain == "domainame.ndl"
     assert john_address.label == "John Doe"
     assert john_address.email == "*****@*****.**"
     assert john_address.force_angle_bracket is True
     assert john_address.address == "John Doe <*****@*****.**>"
Example #6
0
    def notify_reset_password(self, user: User,
                              reset_password_token: str) -> None:
        """
        Reset password link for user
        :param user: user to notify
        :param reset_password_token: token for resetting password
        """
        logger.debug(self, "user: {}".format(user.user_id))
        logger.info(self,
                    "Generating reset password email to {}".format(user.email))
        translator = Translator(self.config, default_lang=user.lang)
        email_sender = EmailSender(self.config, self._smtp_config,
                                   self.config.EMAIL__NOTIFICATION__ACTIVATED)
        translated_subject = translator.get_translation(
            self.config.EMAIL__NOTIFICATION__RESET_PASSWORD_REQUEST__SUBJECT)
        subject = translated_subject.replace(EST.WEBSITE_TITLE,
                                             str(self.config.WEBSITE__TITLE))
        from_header = self._get_sender()
        to_header = EmailAddress(user.get_display_name(), user.email)

        html_template_file_path = (
            self.config.
            EMAIL__NOTIFICATION__RESET_PASSWORD_REQUEST__TEMPLATE__HTML)
        # TODO - G.M - 2018-08-17 - Generate token
        context = {
            "user":
            user,
            "logo_url":
            get_email_logo_frontend_url(self.config),
            "reset_password_url":
            get_reset_password_frontend_url(self.config,
                                            token=reset_password_token,
                                            email=user.email),
        }

        body_html = self._render_template(
            mako_template_filepath=html_template_file_path,
            context=context,
            translator=translator)
        message = EmailNotificationMessage(
            subject=subject,
            from_header=from_header,
            to_header=to_header,
            body_html=body_html,
            lang=translator.default_lang,
        )
        send_email_through(config=self.config,
                           sendmail_callable=email_sender.send_mail,
                           message=message)
Example #7
0
    def notify_created_account(
            self,
            user: User,
            password: str,
            origin_user: typing.Optional[User] = None) -> None:
        """
        Send created account email to given user.

        :param password: choosed password
        :param user: user to notify
        """
        logger.info(self,
                    "Generating created account mail to {}".format(user.email))

        email_sender = EmailSender(self.config, self._smtp_config,
                                   self.config.EMAIL__NOTIFICATION__ACTIVATED)
        translator = Translator(self.config, default_lang=user.lang)
        translated_subject = translator.get_translation(
            self.config.EMAIL__NOTIFICATION__CREATED_ACCOUNT__SUBJECT)
        subject = translated_subject.replace(EST.WEBSITE_TITLE,
                                             str(self.config.WEBSITE__TITLE))
        from_header = self._get_sender(origin_user)
        to_header = EmailAddress(user.get_display_name(), user.email)
        html_template_file_path = self.config.EMAIL__NOTIFICATION__CREATED_ACCOUNT__TEMPLATE__HTML

        context = {
            "origin_user": origin_user,
            "user": user,
            "password": password,
            "logo_url": get_email_logo_frontend_url(self.config),
            "login_url": get_login_frontend_url(self.config),
        }
        translator = Translator(self.config, default_lang=user.lang)
        body_html = self._render_template(
            mako_template_filepath=html_template_file_path,
            context=context,
            translator=translator)
        message = EmailNotificationMessage(
            subject=subject,
            from_header=from_header,
            to_header=to_header,
            body_html=body_html,
            lang=translator.default_lang,
        )

        send_email_through(config=self.config,
                           sendmail_callable=email_sender.send_mail,
                           message=message)
Example #8
0
    def _notify_emitter(
        self,
        emitter: User,
        shared_content: ContentInContext,
        content_share_receivers: typing.List[ContentShareInContext],
        share_password: str,
        translator: Translator,
    ) -> Message:
        logger.info(
            self,
            'preparing email to user "{}" about share on content "{}" info created'.format(
                emitter.user_id, shared_content.content_id
            ),
        )
        translated_subject = translator.get_translation(
            self.config.EMAIL__NOTIFICATION__SHARE_CONTENT_TO_EMITTER__SUBJECT
        )
        subject = translated_subject.format(
            website_title=self.config.WEBSITE__TITLE,
            content_filename=shared_content.filename,
            nb_receivers=len(content_share_receivers),
        )
        from_header = self._get_sender()
        to_header = EmailAddress(emitter.display_name, emitter.email)
        html_template_file_path = (
            self.config.EMAIL__NOTIFICATION__SHARE_CONTENT_TO_EMITTER__TEMPLATE__HTML
        )
        context = {
            "emitter": emitter,
            "shared_content": shared_content,
            "content_share_receivers": content_share_receivers,
            "share_password": share_password,
        }
        body_html = self._render_template(
            mako_template_filepath=html_template_file_path, context=context, translator=translator
        )
        message = EmailNotificationMessage(
            subject=subject,
            from_header=from_header,
            to_header=to_header,
            body_html=body_html,
            lang=translator.default_lang,
        )

        return message
Example #9
0
    def _notify_emitter(
        self,
        emitter: UserInContext,
        workspace_in_context: WorkspaceInContext,
        upload_permission_receivers: typing.List[UploadPermissionInContext],
        upload_permission_password: str,
        translator: Translator,
    ) -> Message:
        logger.info(
            self,
            'preparing email to user "{}" about upload_permission on workspace "{}" info created'
            .format(emitter.user_id, workspace_in_context.workspace_id),
        )
        translated_subject = translator.get_translation(
            self.config.
            EMAIL__NOTIFICATION__UPLOAD_PERMISSION_TO_EMITTER__SUBJECT)
        subject = translated_subject.format(
            website_title=self.config.WEBSITE__TITLE,
            nb_receivers=len(upload_permission_receivers),
            workspace_name=workspace_in_context.label,
        )
        from_header = self._get_sender()
        to_header = EmailAddress(emitter.display_name, emitter.email)
        html_template_file_path = (
            self.config.
            EMAIL__NOTIFICATION__UPLOAD_PERMISSION_TO_EMITTER__TEMPLATE__HTML)
        context = {
            "emitter": emitter,
            "workspace": workspace_in_context,
            "upload_permission_receivers": upload_permission_receivers,
            "upload_permission_password": upload_permission_password,
        }
        body_html = self._render_template(
            mako_template_filepath=html_template_file_path,
            context=context,
            translator=translator)

        message = EmailNotificationMessage(
            subject=subject,
            from_header=from_header,
            to_header=to_header,
            body_html=body_html,
            lang=translator.default_lang,
        )
        return message
Example #10
0
 def _notify_new_upload(
     self,
     workspace_in_context: WorkspaceInContext,
     receiver: UserInContext,
     uploader: EmailUser,
     uploader_message: typing.Optional[str],
     uploaded_contents: typing.List[ContentInContext],
     translator: Translator,
 ) -> Message:
     logger.info(
         self,
         'preparing email to user "{}" about new upload on workspace "{}" info created'
         .format(receiver.user_id, workspace_in_context.workspace_id),
     )
     translated_subject = translator.get_translation(
         self.config.EMAIL__NOTIFICATION__NEW_UPLOAD_EVENT__SUBJECT)
     subject = translated_subject.format(
         website_title=self.config.WEBSITE__TITLE,
         uploader_username=uploader.username,
         nb_uploaded_contents=len(uploaded_contents),
         workspace_name=workspace_in_context.label,
     )
     from_header = self._get_sender()
     to_header = EmailAddress(receiver.display_name, receiver.email)
     html_template_file_path = self.config.EMAIL__NOTIFICATION__NEW_UPLOAD_EVENT__TEMPLATE__HTML
     context = {
         "receiver": receiver,
         "workspace": workspace_in_context,
         "uploader": uploader,
         "uploaded_contents": uploaded_contents,
         "uploader_message": uploader_message,
     }
     body_html = self._render_template(
         mako_template_filepath=html_template_file_path,
         context=context,
         translator=translator)
     message = EmailNotificationMessage(
         subject=subject,
         from_header=from_header,
         to_header=to_header,
         body_html=body_html,
         lang=translator.default_lang,
     )
     return message
Example #11
0
    def notify_content_update(self, event_actor_id: int,
                              event_content_id: int) -> None:
        """
        Look for all users to be notified about the new content and send them an
        individual email
        :param event_actor_id: id of the user that has triggered the event
        :param event_content_id: related content_id
        :return:
        """
        # FIXME - D.A. - 2014-11-05
        # Dirty import. It's here in order to avoid circular import
        from tracim_backend.lib.core.content import ContentApi
        from tracim_backend.lib.core.user import UserApi

        user = UserApi(None, config=self.config,
                       session=self.session).get_one(event_actor_id)
        logger.debug(self, "Content: {}".format(event_content_id))
        content_api = ContentApi(current_user=user,
                                 session=self.session,
                                 config=self.config)
        content = ContentApi(
            session=self.session,
            current_user=user,
            # TODO - G.M - 2019-04-24 - use a system user instead of the user that has triggered the event
            config=self.config,
            show_archived=True,
            show_deleted=True,
        ).get_one(event_content_id, content_type_list.Any_SLUG)
        workspace_api = WorkspaceApi(session=self.session,
                                     current_user=user,
                                     config=self.config)
        workpace_in_context = workspace_api.get_workspace_with_context(
            workspace_api.get_one(content.workspace_id))
        main_content = content.parent if content.type == content_type_list.Comment.slug else content
        notifiable_roles = WorkspaceApi(
            current_user=user, session=self.session,
            config=self.config).get_notifiable_roles(content.workspace)

        if len(notifiable_roles) <= 0:
            logger.info(
                self,
                "Skipping notification as nobody subscribed to in workspace {}"
                .format(content.workspace.label),
            )
            return

        logger.info(
            self,
            "Generating content {} notification email for {} user(s)".format(
                content.content_id, len(notifiable_roles)),
        )
        # INFO - D.A. - 2014-11-06
        # The following email sender will send emails in the async task queue
        # This allow to build all mails through current thread but really send them (including SMTP connection)
        # In the other thread.
        #
        # This way, the webserver will return sooner (actually before notification emails are sent
        email_sender = EmailSender(self.config, self._smtp_config,
                                   self.config.EMAIL__NOTIFICATION__ACTIVATED)
        for role in notifiable_roles:
            logger.info(
                self,
                "Generating content {} notification email to {}".format(
                    content.content_id, role.user.email),
            )
            translator = Translator(app_config=self.config,
                                    default_lang=role.user.lang)
            _ = translator.get_translation
            # INFO - G.M - 2017-11-15 - set content_id in header to permit reply
            # references can have multiple values, but only one in this case.
            replyto_addr = self.config.EMAIL__NOTIFICATION__REPLY_TO__EMAIL.replace(
                "{content_id}", str(main_content.content_id))

            reference_addr = self.config.EMAIL__NOTIFICATION__REFERENCES__EMAIL.replace(
                "{content_id}", str(main_content.content_id))
            #
            #  INFO - D.A. - 2014-11-06
            # We do not use .format() here because the subject defined in the .ini file
            # may not include all required labels. In order to avoid partial format() (which result in an exception)
            # we do use replace and force the use of .__str__() in order to process LazyString objects
            #
            content_status = translator.get_translation(
                main_content.get_status().label)
            translated_subject = translator.get_translation(
                self.config.EMAIL__NOTIFICATION__CONTENT_UPDATE__SUBJECT)
            subject = translated_subject.replace(
                EST.WEBSITE_TITLE, self.config.WEBSITE__TITLE.__str__())
            subject = subject.replace(EST.WORKSPACE_LABEL,
                                      main_content.workspace.label.__str__())
            subject = subject.replace(EST.CONTENT_LABEL,
                                      main_content.label.__str__())
            subject = subject.replace(EST.CONTENT_STATUS_LABEL, content_status)
            reply_to_label = _(
                "{username} & all members of {workspace}").format(
                    username=user.display_name,
                    workspace=main_content.workspace.label)

            content_in_context = content_api.get_content_in_context(content)
            parent_in_context = None
            if content.parent_id:
                parent_in_context = content_api.get_content_in_context(
                    content.parent)

            body_html = self._build_email_body_for_content(
                self.config.
                EMAIL__NOTIFICATION__CONTENT_UPDATE__TEMPLATE__HTML,
                role,
                content_in_context,
                parent_in_context,
                workpace_in_context,
                user,
                translator,
            )

            message = EmailNotificationMessage(
                subject=subject,
                from_header=self._get_sender(user),
                to_header=EmailAddress(role.user.display_name,
                                       role.user.email),
                reply_to=EmailAddress(reply_to_label, replyto_addr),
                # INFO - G.M - 2017-11-15
                # References can theorically have label, but in pratice, references
                # contains only message_id from parents post in thread.
                # To link this email to a content we create a virtual parent
                # in reference who contain the content_id.
                # INFO - G.M - 2020-04-03 - Enforce angle bracket in references header
                # we need that to ensure best software compatibility
                # compat from parsing software
                references=EmailAddress("",
                                        reference_addr,
                                        force_angle_bracket=True),
                body_html=body_html,
                lang=translator.default_lang,
            )

            self.log_email_notification(
                msg="an email was created to {}".format(message["To"]),
                action="{:8s}".format("CREATED"),
                email_recipient=message["To"],
                email_subject=message["Subject"],
                config=self.config,
            )

            send_email_through(self.config, email_sender.send_mail, message)