def connect(self): if not self._smtp_connection: log = 'Connecting from SMTP server {}' logger.info(self, log.format(self._smtp_config.server)) self._smtp_connection = smtplib.SMTP(self._smtp_config.server, self._smtp_config.port) self._smtp_connection.ehlo() if self._smtp_config.login: try: starttls_result = self._smtp_connection.starttls() log = 'SMTP start TLS result: {}' logger.debug(self, log.format(starttls_result)) except Exception as e: log = 'SMTP start TLS error: {}' logger.debug(self, log.format(e.__str__())) if self._smtp_config.login: try: login_res = self._smtp_connection.login( self._smtp_config.login, self._smtp_config.password) log = 'SMTP login result: {}' logger.debug(self, log.format(login_res)) except Exception as e: log = 'SMTP login error: {}' logger.debug(self, log.format(e.__str__())) logger.info(self, 'Connection OK')
def notify_content_update(self, content: Content): type(self).send_count += 1 logger.info( self, 'Fake notifier, do not send notification for update of content {}'. format(content.content_id) # nopep8 )
def __init__(self, config: CFG, session: Session, current_user: User = None) -> None: INotifier.__init__( self, config, session, current_user, ) logger.info(self, 'Instantiating Dummy Notifier')
def send_mail(self, message: MIMEMultipart): if not self._is_active: log = 'Not sending email to {} (service disabled)' logger.info(self, log.format(message['To'])) else: self.connect() # Actually, this connects to SMTP only if required logger.info(self, 'Sending email to {}'.format(message['To'])) self._smtp_connection.send_message(message) from tracim.lib.mail_notifier.notifier import EmailManager EmailManager.log_notification( action=' SENT', recipient=message['To'], subject=message['Subject'], config=self.config, )
def __setattr__(self, key, value): """ Log-ready setter. Logs all configuration parameters except password. :param key: :param value: :return: """ if 'PASSWORD' not in key and \ ('URL' not in key or type(value) == str) and \ 'CONTENT' not in key: # We do not show PASSWORD for security reason # we do not show URL because At the time of configuration setup, # it can't be evaluated # We do not show CONTENT in order not to pollute log files logger.info(self, 'CONFIG: [ {} | {} ]'.format(key, value)) else: logger.info(self, 'CONFIG: [ {} | <value not shown> ]'.format(key)) self.__dict__[key] = value
def __init__( self, config: CFG, session: Session, current_user: User=None ): """ :param current_user: the user that has triggered the notification :return: """ INotifier.__init__(self, config, session, current_user) logger.info(self, 'Instantiating Email Notifier') self._user = current_user self.session = session self.config = config self._smtp_config = SmtpConfiguration( self.config.EMAIL_NOTIFICATION_SMTP_SERVER, self.config.EMAIL_NOTIFICATION_SMTP_PORT, self.config.EMAIL_NOTIFICATION_SMTP_USER, self.config.EMAIL_NOTIFICATION_SMTP_PASSWORD )
def __init__(self, config, current_user: User = None): INotifier.__init__(config, current_user) logger.info(self, 'Instantiating Dummy Notifier')
def notify_content_update(self, content: Content): if content.get_last_action().id not \ in self.config.EMAIL_NOTIFICATION_NOTIFIED_EVENTS: logger.info( self, 'Skip email notification for update of content {}' 'by user {} (the action is {})'.format( content.content_id, # below: 0 means "no user" self._user.user_id if self._user else 0, content.get_last_action().id ) ) return logger.info(self, 'About to email-notify update' 'of content {} by user {}'.format( content.content_id, # Below: 0 means "no user" self._user.user_id if self._user else 0 ) ) if content.type not \ in self.config.EMAIL_NOTIFICATION_NOTIFIED_CONTENTS: logger.info( self, 'Skip email notification for update of content {}' 'by user {} (the content type is {})'.format( content.type, # below: 0 means "no user" self._user.user_id if self._user else 0, content.get_last_action().id ) ) return logger.info(self, 'About to email-notify update' 'of content {} by user {}'.format( content.content_id, # Below: 0 means "no user" self._user.user_id if self._user else 0 ) ) #### # # INFO - D.A. - 2014-11-05 - Emails are sent through asynchronous jobs. # For that reason, we do not give SQLAlchemy objects but ids only # (SQLA objects are related to a given thread/session) # try: if self.config.EMAIL_NOTIFICATION_PROCESSING_MODE.lower() == self.config.CST.ASYNC.lower(): logger.info(self, 'Sending email in ASYNC mode') # TODO - D.A - 2014-11-06 # This feature must be implemented in order to be able to scale to large communities raise NotImplementedError('Sending emails through ASYNC mode is not working yet') else: logger.info(self, 'Sending email in SYNC mode') EmailManager( self._smtp_config, self.config, self.session, ).notify_content_update(self._user.user_id, content.content_id) except TypeError as e: logger.error(self, 'Exception catched during email notification: {}'.format(e.__str__()))
def notify_created_account( self, user: User, password: str, ) -> None: """ Send created account email to given user. :param password: choosed password :param user: user to notify """ # TODO BS 20160712: Cyclic import logger.debug(self, 'user: {}'.format(user.user_id)) logger.info(self, 'Sending asynchronous email to 1 user ({0})'.format( user.email, )) async_email_sender = EmailSender( self.config, self._smtp_config, self.config.EMAIL_NOTIFICATION_ACTIVATED ) subject = \ self.config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_SUBJECT \ .replace( EST.WEBSITE_TITLE, self.config.WEBSITE_TITLE.__str__() ) message = MIMEMultipart('alternative') message['Subject'] = subject message['From'] = self._get_sender() message['To'] = formataddr((user.get_display_name(), user.email)) text_template_file_path = self.config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_TEXT # nopep8 html_template_file_path = self.config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_HTML # nopep8 context = { 'user': user, 'password': password, # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for logo_url # nopep8 'logo_url': '', # TODO - G.M - 11-06-2018 - [emailTemplateURL] correct value for login_url # nopep8 'login_url': self.config.WEBSITE_BASE_URL, } body_text = self._render_template( mako_template_filepath=text_template_file_path, context=context ) body_html = self._render_template( mako_template_filepath=html_template_file_path, context=context, ) part1 = MIMEText(body_text, 'plain', 'utf-8') part2 = MIMEText(body_html, 'html', 'utf-8') # Attach parts into message container. # According to RFC 2046, the last part of a multipart message, # in this case the HTML message, is best and preferred. message.attach(part1) message.attach(part2) send_email_through( config=self.config, sendmail_callable=async_email_sender.send_mail, message=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.lib.core.content import ContentApi from tracim.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 - 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, ContentType.Any) main_content = content.parent if content.type == ContentType.Comment 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, 'Sending asynchronous emails to {} user(s)'.format(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 async_email_sender = EmailSender( self.config, self._smtp_config, self.config.EMAIL_NOTIFICATION_ACTIVATED ) for role in notifiable_roles: logger.info(self, 'Sending email to {}'.format(role.user.email)) to_addr = formataddr((role.user.display_name, role.user.email)) # # 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( # nopep8 '{content_id}',str(content.content_id) ) reference_addr = self.config.EMAIL_NOTIFICATION_REFERENCES_EMAIL.replace( #nopep8 '{content_id}',str(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 # subject = self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_SUBJECT subject = 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, main_content.get_status().label.__str__()) reply_to_label = l_('{username} & all members of {workspace}').format( username=user.display_name, workspace=main_content.workspace.label) message = MIMEMultipart('alternative') message['Subject'] = subject message['From'] = self._get_sender(user) message['To'] = to_addr message['Reply-to'] = formataddr((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. message['References'] = formataddr(('',reference_addr)) body_text = self._build_email_body_for_content(self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_TEXT, role, content, user) body_html = self._build_email_body_for_content(self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_HTML, role, content, user) part1 = MIMEText(body_text, 'plain', 'utf-8') part2 = MIMEText(body_html, 'html', 'utf-8') # Attach parts into message container. # According to RFC 2046, the last part of a multipart message, in this case # the HTML message, is best and preferred. message.attach(part1) message.attach(part2) self.log_notification( action='CREATED', recipient=message['To'], subject=message['Subject'], config=self.config, ) send_email_through( self.config, async_email_sender.send_mail, message )
def disconnect(self): if self._smtp_connection: log = 'Disconnecting from SMTP server {}' logger.info(self, log.format(self._smtp_config.server)) self._smtp_connection.quit() logger.info(self, 'Connection closed.')