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 create_index(self) -> None: """ Create the index template in elasticsearch specifying the mappings and any settings to be used. This can be run at any time, ideally at every new code deploy """ # FIXME BS 2019-06-10: Load ES model only when ES search (see #1892) from tracim_backend.lib.search.es_models import IndexedContent # INFO - G.M - 2019-05-15 - alias migration mecanism to allow easily updatable index. # from https://github.com/elastic/elasticsearch-dsl-py/blob/master/examples/alias_migration.py # Configure index with our indexing preferences logger.info(self, "Create index settings ...") if self._config.SEARCH__ELASTICSEARCH__USE_INGEST: self._create_ingest_pipeline() # create an index template index_template = IndexedContent._index.as_template( self.index_document_alias, self.index_document_pattern) # upload the template into elasticsearch # potentially overriding the one already there index_template.save(using=self.es) # create the first index if it doesn't exist current_index = Index(self.index_document_alias) if not current_index.exists(using=self.es): self.migrate_index(move_data=False) logger.info(self, "ES index is ready")
def __init__(self, settings: typing.Dict[str, typing.Any]): # INFO - G.M - 2019-12-02 - Store own settings original dict, with copy # to avoid issue when serializing CFG object. settings dict is completed # with object in some context self.settings = settings.copy() self.config_info = [] # type: typing.List[ConfigParam] logger.debug(self, "CONFIG_PROCESS:1: load enabled apps") self.load_enabled_apps() logger.debug(self, "CONFIG_PROCESS:3: load config from settings") self.load_config() logger.debug(self, "CONFIG_PROCESS:4: check validity of config given") self._check_consistency() self.check_config_validity() logger.debug(self, "CONFIG_PROCESS:5: End of config process") app_lib = ApplicationApi(app_list=app_list, show_inactive=True) for app in app_lib.get_all(): logger.info( self, "LOADED_APP:{state}:{slug}:{label}".format( state="ENABLED" if app.is_active else "DISABLED", slug=app.slug, label=app.label, ), )
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 send_email_through( config: CFG, sendmail_callable: typing.Callable[[MIMEMultipart], None], message: MIMEMultipart ) -> None: """ Send mail encapsulation to send it in async or sync mode. TODO BS 20170126: A global mail/sender management should be a good thing. Actually, this method is an fast solution. :param config: system configuration :param sendmail_callable: A callable who get message on first parameter :param message: The message who have to be sent """ if config.JOBS__PROCESSING_MODE == config.CST.SYNC: logger.info(send_email_through, "send email to {} synchronously".format(message["To"])) sendmail_callable(message) elif config.JOBS__PROCESSING_MODE == config.CST.ASYNC: logger.info( send_email_through, "send email to {} asynchronously:" "mail stored in queue in wait for a" "mail_notifier daemon".format(message["To"]), ) redis_connection = get_redis_connection(config) queue = get_rq_queue(redis_connection, "mail_sender") queue.enqueue(sendmail_callable, message) else: raise NotImplementedError( "Mail sender processing mode {} is not implemented".format(config.JOBS__PROCESSING_MODE) )
def __init__(self, **settings): logger.info(self, "Add additional radicale config") self.radicale_config = load_radicale_config(()) self.radicale_config = self._parse_additional_radicale_config( self.radicale_config, settings) self.app_config = CFG(settings) self.create_dir_tree(self.radicale_config, self.app_config)
def init_plugin_manager(app_config: CFG) -> pluggy.PluginManager: plugin_manager = create_plugin_manager() # Static plugins, imported here to avoid circular reference with hookimpl from tracim_backend.lib.core.event import EventBuilder from tracim_backend.lib.core.event import EventPublisher import tracim_backend.lib.core.mention as mention plugin_manager.register(EventBuilder(app_config)) plugin_manager.register(EventPublisher(app_config)) mention.register_tracim_plugin(plugin_manager) # INFO - G.M - 2019-11-27 - if a plugin path is provided, load plugins from this path plugin_path = app_config.PLUGIN__FOLDER_PATH plugins = {} # type: typing.Dict[str, types.ModuleType] if plugin_path and plugin_path not in sys.path: sys.path.append(plugin_path) try: plugins = _load_plugins(plugin_manager) finally: sys.path.remove(plugin_path) plugin_manager.hook.add_new_hooks.call_historic(kwargs=dict( plugin_manager=plugin_manager)) _register_all(plugin_manager, plugins) logger.info( init_plugin_manager, "Loaded and registered the following plugins: {}".format( tuple(plugins.keys())), ) plugin_manager.hook.on_plugins_loaded(plugin_manager=plugin_manager) return plugin_manager
def wrapper(self, context, request: "TracimRequest") -> typing.Callable: authorization_checker.check(tracim_context=request) logger.info( request, "{} {} from authenticated user {}".format( request.method, request.path, request.current_user.user_id), ) return func(self, context, request)
def delete_index(self) -> None: # TODO - G.M - 2019-05-31 - This code delete all index related to pattern, check if possible # to be more specific here. logger.info( self, "delete index with pattern {}".format(self.index_document_pattern)) self.es.indices.delete(self.index_document_pattern, allow_no_indices=True) self.es.indices.delete_template(self.index_document_alias)
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"])) # TODO - G.M - 2019-01-29 - optimisize this code, we should not send # email if connection has failed. send_action = "{:8s}".format("SENT") failed_action = "{:8s}".format("SENDFAIL") action = send_action try: send_message_result = self._smtp_connection.send_message( message) # INFO - G.M - 2019-01-29 - send_message return if not failed, # dict of refused recipients. if send_message_result == {}: logger.debug(self, "One mail correctly sent using SMTP.") else: # INFO - G.M - 2019-01-29 - send_message_result != {} # case should not happened # as we send not mail with multiple recipient at the same # time. send_message will not raise exception # just if some recipient work and some other failed. # TODO - G.M - 2019-01-29 - better support for multirecipient email log = "Mail could not be send to some recipient: {}" logger.debug(self, log.format(send_message_result)) action = failed_action except smtplib.SMTPException as exc: log = "SMTP sending message return error: {}" logger.error(self, log.format(str(exc))) action = failed_action except Exception as exc: log = "Unexpected exception during sending email message using SMTP: {}" logger.error(self, log.format(exc.__str__())) logger.error(self, traceback.format_exc()) action = failed_action from tracim_backend.lib.mail_notifier.notifier import EmailManager if action == send_action: msg = "an email was sended to {}".format(message["To"]) else: msg = "fail to send email to {}".format(message["To"]) EmailManager.log_email_notification( msg=msg, action=action, email_recipient=message["To"], email_subject=message["Subject"], config=self.config, )
def log_config_header(self, title: str) -> None: logger.info(self, title) logger.info( self, CONFIG_LOG_TEMPLATE.format( config_value="<config_value>", config_source="<config_source>", config_name="<config_name>", config_name_source="<config_name_source>", ), )
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'])) # TODO - G.M - 2019-01-29 - optimisize this code, we should not send # email if connection has failed. send_action = '{:8s}'.format('SENT') failed_action = '{:8s}'.format('SENDFAIL') action = send_action try: send_message_result = self._smtp_connection.send_message(message) # INFO - G.M - 2019-01-29 - send_message return if not failed, # dict of refused recipients. if send_message_result == {}: logger.debug(self, 'One mail correctly sent using SMTP.') else: # INFO - G.M - 2019-01-29 - send_message_result != {} # case should not happened # as we send not mail with multiple recipient at the same # time. send_message will not raise exception # just if some recipient work and some other failed. # TODO - G.M - 2019-01-29 - better support for multirecipient email log = 'Mail could not be send to some recipient: {}' logger.debug(self, log.format(send_message_result)) action = failed_action except smtplib.SMTPException as exc: log = 'SMTP sending message return error: {}' logger.error(self, log.format(str(exc))) action = failed_action except Exception as exc: log = 'Unexpected exception during sending email message using SMTP: {}' logger.error(self, log.format(exc.__str__())) logger.error(self, traceback.format_exc()) action = failed_action from tracim_backend.lib.mail_notifier.notifier import EmailManager if action == send_action: msg = 'an email was sended to {}'.format(message['To']) else: msg = 'fail to send email to {}'.format(message['To']) EmailManager.log_email_notification( msg=msg, action=action, email_recipient=message['To'], email_subject=message['Subject'], config=self.config, )
def notify_upload_permission( self, emitter: UserInContext, workspace_in_context: WorkspaceInContext, upload_permission_receivers: typing.List[UploadPermissionInContext], upload_permission_password: str, ) -> None: """ Send mails to notify users for sharing content :param emitter: User emitter of the sharing :param workspace_in_context: workspace where receivers can now upload file :param upload_permission_receivers: list of upload_permission :param upload_permission_password: cleartext password of the sharing """ email_sender = EmailSender(self.config, self._smtp_config, self.config.EMAIL__NOTIFICATION__ACTIVATED) upload_permission_password_enabled = False if upload_permission_password: upload_permission_password_enabled = True translator = Translator(self.config, default_lang=emitter.lang) message = self._notify_emitter( emitter=emitter, workspace_in_context=workspace_in_context, upload_permission_receivers=upload_permission_receivers, upload_permission_password=upload_permission_password, translator=translator, ) send_email_through(config=self.config, sendmail_callable=email_sender.send_mail, message=message) emails_receivers_list = [ upload_permission.email for upload_permission in upload_permission_receivers ] logger.info( self, 'Generating upload permission mail from user "{}" to "{}"'.format( emitter.user_id, "".join(emails_receivers_list)), ) for upload_permission in upload_permission_receivers: message = self._notify_receiver( emitter=emitter, workspace=workspace_in_context, upload_permission=upload_permission, upload_permission_password_enabled= upload_permission_password_enabled, translator=translator, ) send_email_through(config=self.config, sendmail_callable=email_sender.send_mail, message=message)
def on_content_created(self, content: Content, context: TracimContext) -> None: try: parser = self._parsers[content.type] except KeyError: logger.info( self, "No mention parser for '{}' content type, doing nothing".format(content.type), ) return mentions = parser.get_mentions(content.current_revision) if not mentions: return self._create_mention_events(mentions, content, context)
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 _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__share__content( self, emitter: User, shared_content: ContentInContext, content_share_receivers: typing.List[ContentShareInContext], share_password: str, ) -> None: """ Send mails to notify users for sharing content :param emitter: User emitter of the sharing :param shared_content: content that is now shared :param content_share_receivers: list of content share :param share_password: cleartext password of the sharing """ email_sender = EmailSender(self.config, self._smtp_config, self.config.EMAIL__NOTIFICATION__ACTIVATED) share_password_enabled = False if share_password: share_password_enabled = True translator = Translator(self.config, default_lang=emitter.lang) message = self._notify_emitter( emitter=emitter, shared_content=shared_content, content_share_receivers=content_share_receivers, share_password=share_password, translator=translator, ) send_email_through(config=self.config, sendmail_callable=email_sender.send_mail, message=message) for content_share in content_share_receivers: emails_receivers_list = [ share_content.email for share_content in content_share_receivers ] logger.info( self, 'Generating share mail from user "{}" to "{}"'.format( emitter.user_id, "".join(emails_receivers_list)), ) message = self._notify_receiver( emitter=emitter, shared_content=shared_content, content_share=content_share, share_password_enabled=share_password_enabled, translator=translator, ) send_email_through(config=self.config, sendmail_callable=email_sender.send_mail, message=message)
def delete_full_user(self, user: User) -> int: """ Full deletion of user in database (including all his revisions) /!\\ May cause inconsistent database :param user: user to delete :return: user_id """ self.delete_user_associated_data(user) self.delete_user_owned_workspace(user) self.delete_user_revisions(user) logger.info(self, "delete user {}".format(user.user_id)) user_id = user.user_id self.safe_delete(user) return user_id
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 delete_revision( self, revision: ContentRevisionRO, do_update_content_last_revision: bool = True, ) -> int: """ :param do_update_content_last_revision: update last revision of content associated to last one if needed. Set only to False when needed to delete all content data and will delete associated content after. :param revision: revision to delete :return: revision_id of revision to delete """ if do_update_content_last_revision and revision.node.revision_id == revision.revision_id: try: new_last_revision = ( self.session.query(ContentRevisionRO).filter( and_( ContentRevisionRO.content_id == revision.node.id, ContentRevisionRO.revision_id != revision.revision_id, )).order_by( ContentRevisionRO.revision_id.desc()).limit( 1).one()) revision.node.current_revision = new_last_revision self.safe_update(revision.node) except NoResultFound: raise CannotDeleteUniqueRevisionWithoutDeletingContent( 'revision "{}" is the only revision for content "{}", it is not possible to delete' .format(revision.revision_id, revision.content_id)) # INFO - G.M - 2019-12-11 - delete revision read status read_statuses = self.session.query(RevisionReadStatus).filter( RevisionReadStatus.revision_id == revision.revision_id) for read_status in read_statuses: logger.info( self, "delete read status of user {} from revision {}".format( read_status.user_id, read_status.revision_id), ) self.safe_delete(read_status) logger.info( self, "delete revision {} of content {}".format(revision.revision_id, revision.content_id), ) revision_id = revision.revision_id self.safe_delete(revision) return revision_id
def connect(self): if not self._smtp_connection: log = "Connecting to SMTP server {}" logger.info(self, log.format(self._smtp_config.server)) if self._smtp_config.use_implicit_ssl: self._smtp_connection = smtplib.SMTP_SSL( self._smtp_config.server, self._smtp_config.port ) else: self._smtp_connection = smtplib.SMTP( self._smtp_config.server, self._smtp_config.port ) self._smtp_connection.ehlo() # TODO - G.M - 2020-04-02 - Starttls usage should be explicit in configuration, see # https://github.com/tracim/tracim/issues/2815 if self._smtp_config.login and not self._smtp_config.use_implicit_ssl: try: starttls_result = self._smtp_connection.starttls() if starttls_result[0] == 220: logger.info(self, "SMTP Start TLS OK") log = "SMTP Start TLS return code: {} with message: {}" logger.debug( self, log.format(starttls_result[0], starttls_result[1].decode("utf-8")) ) except smtplib.SMTPResponseException as exc: log = "SMTP start TLS return error code: {} with message: {}" logger.error(self, log.format(exc.smtp_code, exc.smtp_error.decode("utf-8"))) except Exception: log = "Unexpected exception during SMTP start TLS process" logger.exception(self, log) if self._smtp_config.login: try: login_res = self._smtp_connection.login( self._smtp_config.login, self._smtp_config.password ) if login_res[0] == 235: logger.info(self, "SMTP Authentication Successful") if login_res[0] == 503: logger.info(self, "SMTP Already Authenticated") log = "SMTP login return code: {} with message: {}" logger.debug(self, log.format(login_res[0], login_res[1].decode("utf-8"))) except smtplib.SMTPAuthenticationError as exc: log = "SMTP auth return error code: {} with message: {}" logger.error(self, log.format(exc.smtp_code, exc.smtp_error.decode("utf-8"))) logger.error( self, "check your auth params combinaison " "(login/password) for SMTP" ) except smtplib.SMTPResponseException as exc: log = "SMTP login return error code: {} with message: {}" logger.error(self, log.format(exc.smtp_code, exc.smtp_error.decode("utf-8"))) except Exception: log = "Unexpected exception during SMTP login" logger.exception(self, log)
def get_raw_config( self, config_file_name: str, default_value: typing.Optional[str] = None, secret: bool = False, deprecated: bool = False, deprecated_extended_information: str = "", ) -> str: """ Get config parameter according to a config name. Priority: - 1: Environement variable - 2: Config file data (stored in CFG.settings dict) - 3: default_value :param config_file_name: name of the config parameter name :param default_value: default value if not setted value found :param secret: is the value of the parameter secret ? (if true, it will not be printed) :param deprecated: is the parameter deprecated ? :param deprecated_extended_information: some more information about deprecated parameter :return: """ param = ConfigParam( config_file_name=config_file_name, secret=secret, default_value=default_value, settings=self.settings, deprecated=deprecated, deprecated_extended_information=deprecated_extended_information, ) self.config_info.append(param) logger.info( self, CONFIG_LOG_TEMPLATE.format( config_value=param.config_value, config_source=param.config_source, config_name=param.config_name, config_name_source=param.config_name_source, ), ) if param.deprecated and param.config_value: logger.warning( self, "{parameter_name} parameter is deprecated. {extended_information}" .format( parameter_name=param.config_name, extended_information=param.deprecated_extended_information, ), ) return param.real_config_value
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_backend.lib.mail_notifier.notifier import EmailManager EmailManager.log_notification( action=' SENT', recipient=message['To'], subject=message['Subject'], config=self.config, )
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 init_database(self, settings: typing.Dict[str, typing.Any]): with transaction.manager: try: fixtures_loader = FixturesLoader(self.session, self.app_config) fixtures_loader.loads(self.fixtures) transaction.commit() logger.info(self, "Database initialized.") except IntegrityError: logger.error( self, 'Warning, there was a problem when adding default data' # nopep8 ', it may have already been added:') import traceback logger.error(self, traceback.format_exc()) transaction.abort() logger.error(self, 'Database initialization failed')
def load_config(self) -> None: """Parse configuration file and env variables""" logger.info( self, CONFIG_LOG_TEMPLATE.format( config_value="<config_value>", config_source="<config_source>", config_name="<config_name>", config_name_source="<config_name_source>", ), ) self._load_global_config() self._load_email_config() self._load_ldap_config() self._load_webdav_config() self._load_caldav_config()
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)) message = MIMEMultipart("alternative") message["Subject"] = subject message["From"] = self._get_sender() message["To"] = formataddr((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 ) 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(part2) send_email_through( config=self.config, sendmail_callable=email_sender.send_mail, message=message )
def __setattr__(self, key: str, value: typing.Any) -> None: """ Log-ready setter. Logs all configuration parameters except secret ones. """ is_value_secret = False for secret in SECRET_ENDING_STR: if key.endswith(secret): is_value_secret = True if is_value_secret: logger.info(self, 'CONFIG: [ {} | <value not shown> ]'.format(key)) else: logger.info(self, 'CONFIG: [ {} | {} ]'.format(key, value)) 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 _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_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 send_email_through( config: CFG, sendmail_callable: typing.Callable[[Message], None], message: Message, ) -> None: """ Send mail encapsulation to send it in async or sync mode. TODO BS 20170126: A global mail/sender management should be a good thing. Actually, this method is an fast solution. :param config: system configuration :param sendmail_callable: A callable who get message on first parameter :param message: The message who have to be sent """ if config.EMAIL_PROCESSING_MODE == config.CST.SYNC: logger.info( send_email_through, 'send email to {} synchronously'.format( message['To'] ) ) sendmail_callable(message) elif config.EMAIL_PROCESSING_MODE == config.CST.ASYNC: logger.info( send_email_through, 'send email to {} asynchronously:' 'mail stored in queue in wait for a' 'mail_notifier daemon'.format( message['To'] ) ) redis_connection = get_redis_connection(config) queue = get_rq_queue(redis_connection, 'mail_sender') queue.enqueue(sendmail_callable, message) else: raise NotImplementedError( 'Mail sender processing mode {} is not implemented'.format( config.EMAIL_PROCESSING_MODE, ) )
def connect(self): if not self._smtp_connection: log = 'Connecting to SMTP server {}' logger.info(self, log.format(self._smtp_config.server)) # TODO - G.M - 2019-01-29 - Support for SMTP SSL-only port connection # using smtplib.SMTP_SSL 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() if starttls_result[0] == 220: logger.info(self, 'SMTP Start TLS OK') log = 'SMTP Start TLS return code: {} with message: {}' logger.debug( self, log.format( starttls_result[0], starttls_result[1].decode('utf-8') ) ) except smtplib.SMTPResponseException as exc: log = 'SMTP start TLS return error code: {} with message: {}' logger.error( self, log.format( exc.smtp_code, exc.smtp_error.decode('utf-8') ) ) except Exception as exc: log = 'Unexpected exception during SMTP start TLS process: {}' logger.error(self, log.format(exc.__str__())) logger.error(self, traceback.format_exc()) if self._smtp_config.login: try: login_res = self._smtp_connection.login( self._smtp_config.login, self._smtp_config.password ) if login_res[0] == 235: logger.info(self, 'SMTP Authentication Successful') if login_res[0] == 503: logger.info(self, 'SMTP Already Authenticated') log = 'SMTP login return code: {} with message: {}' logger.debug( self, log.format( login_res[0], login_res[1].decode('utf-8') ) ) except smtplib.SMTPAuthenticationError as exc: log = 'SMTP auth return error code: {} with message: {}' logger.error( self, log.format( exc.smtp_code, exc.smtp_error.decode('utf-8') ) ) logger.error(self, 'check your auth params combinaison ' '(login/password) for SMTP' ) except smtplib.SMTPResponseException as exc: log = 'SMTP login return error code: {} with message: {}' logger.error( self, log.format( exc.smtp_code, exc.smtp_error.decode('utf-8') ) ) except Exception as exc: log = 'Unexpected exception during SMTP login {}' logger.error(self, log.format(exc.__str__())) logger.error(self, traceback.format_exc())
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 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.')