Esempio n. 1
0
 def send_mail(self, message: MIMEMultipart):
     if not self._is_active:
         logger.info(self, 'Not sending email to {} (service desactivated)'.format(message['To']))
     else:
         self.connect() # Acutally, this connects to SMTP only if required
         logger.info(self, 'Sending email to {}'.format(message['To']))
         self._smtp_connection.send_message(message)
Esempio n. 2
0
 def disconnect(self):
     if self._smtp_connection:
         logger.info(
             self, 'Disconnecting from SMTP server {}'.format(
                 self._smtp_config.server))
         self._smtp_connection.quit()
         logger.info(self, 'Connection closed.')
Esempio n. 3
0
    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')
Esempio n. 4
0
    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')
Esempio n. 5
0
 def send_mail(self, message: MIMEMultipart):
     if not self._is_active:
         logger.info(self, 'Not sending email to {} (service desactivated)'.format(message['To']))
     else:
         self.connect() # Acutally, this connects to SMTP only if required
         logger.info(self, 'Sending email to {}'.format(message['To']))
         self._smtp_connection.send_message(message)
Esempio n. 6
0
    def serialize_it():
        nonlocal content

        if content.type == ContentType.Comment:
            logger.info(
                'serialize_content_for_search_result',
                'Serializing parent class {} instead of {} [content #{}]'.
                format(content.parent.type, content.type, content.content_id))
            content = content.parent

        data_container = content

        if content.revision_to_serialize > 0:
            for revision in content.revisions:
                if revision.revision_id == content.revision_to_serialize:
                    data_container = revision
                    break

        # FIXME - D.A. - 2015-02-23 - This import should not be there...
        from tracim.lib.content import ContentApi
        breadcrumbs = ContentApi(None).build_breadcrumb(
            data_container.workspace,
            data_container.content_id,
            skip_root=True)

        last_comment_datetime = data_container.updated
        comments = data_container.get_comments()
        if comments:
            last_comment_datetime = max(
                last_comment_datetime,
                max(comment.updated for comment in comments))

        content_type = ContentType(content.type)
        result = DictLikeClass(
            id=content.content_id,
            type=DictLikeClass(content_type.toDict()),
            parent=context.toDict(content.parent),
            workspace=context.toDict(content.workspace),
            content=data_container.description,
            content_raw=data_container.description_as_raw_text(),
            created=data_container.created,
            created_as_delta=data_container.created_as_delta(),
            label=data_container.label,
            icon=ContentType.get_icon(content.type),
            owner=context.toDict(data_container.owner),
            status=context.toDict(data_container.get_status()),
            breadcrumb=context.toDict(breadcrumbs),
            last_activity=last_comment_datetime,
            last_activity_as_delta=content.datetime_as_delta(
                last_comment_datetime))

        if content.type == ContentType.File:
            result.label = content.label.__str__()

        if not result.label or '' == result.label:
            result.label = 'No title'

        return result
Esempio n. 7
0
 def get_tracker_js_content(self, js_tracker_file_path = None):
     js_tracker_file_path = tg.config.get('js_tracker_path', None)
     if js_tracker_file_path:
         logger.info(self, 'Reading JS tracking code from file {}'.format(js_tracker_file_path))
         with open (js_tracker_file_path, 'r') as js_file:
             data = js_file.read()
         return data
     else:
         return ''
Esempio n. 8
0
 def get_tracker_js_content(self, js_tracker_file_path = None):
     js_tracker_file_path = tg.config.get('js_tracker_path', None)
     if js_tracker_file_path:
         logger.info(self, 'Reading JS tracking code from file {}'.format(js_tracker_file_path))
         with open (js_tracker_file_path, 'r') as js_file:
             data = js_file.read()
         return data
     else:
         return ''
