def execute_created_user_actions(self, user: User) -> None: """ WARNING! This method will be deprecated soon, see https://github.com/tracim/tracim/issues/1589 and https://github.com/tracim/tracim/issues/1487 This method do post-create user actions """ # FIXME - G.M - 2019-03-18 - move this code to another place when # event mechanism is ready, see https://github.com/tracim/tracim/issues/1487 # event on_created_user should start hook use by agenda app code. app_lib = ApplicationApi(app_list=app_list) if app_lib.exist(AGENDA__APP_SLUG): agenda_api = AgendaApi(current_user=self._user, session=self._session, config=self._config) try: agenda_already_exist = agenda_api.ensure_user_agenda_exists( user) if agenda_already_exist: logger.warning( self, "user {} has just been created but their own agenda already exists" .format(user.user_id), ) except AgendaServerConnectionError as exc: logger.error(self, "Cannot connect to the agenda server") logger.exception(self, exc) except Exception as exc: logger.error( self, "Something went wrong during agenda create/update") logger.exception(self, exc)
def execute_updated_user_actions(self, user: User) -> None: """ WARNING ! This method Will be Deprecated soon, see https://github.com/tracim/tracim/issues/1589 and https://github.com/tracim/tracim/issues/1487 This method do post-update user actions """ # FIXME - G.M - 2019-03-18 - move this code to another place when # event mecanism is ready, see https://github.com/tracim/tracim/issues/1487 # event on_updated_user should start hook use by agenda app code. if self._config.CALDAV__ENABLED: agenda_api = AgendaApi(current_user=self._user, session=self._session, config=self._config) try: agenda_api.ensure_user_agenda_exists(user) except AgendaServerConnectionError as exc: logger.error(self, "Cannot connect to agenda server") logger.exception(self, exc) except Exception as exc: logger.error( self, "Something goes wrong during agenda create/update") logger.exception(self, exc)
def execute_updated_user_actions(self, user: User) -> None: """ WARNING! This method will be deprecated soon, see https://github.com/tracim/tracim/issues/1589 and https://github.com/tracim/tracim/issues/1487 This method does post-update user actions """ # TODO - G.M - 04-04-2018 - [auth] # Check if this is already needed with # new auth system user.ensure_auth_token( validity_seconds=self._config.USER__AUTH_TOKEN__VALIDITY) # FIXME - G.M - 2019-03-18 - move this code to another place when # event mechanism is ready, see https://github.com/tracim/tracim/issues/1487 # event on_updated_user should start hook use by agenda app code. app_lib = ApplicationApi(app_list=app_list) if app_lib.exist(AGENDA__APP_SLUG): agenda_api = AgendaApi(current_user=self._user, session=self._session, config=self._config) try: agenda_api.ensure_user_agenda_exists(user) except AgendaServerConnectionError as exc: logger.error(self, "Cannot connect to agenda server") logger.exception(self, exc) except Exception as exc: logger.error( self, "Something goes wrong during agenda create/update") logger.exception(self, exc)
def execute_update_workspace_actions(self, workspace: Workspace) -> None: """ WARNING ! This method Will be Deprecated soon, see https://github.com/tracim/tracim/issues/1589 and https://github.com/tracim/tracim/issues/1487 This method do post update workspace actions """ # FIXME - G.M - 2019-03-18 - move this code to another place when # event mechanism is ready, see https://github.com/tracim/tracim/issues/1487 # event on_updated_workspace should start hook use by agenda app code. app_lib = ApplicationApi(app_list=app_list) if app_lib.exist(AGENDA__APP_SLUG): # TODO - G.M - 2019-04-11 - Circular Import, will probably be remove # with event refactor, see https://github.com/tracim/tracim/issues/1487 from tracim_backend.applications.agenda.lib import AgendaApi if workspace.agenda_enabled: agenda_api = AgendaApi(current_user=self._user, session=self._session, config=self._config) try: agenda_api.ensure_workspace_agenda_exists(workspace) except AgendaServerConnectionError as exc: logger.error(self, "Cannot connect to agenda server") logger.exception(self, exc) except Exception as exc: logger.error( self, "Something goes wrong during agenda create/update") logger.exception(self, exc)
def _render_template(self, mako_template_filepath: str, context: dict, translator: Translator) -> str: """ Render mako template with all needed current variables. :param mako_template_filepath: file path of mako template :param context: dict with template context :return: template rendered string """ try: template = Template( filename=mako_template_filepath, default_filters=["html_escape"], imports=[ "from mako.filters import html_escape", "from lxml.html.diff import htmldiff", "import humanize", ], ) return template.render(_=translator.get_translation, config=self.config, lang=translator.default_lang, **context) except Exception: logger.exception(self, "Failed to render email template") raise EmailTemplateError("Failed to render email template")
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 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: log = "SMTP sending message return error" logger.exception(self, log) action = failed_action except Exception: log = "Unexpected exception during sending email message using SMTP" logger.exception(self, log) 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 take_action(self, parsed_args: argparse.Namespace) -> None: try: super(AppContextCommand, self).take_action(parsed_args) self._setup_logging(parsed_args) if self.auto_setup_context: with bootstrap(parsed_args.config_file) as app_context: with app_context["request"].tm: self.take_app_action(parsed_args, app_context) except Exception as exc: logger.exception(self, exc) print("Something goes wrong during command") raise exc
def take_action(self, parsed_args: argparse.Namespace) -> None: try: super(AppContextCommand, self).take_action(parsed_args) self._setup_logging(parsed_args) if self.auto_setup_context: with bootstrap(parsed_args.config_file) as app_context: with app_context["request"].tm: self.take_app_action(parsed_args, app_context) except transaction.interfaces.DoomedTransaction: # The code willingly doomed the transaction, do not display an error pass except Exception as exc: logger.exception(self, exc) print("Something goes wrong during command: {}".format(exc)) raise exc
def _check_agenda_exist(self, agenda_url) -> bool: try: response = requests.get(agenda_url) except requests.exceptions.ConnectionError as exc: logger.error( self, "Cannot check agenda existence, connection error to radicale server" ) logger.exception(self, exc) raise AgendaServerConnectionError() from exc if response.status_code < 400: return True else: # TODO - G.M - 2019-03-13 - Better deal with other error code return False
def index_all_content(self) -> IndexedContentsResults: """ Index/update all content in current index of ElasticSearch """ content_api = ContentApi( session=self._session, config=self._config, current_user=self._user, show_archived=True, show_active=True, show_deleted=True, ) contents = content_api.get_all() content_ids_to_index = [] # type: typing.List[int] errored_indexed_contents_ids = [] # type: typing.List[int] for content in contents: content_in_context = ContentInContext( content, config=self._config, dbsession=self._session ) content_ids_to_index.append(content_in_context.content_id) try: self.index_content(content_in_context) except ConnectionError as exc: logger.error( self, "connexion error issue with elasticsearch during indexing of content {}".format( content_in_context.content_id ), ) logger.exception(self, exc) errored_indexed_contents_ids.append(content_in_context.content_id) except Exception as exc: logger.error( self, "something goes wrong during indexing of content {}".format( content_in_context.content_id ), ) logger.exception(self, exc) errored_indexed_contents_ids.append(content_in_context.content_id) return IndexedContentsResults(content_ids_to_index, errored_indexed_contents_ids)
def execute_created_user_actions(self, user: User) -> None: """ WARNING ! This method Will be Deprecated soon, see https://github.com/tracim/tracim/issues/1589 and https://github.com/tracim/tracim/issues/1487 This method do post-create user actions """ # TODO - G.M - 04-04-2018 - [auth] # Check if this is already needed with # new auth system user.ensure_auth_token( validity_seconds=self._config.USER__AUTH_TOKEN__VALIDITY) # FIXME - G.M - 2019-03-18 - move this code to another place when # event mecanism is ready, see https://github.com/tracim/tracim/issues/1487 # event on_created_user should start hook use by agenda app code. if self._config.CALDAV__ENABLED: agenda_api = AgendaApi(current_user=self._user, session=self._session, config=self._config) try: agenda_already_exist = agenda_api.ensure_user_agenda_exists( user) if agenda_already_exist: logger.warning( self, "user {} is just created but his own agenda already exist !!" .format(user.user_id), ) except AgendaServerConnectionError as exc: logger.error(self, "Cannot connect to agenda server") logger.exception(self, exc) except Exception as exc: logger.error( self, "Something goes wrong during agenda create/update") logger.exception(self, exc)
def _notify_tracim(self, mails: typing.List[DecodedMail], imapc: imapclient.IMAPClient) -> None: """ Send http request to tracim endpoint :param mails: list of mails to send :return: none """ logger.debug(self, "Notify tracim about {} new responses".format(len(mails))) # TODO BS 20171124: Look around mail.get_from_address(), mail.get_key() # , mail.get_body() etc ... for raise InvalidEmailError if missing # required informations (actually get_from_address raise IndexError # if no from address for example) and catch it here while mails: mail = mails.pop() try: method, endpoint, json_body_dict = self._create_comment_request( mail) except NoKeyFound: log = "Failed to create comment request due to missing specialkey in mail" logger.exception(self, log) continue except EmptyEmailBody: log = "Empty body, skip mail" logger.error(self, log) continue except AutoReplyEmailNotAllowed: log = "Autoreply mail, skip mail" logger.warning(self, log) continue except Exception: log = "Failed to create comment request in mail fetcher error" logger.exception(self, log) continue try: self._send_request( mail=mail, imapc=imapc, method=method, endpoint=endpoint, json_body_dict=json_body_dict, ) except requests.exceptions.Timeout: log = "Timeout error to transmit fetched mail to tracim" logger.exception(self, log) except requests.exceptions.RequestException: log = "Fail to transmit fetched mail to tracim" logger.exception(self, log)
def execute_created_workspace_actions(self, workspace: Workspace) -> None: """ WARNING ! This method Will be Deprecated soon, see https://github.com/tracim/tracim/issues/1589 and https://github.com/tracim/tracim/issues/1487 This method do post creation workspace actions """ # FIXME - G.M - 2019-03-18 - move this code to another place when # event mecanism is ready, see https://github.com/tracim/tracim/issues/1487 # event on_created_workspace should start hook use by agenda app code. # TODO - G.M - 2019-04-11 - Circular Import, will probably be remove # with event refactor, see https://github.com/tracim/tracim/issues/1487 from tracim_backend.lib.agenda.agenda import AgendaApi if self._config.CALDAV__ENABLED: if workspace.agenda_enabled: agenda_api = AgendaApi(current_user=self._user, session=self._session, config=self._config) try: agenda_already_exist = agenda_api.ensure_workspace_agenda_exists( workspace) if agenda_already_exist: logger.warning( self, "workspace {} is just created but it own agenda already exist !!" .format(workspace.workspace_id), ) except AgendaServerConnectionError as exc: logger.error(self, "Cannot connect to agenda server") logger.exception(self, exc) except Exception as exc: logger.error( self, "Something goes wrong during agenda create/update") logger.exception(self, exc)
def _render_template( self, mako_template_filepath: str, context: dict, translator: Translator ) -> str: """ Render mako template with all needed current variables. :param mako_template_filepath: file path of mako template :param context: dict with template context :return: template rendered string """ try: template = Template(filename=mako_template_filepath) return template.render( _=translator.get_translation, config=self.config, **context ) except Exception as exc: logger.exception(self, 'Failed to render email template: {}'.format(exc.__str__())) raise EmailTemplateError('Failed to render email template: {}'.format(exc.__str__()))
def run(self) -> None: logger.info(self, "Starting MailFetcher") while self._is_active: imapc = None sleep_after_connection = True try: imapc = imapclient.IMAPClient( self.host, self.port, ssl=self.use_ssl, timeout=MAIL_FETCHER_CONNECTION_TIMEOUT) imapc.login(self.user, self.password) logger.debug(self, "Select folder {}".format(self.folder)) imapc.select_folder(self.folder) # force renew connection when deadline is reached deadline = time.time() + self.connection_max_lifetime while True: if not self._is_active: logger.warning(self, "Mail Fetcher process aborted") sleep_after_connection = False break if time.time() > deadline: logger.debug( self, "MailFetcher Connection Lifetime limit excess" ", Try Re-new connection", ) sleep_after_connection = False break # check for new mails self._check_mail(imapc) if self.use_idle and imapc.has_capability("IDLE"): # IDLE_mode wait until event from server logger.debug(self, "wait for event(IDLE)") imapc.idle() imapc.idle_check( timeout=MAIL_FETCHER_IDLE_RESPONSE_TIMEOUT) imapc.idle_done() else: if self.use_idle and not imapc.has_capability("IDLE"): log = ("IDLE mode activated but server do not" "support it, use polling instead.") logger.warning(self, log) if self.burst: self.stop() break # normal polling mode : sleep a define duration logger.debug(self, "sleep for {}".format(self.heartbeat)) time.sleep(self.heartbeat) if self.burst: self.stop() break # Socket except (socket.error, socket.gaierror, socket.herror): log = "Socket fail with IMAP connection" logger.exception(self, log) except socket.timeout: log = "Socket timeout on IMAP connection" logger.exception(self, log) # SSL except ssl.SSLError: log = "SSL error on IMAP connection" logger.exception(self, log) except ssl.CertificateError: log = "SSL Certificate verification failed on IMAP connection" logger.exception(self, log) # Filelock except filelock.Timeout: log = "Mail Fetcher Lock Timeout" logger.warning(self, log, exc_info=True) # IMAP # TODO - G.M - 10-01-2017 - Support imapclient exceptions # when Imapclient stable will be 2.0+ except BadIMAPFetchResponse: log = ("Imap Fetch command return bad response." "Is someone else connected to the mailbox ?") logger.exception(self, log) # Others except Exception: log = "Mail Fetcher error" logger.exception(self, log) finally: # INFO - G.M - 2018-01-09 - Connection closing # Properly close connection according to # https://github.com/mjs/imapclient/pull/279/commits/043e4bd0c5c775c5a08cb5f1baa93876a46732ee # TODO : Use __exit__ method instead when imapclient stable will # be 2.0+ . if imapc: logger.debug(self, "Try logout") try: imapc.logout() except Exception: try: imapc.shutdown() except Exception: log = "Can't logout, connection broken ?" logger.exception(self, log) if self.burst: self.stop() break if sleep_after_connection: logger.debug(self, "sleep for {}".format(self.heartbeat)) time.sleep(self.heartbeat) log = "Mail Fetcher stopped" logger.debug(self, log)
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 Exception as e: # TODO - G.M - 2018-08-27 - Do Better catching for exception here logger.error(self, 'Exception catched during email notification: {}'.format(e.__str__())) logger.exception(self, e)
def take_app_action(self, parsed_args: argparse.Namespace, app_context: AppEnvironment) -> None: # TODO - G.M - 05-04-2018 -Refactor this in order # to not setup object var outside of __init__ . self._session = app_context["request"].dbsession self._app_config = app_context["registry"].settings["CFG"] self._user_api = UserApi(current_user=None, session=self._session, config=self._app_config) self._workspace_api = WorkspaceApi(current_user=None, force_role=True, session=self._session, config=self._app_config) self._agenda_api = AgendaApi(current_user=None, session=self._session, config=self._app_config) # INFO - G.M - 2019-03-13 - check users agendas users = self._user_api.get_all() nb_error_agenda_access = 0 for user in users: try: already_exist = self._agenda_api.ensure_user_agenda_exists( user) if not already_exist: print("New created agenda for user {}".format(user)) except CannotCreateAgenda as exc: nb_error_agenda_access += 1 print("Cannot create agenda for user {}".format(user.user_id)) logger.exception(self, exc) except AgendaServerConnectionError as exc: nb_error_agenda_access += 1 print("Cannot access to agenda server: connection error.") logger.exception(self, exc) except Exception as exc: nb_error_agenda_access += 1 print("Something goes wrong during agenda create/update") logger.exception(self, exc) nb_user_agendas = len(users) nb_verified_user_agenda = len(users) - nb_error_agenda_access print("{}/{} users agenda verified".format(nb_verified_user_agenda, nb_user_agendas)) # # INFO - G.M - 2019-03-13 - check workspaces agendas workspaces = self._workspace_api.get_all() nb_error_agenda_access = 0 nb_workspaces = 0 nb_agenda_enabled_workspace = 0 for workspace in workspaces: nb_workspaces += 1 if workspace.agenda_enabled: nb_agenda_enabled_workspace += 1 try: already_exist = self._agenda_api.ensure_workspace_agenda_exists( workspace) if not already_exist: print("New created agenda for workspace {}".format( workspace.workspace_id)) except CannotCreateAgenda as exc: print("Cannot create agenda for workspace {}".format( workspace.workspace_id)) logger.exception(self, exc) except AgendaServerConnectionError as exc: nb_error_agenda_access += 1 print("Cannot access to agenda server: connection error.") logger.exception(self, exc) nb_verified_workspace_agenda = nb_agenda_enabled_workspace - nb_error_agenda_access nb_workspace_without_agenda_enabled = nb_workspaces - nb_agenda_enabled_workspace print( "{}/{} workspace agenda verified ({} workspace without agenda feature enabled)" .format( nb_verified_workspace_agenda, nb_agenda_enabled_workspace, nb_workspace_without_agenda_enabled, ))
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 Exception: logger.exception(self, "Exception catched during email notification")