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"]
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)
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 == "*****@*****.**"
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 <*****@*****.**>"
def test_unit__email_address_address__ok__from_rfc_email_address__with_label_quotation( self): john_address = EmailAddress.from_rfc_email_address( '"John Doe" <*****@*****.**>') 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 <*****@*****.**>"
def test_unit__email_address_address__ok__from_rfc_email_address__no_label_with_bracket( self): john_address = EmailAddress.from_rfc_email_address( "<*****@*****.**>") 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 == "*****@*****.**"
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 <*****@*****.**>"
def _notify_receiver( self, emitter: User, workspace: Workspace, upload_permission: UploadPermissionInContext, upload_permission_password_enabled: bool, translator: Translator, ) -> Message: logger.info( self, 'preparing email from user "{}" for the upload permission on workspace "{}" to "{}"' .format(emitter.user_id, workspace.workspace_id, upload_permission.email), ) translated_subject = translator.get_translation( self.config. EMAIL__NOTIFICATION__UPLOAD_PERMISSION_TO_RECEIVER__SUBJECT) subject = translated_subject.format( website_title=self.config.WEBSITE__TITLE, emitter_name=emitter.display_name) from_header = self._get_sender(emitter) to_header = EmailAddress.from_rfc_email_address( upload_permission.email) html_template_file_path = ( self.config. EMAIL__NOTIFICATION__UPLOAD_PERMISSION_TO_RECEIVER__TEMPLATE__HTML) receiver = EmailUser(user_email=upload_permission.email) context = { "emitter": emitter, "workspace": workspace, "upload_permission": upload_permission, "receiver": receiver, "upload_permission_password_enabled": upload_permission_password_enabled, } 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
def _notify_receiver( self, emitter: User, shared_content: ContentInContext, content_share: ContentShareInContext, share_password_enabled: bool, translator: Translator, ) -> Message: logger.info( self, 'preparing email from user "{}" for the share on content "{}" to "{}"' .format(emitter.user_id, shared_content.content_id, content_share.email), ) translated_subject = translator.get_translation( self.config.EMAIL__NOTIFICATION__SHARE_CONTENT_TO_RECEIVER__SUBJECT ) subject = translated_subject.format( website_title=self.config.WEBSITE__TITLE, content_filename=shared_content.filename, emitter_name=emitter.display_name, ) from_header = self._get_sender(emitter) to_header = EmailAddress.from_rfc_email_address(content_share.email) username, address = email.utils.parseaddr(content_share.email) html_template_file_path = ( self.config. EMAIL__NOTIFICATION__SHARE_CONTENT_TO_RECEIVER__TEMPLATE__HTML) receiver = EmailUser(user_email=content_share.email) context = { "emitter": emitter, "shared_content": shared_content, "content_share": content_share, "share_password_enabled": share_password_enabled, "receiver": receiver, } 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
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)
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)
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
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
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
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)