Esempio n. 9
0
 def get_tracker_js_content(self, js_tracker_file_path=None):
     """Get frontend analytics file."""
     result = ''
     js_tracker_file_path = tg.config.get('js_tracker_path', None)
     if js_tracker_file_path:
         info_log = 'Reading JS tracking code from file {}'
         logger.info(self, info_log.format(js_tracker_file_path))
         with open(js_tracker_file_path, 'r') as js_file:
             data = js_file.read()
         result = data
     return result
Esempio n. 10
0
 def get_tracker_js_content(self, js_tracker_file_path=None):
     """Get frontend analytics file."""
     result = ''
     js_tracker_file_path = tg.config.get('js_tracker_path', None)
     if js_tracker_file_path:
         info_log = 'Reading JS tracking code from file {}'
         logger.info(self, info_log.format(js_tracker_file_path))
         with open(js_tracker_file_path, 'r') as js_file:
             data = js_file.read()
         result = data
     return result
Esempio n. 11
0
 def login(self, *args, **kwargs):
     """
     This method is there for backward compatibility only
     This is related to the default TG2 authentication behavior...
     Now the login form is included in home page
     :param args:
     :param kwargs:
     :return:
     """
     came_from = kwargs['came_from'] if 'came_from' in kwargs.keys() else ''
     logger.info(self, 'came_from: {}'.format(kwargs))
     return self.index(came_from, args, *kwargs)
Esempio n. 12
0
 def login(self, *args, **kwargs):
     """
     This method is there for backward compatibility only
     This is related to the default TG2 authentication behavior...
     Now the login form is included in home page
     :param args:
     :param kwargs:
     :return:
     """
     came_from = kwargs['came_from'] if 'came_from' in kwargs.keys() else ''
     logger.info(self, 'came_from: {}'.format(kwargs))
     return self.index(came_from, args, *kwargs)
Esempio n. 13
0
    def serialize_it():
        nonlocal content

        if content.type == ContentType.Comment:
            logger.info('serialize_content_for_search_result', 'Serializing parent class {} instead of {} [content #{}]'.format(content.parent.type, content.type, content.content_id))
            content = content.parent

        data_container = content

        if content.revision_to_serialize>0:
            for revision in content.revisions:
                if revision.revision_id==content.revision_to_serialize:
                    data_container = revision
                    break

        # FIXME - D.A. - 2015-02-23 - This import should not be there...
        from tracim.lib.content import ContentApi
        breadcrumbs = ContentApi(None).build_breadcrumb(data_container.workspace, data_container.content_id, skip_root=True)

        last_comment_datetime = data_container.updated
        comments = data_container.get_comments()
        if comments:
            last_comment_datetime = max(last_comment_datetime, max(comment.updated for comment in comments))

        content_type = ContentType(content.type)
        result = DictLikeClass(
            id = content.content_id,
            type = DictLikeClass(content_type.toDict()),
            parent = context.toDict(content.parent),
            workspace = context.toDict(content.workspace),

            content = data_container.description,
            content_raw = data_container.description_as_raw_text(),

            created = data_container.created,
            created_as_delta = data_container.created_as_delta(),
            label = data_container.label,
            icon = ContentType.get_icon(content.type),
            owner = context.toDict(data_container.owner),
            status = context.toDict(data_container.get_status()),
            breadcrumb = context.toDict(breadcrumbs),
            last_activity=last_comment_datetime,
            last_activity_as_delta=content.datetime_as_delta(last_comment_datetime)
        )

        if content.type==ContentType.File:
            result.label = content.label.__str__() if content.label else content.file_name.__str__()

        if not result.label or ''==result.label:
            result.label = 'No title'

        return result
Esempio n. 14
0
    def __init__(self, current_user: User=None):
        """
        :param current_user: the user that has triggered the notification
        :return:
        """
        logger.info(self, 'Instantiating Real Notifier')
        cfg = CFG.get_instance()

        self._user = current_user
        self._smtp_config = SmtpConfiguration(cfg.EMAIL_NOTIFICATION_SMTP_SERVER,
                                       cfg.EMAIL_NOTIFICATION_SMTP_PORT,
                                       cfg.EMAIL_NOTIFICATION_SMTP_USER,
                                       cfg.EMAIL_NOTIFICATION_SMTP_PASSWORD)
Esempio n. 15
0
    def index(self, came_from='', *args, **kwargs):
        if request.identity:
            if came_from:
                logger.info(self, 'Will redirect to {}'.format(came_from))
                redirect(url(came_from))
            else:
                redirect(self.url(None, self.home.__name__))

        login_counter = request.environ.get('repoze.who.logins', 0)
        if login_counter > 0:
            flash(_('Wrong credentials'), CST.STATUS_ERROR)
        return dict(page='login', login_counter=str(login_counter),
                    came_from=came_from)
Esempio n. 16
0
    def index(self, came_from='', *args, **kwargs):
        if request.identity:
            if came_from:
                logger.info(self, 'Will redirect to {}'.format(came_from))
                redirect(url(came_from))
            else:
                redirect(self.url(None, self.home.__name__))

        login_counter = request.environ.get('repoze.who.logins', 0)
        if login_counter > 0:
            flash(_('Wrong credentials'), CST.STATUS_ERROR)
        return dict(page='login', login_counter=str(login_counter),
                    came_from=came_from)
Esempio n. 17
0
 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.notifications import EmailNotifier
         EmailNotifier.log_notification(
             action='   SENT',
             recipient=message['To'],
             subject=message['Subject'],
         )
Esempio n. 18
0
 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.notifications import EmailNotifier
         EmailNotifier.log_notification(
             action='   SENT',
             recipient=message['To'],
             subject=message['Subject'],
         )
Esempio n. 19
0
    def __init__(self, current_user: User=None):
        """
        :param current_user: the user that has triggered the notification
        :return:
        """
        logger.info(self, 'Instantiating Real Notifier')
        # TODO: Find a way to import properly without cyclic import
        from tracim.config.app_cfg import CFG
        cfg = CFG.get_instance()

        self._user = current_user
        self._smtp_config = SmtpConfiguration(cfg.EMAIL_NOTIFICATION_SMTP_SERVER,
                                       cfg.EMAIL_NOTIFICATION_SMTP_PORT,
                                       cfg.EMAIL_NOTIFICATION_SMTP_USER,
                                       cfg.EMAIL_NOTIFICATION_SMTP_PASSWORD)
Esempio n. 20
0
 def stop(self, name: str) -> None:
     """
     Stop daemon with his name and wait for him.
     Where name is given name when daemon started
     with run method.
     :param name:
     """
     if name in self._running_daemons:
         logger.info(self, 'Stopping daemon with name "{0}" ...'
                           .format(name))
         self._running_daemons[name].stop()
         self._running_daemons[name].join()
         del self._running_daemons[name]
         logger.info(self, 'Stopping daemon with name "{0}": OK'
                           .format(name))
Esempio n. 21
0
 def stop(self, name: str) -> None:
     """
     Stop daemon with his name and wait for him.
     Where name is given name when daemon started
     with run method.
     :param name:
     """
     if name in self._running_daemons:
         logger.info(self,
                     'Stopping daemon with name "{0}" ...'.format(name))
         self._running_daemons[name].stop()
         self._running_daemons[name].join()
         del self._running_daemons[name]
         logger.info(self,
                     'Stopping daemon with name "{0}": OK'.format(name))
Esempio n. 22
0
    def __init__(self, current_user: User = None):
        """
        :param current_user: the user that has triggered the notification
        :return:
        """
        logger.info(self, 'Instantiating Real Notifier')
        # TODO: Find a way to import properly without cyclic import
        from tracim.config.app_cfg import CFG
        cfg = CFG.get_instance()

        self._user = current_user
        self._smtp_config = SmtpConfiguration(
            cfg.EMAIL_NOTIFICATION_SMTP_SERVER,
            cfg.EMAIL_NOTIFICATION_SMTP_PORT, cfg.EMAIL_NOTIFICATION_SMTP_USER,
            cfg.EMAIL_NOTIFICATION_SMTP_PASSWORD)
Esempio n. 23
0
    def __setattr__(self, key, value):
        """
        Log-ready setter. this is used for logging configuration (every parameter except password)
        :param key:
        :param value:
        :return:
        """
        if 'PASSWORD' not in key and 'URL' not in key and 'CONTENT' not in key:
            # We do not show PASSWORD for security reason
            # we do not show URL because the associated config uses tg.lurl() which is evaluated when at display time.
            # 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
Esempio n. 24
0
    def __setattr__(self, key, value):
        """
        Log-ready setter. this is used for logging configuration (every parameter except password)
        :param key:
        :param value:
        :return:
        """
        if 'PASSWORD' not in key and 'URL' not in key and 'CONTENT' not in key:
            # We do not show PASSWORD for security reason
            # we do not show URL because the associated config uses tg.lurl() which is evaluated when at display time.
            # 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
Esempio n. 25
0
    def register_converter(cls, context_string, model_class, convert_function):
        """

        :param context_string:
        :param model_class:
        :param convert_function:
        :return:
        """
        if context_string not in Context._converters:
            Context._converters[context_string] = dict()

        if model_class not in Context._converters[context_string]:
            logger.info(Context, 'Registering Serialization feature: [ {2} | {1} | {0} ]'.format(
                convert_function.__name__,
                model_class.__name__,
                context_string))

            Context._converters[context_string][model_class] = convert_function
Esempio n. 26
0
    def register_converter(cls, context_string, model_class, convert_function):
        """

        :param context_string:
        :param model_class:
        :param convert_function:
        :return:
        """
        if context_string not in Context._converters:
            Context._converters[context_string] = dict()

        if model_class not in Context._converters[context_string]:
            logger.info(Context, 'Registering Serialization feature: [ {2} | {1} | {0} ]'.format(
                convert_function.__name__,
                model_class.__name__,
                context_string))

            Context._converters[context_string][model_class] = convert_function
Esempio n. 27
0
    def run(self, name: str, daemon_class: object, **kwargs) -> None:
        """
        Start a daemon with given daemon class.
        :param name: Name of runned daemon. It's not possible to start two
        daemon with same name. In the opposite case, raise
        tracim.lib.exceptions.AlreadyRunningDaemon
        :param daemon_class: Daemon class to use for daemon instance.
        :param kwargs: Other kwargs will be given to daemon class
        instantiation.
        """
        if name in self._running_daemons:
            raise AlreadyRunningDaemon(
                'Daemon with name "{0}" already running'.format(name)
            )

        logger.info(self, 'Starting daemon with name "{0}" and class "{1}" ...'
                          .format(name, daemon_class))
        daemon = daemon_class(name=name, kwargs=kwargs, daemon=True)
        daemon.start()
        self._running_daemons[name] = daemon
Esempio n. 28
0
    def run(self, name: str, daemon_class: object, **kwargs) -> None:
        """
        Start a daemon with given daemon class.
        :param name: Name of runned daemon. It's not possible to start two
        daemon with same name. In the opposite case, raise
        tracim.lib.exceptions.AlreadyRunningDaemon
        :param daemon_class: Daemon class to use for daemon instance.
        :param kwargs: Other kwargs will be given to daemon class
        instantiation.
        """
        if name in self._running_daemons:
            raise AlreadyRunningDaemon(
                'Daemon with name "{0}" already running'.format(name))

        logger.info(
            self, 'Starting daemon with name "{0}" and class "{1}" ...'.format(
                name, daemon_class))
        daemon = daemon_class(name=name, kwargs=kwargs, daemon=True)
        daemon.start()
        self._running_daemons[name] = daemon
Esempio n. 29
0
 def run(self) -> None:
     logger.info(self, 'Starting MailFetcher')
     while self._is_active:
         logger.debug(self, 'sleep for {}'.format(self.delay))
         time.sleep(self.delay)
         try:
             self._connect()
             with self.lock.acquire(timeout=MAIL_FETCHER_FILELOCK_TIMEOUT):
                 messages = self._fetch()
             cleaned_mails = [
                 DecodedMail(m.message, m.uid) for m in messages
             ]
             self._notify_tracim(cleaned_mails)
             self._disconnect()
         except filelock.Timeout as e:
             log = 'Mail Fetcher Lock Timeout {}'
             logger.warning(self, log.format(e.__str__()))
         except Exception as e:
             # TODO - G.M - 2017-11-23 - Identify possible exceptions
             log = 'IMAP error: {}'
             logger.warning(self, log.format(e.__str__()))
Esempio n. 30
0
    def stop_all(self) -> None:
        """
        Stop all started daemons and wait for them.
        """
        logger.info(self, 'Stopping all daemons')
        for name, daemon in self._running_daemons.items():
            logger.info(self, 'Stopping daemon "{0}" ...'.format(name))
            daemon.stop()

        for name, daemon in self._running_daemons.items():
            logger.info(
                self,
                'Stopping daemon "{0}" waiting confirmation'.format(name),
            )
            daemon.join()
            logger.info(self, 'Stopping daemon "{0}" OK'.format(name))

        self._running_daemons = {}
Esempio n. 31
0
    def stop_all(self, *args, **kwargs) -> None:
        """
        Stop all started daemons and wait for them.
        """
        logger.info(self, 'Stopping all daemons')
        for name, daemon in self._running_daemons.items():
            logger.info(self, 'Stopping daemon "{0}" ...'.format(name))
            daemon.stop()

        for name, daemon in self._running_daemons.items():
            daemon.join()
            logger.info(self, 'Stopping daemon "{0}" OK'.format(name))

        self._running_daemons = {}
Esempio n. 32
0
 def notify_content_update(self, content: Content):
     logger.info(self, 'Fake notifier, do not send email-notification for update of content {}'.format(content.content_id))
Esempio n. 33
0
    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
        from tracim.lib.notifications import EST

        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._smtp_config,
            self._global_config.EMAIL_NOTIFICATION_ACTIVATED
        )

        subject = \
            self._global_config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_SUBJECT \
            .replace(
                EST.WEBSITE_TITLE,
                self._global_config.WEBSITE_TITLE.__str__()
            )
        message = MIMEMultipart('alternative')
        message['Subject'] = subject
        message['From'] = self._global_config.EMAIL_NOTIFICATION_FROM
        message['To'] = user.email

        text_template_file_path = self._global_config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_TEXT  # nopep8
        html_template_file_path = self._global_config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_HTML  # nopep8

        body_text = self._render(
            mako_template_filepath=text_template_file_path,
            context={
                'user': user,
                'password': password,
                'login_url': self._global_config.WEBSITE_BASE_URL,
            }
        )

        body_html = self._render(
            mako_template_filepath=html_template_file_path,
            context={
                'user': user,
                'password': password,
                'login_url': self._global_config.WEBSITE_BASE_URL,
            }
        )

        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)

        asyncjob_perform(async_email_sender.send_mail, message)

        # Note: The following action allow to close the SMTP connection.
        # This will work only if the async jobs are done in the right order
        asyncjob_perform(async_email_sender.disconnect)
Esempio n. 34
0
    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
        from tracim.lib.notifications import EST

        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._smtp_config,
            self._global_config.EMAIL_NOTIFICATION_ACTIVATED
        )

        subject = \
            self._global_config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_SUBJECT \
            .replace(
                EST.WEBSITE_TITLE,
                self._global_config.WEBSITE_TITLE.__str__()
            )
        message = MIMEMultipart('alternative')
        message['Subject'] = subject
        message['From'] = '{0} <{1}>'.format(
            self._global_config.EMAIL_NOTIFICATION_FROM_DEFAULT_LABEL,
            self._global_config.EMAIL_NOTIFICATION_FROM_EMAIL,
        )
        message['To'] = user.email

        text_template_file_path = self._global_config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_TEXT  # nopep8
        html_template_file_path = self._global_config.EMAIL_NOTIFICATION_CREATED_ACCOUNT_TEMPLATE_HTML  # nopep8

        body_text = self._render(
            mako_template_filepath=text_template_file_path,
            context={
                'user': user,
                'password': password,
                'login_url': self._global_config.WEBSITE_BASE_URL,
            }
        )

        body_html = self._render(
            mako_template_filepath=html_template_file_path,
            context={
                'user': user,
                'password': password,
                'login_url': self._global_config.WEBSITE_BASE_URL,
            }
        )

        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(async_email_sender.send_mail, message)
Esempio n. 35
0
    def notify_content_update(self, content: Content):
        # TODO: Find a way to import properly without cyclic import
        from tracim.config.app_cfg import CFG
        global_config = CFG.get_instance()

        if content.get_last_action().id not \
                in global_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 global_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 global_config.EMAIL_NOTIFICATION_PROCESSING_MODE.lower()==global_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')
                asyncjob_perform(EmailNotifier(self._smtp_config, global_config).notify_content_update, self._user.user_id, content.content_id)
            else:
                logger.info(self, 'Sending email in SYNC mode')
                EmailNotifier(self._smtp_config, global_config).notify_content_update(self._user.user_id, content.content_id)
        except Exception as e:
            logger.error(self, 'Exception catched during email notification: {}'.format(e.__str__()))
Esempio n. 36
0
 def disconnect(self):
     if self._smtp_connection:
         logger.info(self, 'Disconnecting from SMTP server {}'.format(self._smtp_config.server))
         self._smtp_connection.quit()
         logger.info(self, 'Connection closed.')
Esempio n. 37
0
    def notify_content_update(self, content: Content):
        # TODO: Find a way to import properly without cyclic import
        from tracim.config.app_cfg import CFG
        global_config = CFG.get_instance()

        if content.get_last_action().id not \
                in global_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 global_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 global_config.EMAIL_NOTIFICATION_PROCESSING_MODE.lower(
            ) == global_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')
                EmailNotifier(self._smtp_config,
                              global_config).notify_content_update(
                                  self._user.user_id, content.content_id)
        except Exception as e:
            logger.error(
                self, 'Exception catched during email notification: {}'.format(
                    e.__str__()))
Esempio n. 38
0
    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, 'wail 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)
                        # normal polling mode : sleep a define duration
                        logger.debug(self,
                                     'sleep for {}'.format(self.heartbeat))
                        time.sleep(self.heartbeat)

            # Socket
            except (socket.error, socket.gaierror, socket.herror) as e:
                log = 'Socket fail with IMAP connection {}'
                logger.error(self, log.format(e.__str__()))

            except socket.timeout as e:
                log = 'Socket timeout on IMAP connection {}'
                logger.error(self, log.format(e.__str__()))

            # SSL
            except ssl.SSLError as e:
                log = 'SSL error on IMAP connection'
                logger.error(self, log.format(e.__str__()))

            except ssl.CertificateError as e:
                log = 'SSL Certificate verification failed on IMAP connection'
                logger.error(self, log.format(e.__str__()))

            # Filelock
            except filelock.Timeout as e:
                log = 'Mail Fetcher Lock Timeout {}'
                logger.warning(self, log.format(e.__str__()))

            # IMAP
            # TODO - G.M - 10-01-2017 - Support imapclient exceptions
            # when Imapclient stable will be 2.0+

            except BadIMAPFetchResponse as e:
                log = 'Imap Fetch command return bad response.' \
                      'Is someone else connected to the mailbox ?: ' \
                      '{}'
                logger.error(self, log.format(e.__str__()))
            # Others
            except Exception as e:
                log = 'Mail Fetcher error {}'
                logger.error(self, log.format(e.__str__()))

            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 as e:
                            log = "Can't logout, connection broken ? {}"
                            logger.error(self, log.format(e.__str__()))

            if sleep_after_connection:
                logger.debug(self, 'sleep for {}'.format(self.heartbeat))
                time.sleep(self.heartbeat)

        log = 'Mail Fetcher stopped'
        logger.debug(self, log)
Esempio n. 39
0
 def __init__(self, current_user: User=None):
     logger.info(self, 'Instantiating Dummy Notifier')
Esempio n. 40
0
    def notify_content_update(self, event_actor_id: int, event_content_id: int):
        """
        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.content import ContentApi

        user = UserApi(None).get_one(event_actor_id)
        logger.debug(self, 'Content: {}'.format(event_content_id))

        content = ContentApi(user, show_archived=True, show_deleted=True).get_one(event_content_id, ContentType.Any) # TODO - use a system user instead of the user that has triggered the event
        main_content = content.parent if content.type==ContentType.Comment else content
        notifiable_roles = WorkspaceApi(user).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._smtp_config, self._global_config.EMAIL_NOTIFICATION_ACTIVATED)

        for role in notifiable_roles:
            logger.info(self, 'Sending email to {}'.format(role.user.email))
            to_addr = '{name} <{email}>'.format(name=role.user.display_name, email=role.user.email)

            #
            #  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._global_config.EMAIL_NOTIFICATION_CONTENT_UPDATE_SUBJECT
            subject = subject.replace(EST.WEBSITE_TITLE, self._global_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__())

            message = MIMEMultipart('alternative')
            message['Subject'] = subject
            message['From'] = self._global_config.EMAIL_NOTIFICATION_FROM
            message['To'] = to_addr

            body_text = self._build_email_body(self._global_config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_TEXT, role, content, user)



            body_html = self._build_email_body(self._global_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)

            message_str = message.as_string()
            asyncjob_perform(async_email_sender.send_mail, message)
            # s.send_message(message)

        # Note: The following action allow to close the SMTP connection.
        # This will work only if the async jobs are done in the right order
        asyncjob_perform(async_email_sender.disconnect)
Esempio n. 41
0
    def notify_content_update(self, event_actor_id: int,
                              event_content_id: int):
        """
        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.content import ContentApi

        user = UserApi(None).get_one(event_actor_id)
        logger.debug(self, 'Content: {}'.format(event_content_id))

        content = ContentApi(
            user, show_archived=True, show_deleted=True
        ).get_one(
            event_content_id, ContentType.Any
        )  # TODO - use a system user instead of the user that has triggered the event
        main_content = content.parent if content.type == ContentType.Comment else content
        notifiable_roles = WorkspaceApi(user).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._smtp_config,
            self._global_config.EMAIL_NOTIFICATION_ACTIVATED)

        for role in notifiable_roles:
            logger.info(self, 'Sending email to {}'.format(role.user.email))
            to_addr = '{name} <{email}>'.format(name=role.user.display_name,
                                                email=role.user.email)

            #
            #  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._global_config.EMAIL_NOTIFICATION_CONTENT_UPDATE_SUBJECT
            subject = subject.replace(
                EST.WEBSITE_TITLE, self._global_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__())

            message = MIMEMultipart('alternative')
            message['Subject'] = subject
            message['From'] = self._get_sender(user)
            message['To'] = to_addr

            body_text = self._build_email_body(
                self._global_config.
                EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_TEXT, role, content,
                user)

            body_html = self._build_email_body(
                self._global_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)

            send_email_through(async_email_sender.send_mail, message)
Esempio n. 42
0
 def __init__(self, current_user: User = None):
     logger.info(self, 'Instantiating Dummy Notifier')
Esempio n. 43
0
 def notify_content_update(self, content: Content):
     type(self).send_count += 1
     logger.info(
         self,
         'Fake notifier, do not send email-notification for update of content {}'
         .format(content.content_id))