Exemple #1
0
 def _parse_additional_radicale_config(self, config: ConfigParser,
                                       settings: dict) -> ConfigParser:
     """
     Add settings params beginning with
     "RADICALE_MAIN_SECTION.RADICALE_SUBMAIN_SECTION." to radicale config.
     """
     radicales_params = sliced_dict(
         data=settings,
         beginning_key_string="{}.{}.".format(RADICALE_MAIN_SECTION,
                                              RADICALE_SUBMAIN_SECTION),
     )
     for param_name, value in radicales_params.items():
         parameter_parts = param_name.split(".")
         assert len(parameter_parts) == 4
         main_section, sub_main_section, radicale_section, radicale_param_config = (
             parameter_parts)
         assert main_section == "caldav"
         assert sub_main_section == "radicale"
         if not config.has_section(radicale_section):
             config.add_section(radicale_section)
         logger.debug(
             self,
             "Override radicale config: {} : {}".format(param_name, value))
         config.set(radicale_section, radicale_param_config, value)
     return config
Exemple #2
0
 def ensure_workspace_agenda_exists(self, workspace: Workspace) -> bool:
     """
     Return true if agenda already exist, false if it was just create,
     raise Exception if agenda cannot be created.
     """
     logger.debug(
         self, "check for agenda existence of workspace {}".format(
             workspace.workspace_id))
     if not workspace.agenda_enabled:
         raise WorkspaceAgendaDisabledException()
     workspace_agenda_url = self.get_workspace_agenda_url(workspace,
                                                          use_proxy=False)
     if not self._check_agenda_exist(workspace_agenda_url):
         self._create_agenda(
             agenda_url=workspace_agenda_url,
             agenda_name=workspace.label,
             agenda_description=workspace.description,
         )
         return False
     else:
         self._update_agenda_props(
             agenda_url=workspace_agenda_url,
             agenda_name=workspace.label,
             agenda_description=workspace.description,
         )
         return True
Exemple #3
0
    def _send_request(
        self,
        mail: DecodedMail,
        imapc: imapclient.IMAPClient,
        method: str,
        endpoint: str,
        json_body_dict: dict,
    ):
        logger.debug(
            self,
            "Contact API on {endpoint} with method {method} with body {body}".format(
                endpoint=endpoint, method=method, body=str(json_body_dict)
            ),
        )
        if method == "POST":
            request_method = requests.post
        else:
            # TODO - G.M - 2018-08-24 - Better handling exception
            raise UnsupportedRequestMethod("Request method not supported")

        r = request_method(
            url=endpoint,
            json=json_body_dict,
            headers=self._get_auth_headers(mail.get_from_address()),
        )
        if r.status_code not in [200, 204]:
            details = r.json().get("message")
            msg = "bad status code {} (200 and 204 are valid) response when sending mail to tracim: {}"
            msg = msg.format(str(r.status_code), details)
            raise BadStatusCode(msg)
        # Flag all correctly checked mail
        if r.status_code in [200, 204]:
            imapc.add_flags((mail.uid,), IMAP_CHECKED_FLAG)
            imapc.add_flags((mail.uid,), IMAP_SEEN_FLAG)
Exemple #4
0
 def doom_request_transaction(self, exc: Exception, *args, **kwargs) -> None:
     try:
         # NOTE 2020-09-09 - S.G.
         # we have to search for the request object in all arguments
         # as we cannot be sure of its place in it.
         # For example if a handler is an object method the first argument
         # will be the controller object, not the request object.
         request = next(arg for arg in args if isinstance(arg, Request))
         transaction_status = request.tm.get().status
         # INFO - 2020-10-01 - G.M : In some specific case: we do arrive in situation
         # where transaction is not active anymore, in such situation, it's not possible
         # to doom the request and we do get " ValueError('non-doomable')" Exception.
         # to handle this case in a more clear way, we do Doom only active Transaction.
         if transaction_status == TransactionStatus.ACTIVE:
             request.tm.doom()
         else:
             # INFO - 2020-10-01 - G.M : debug unattended transaction status
             # for troubleshooting future issues
             logger.debug(
                 self,
                 'Transaction not active, status : "{}", cannot be doomed'.format(
                     transaction_status
                 ),
             )
     except StopIteration:
         logger.error(self, "Cannot find request object in arguments")
     except NoTransaction:
         # INFO - 2020-10-01 - G.M - In some very specific case like PageNotFound case, pyramid
         # doesn't provide a true transaction, so doom it will raise a NoTransaction exception.
         logger.debug(self, "Transaction not initialized, cannot be doomed")
Exemple #5
0
    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)
Exemple #6
0
 def safe_delete_dir(self, dir_path: str) -> None:
     """
     Delete dir only if dry-run mode is disabled
     """
     if not self.dry_run_mode:
         logger.debug(self, "delete {} dir".format(str(dir_path)))
         shutil.rmtree(dir_path)
     else:
         logger.debug(self, "fake deletion of {} dir".format(dir_path))
Exemple #7
0
 def publish_messages_for_event(self, event_id: int) -> None:
     redis_connection = get_redis_connection(self._config)
     queue = get_rq_queue(redis_connection, RQ_QUEUE_NAME)
     logger.debug(
         self,
         "publish event(id={}) asynchronously to RQ queue {}".format(
             event_id, RQ_QUEUE_NAME),
     )
     queue.enqueue(self._publish_messages_for_event, event_id)
Exemple #8
0
 def safe_delete(self, object_to_delete: DeclarativeBase) -> None:
     """
     Delete object only if dry-run mode is disabled
     """
     if not self.dry_run_mode:
         logger.debug(self, "delete {}".format(str(object_to_delete)))
         self.session.delete(object_to_delete)
     else:
         logger.debug(self,
                      "fake deletion of {}".format(str(object_to_delete)))
Exemple #9
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"]))
            # 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,
            )
Exemple #10
0
    def tearDown(self) -> None:
        logger.debug(self, "TearDown Test...")
        from tracim_backend.models.meta import DeclarativeBase

        self.session.rollback()
        self.session.close_all()
        transaction.abort()
        DeclarativeBase.metadata.drop_all(self.engine)
        self.engine.dispose()
        DepotManager._clear()
        testing.tearDown()
Exemple #11
0
    def tearDown(self) -> None:
        logger.debug(self, 'TearDown Test...')

        self.session.rollback()
        self.session.close_all()
        transaction.abort()
        DeclarativeBase.metadata.drop_all(self.engine)
        sql = text('DROP TABLE IF EXISTS migrate_version;')
        result = self.engine.execute(sql)
        self.engine.dispose()
        DepotManager._clear()
        testing.tearDown()
Exemple #12
0
    def tearDown(self) -> None:
        logger.debug(self, "TearDown Test...")

        self.session.rollback()
        self.session.close_all()
        transaction.abort()
        DeclarativeBase.metadata.drop_all(self.engine)
        sql = text("DROP TABLE IF EXISTS migrate_version;")
        self.engine.execute(sql)
        self.engine.dispose()
        DepotManager._clear()
        testing.tearDown()
Exemple #13
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']))
            # 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,
            )
Exemple #14
0
 def safe_update(self, object_to_update: DeclarativeBase) -> None:
     if not self.dry_run_mode:
         logger.debug(self, "update {}".format(str(object_to_update)))
         self.session.add(object_to_update)
     else:
         logger.debug(self,
                      "fake update of {}".format(str(object_to_update)))
         # INFO - G.M - 2019-12-16 - to cleanup current modified object from update
         # we do need to expunge it, but expunging need to ensure first object is added
         # in current session. If this is not the case expunge fail with InvalidRequestError
         # exception and message "Instance <object> is not present in this Session"
         self.session.add(object_to_update)
         self.session.expunge(object_to_update)
Exemple #15
0
    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,
                ),
            )
Exemple #16
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')
Exemple #17
0
    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)
Exemple #18
0
    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)
Exemple #19
0
    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
        )
Exemple #20
0
    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)  # nopep8
            except NoSpecialKeyFound as exc:
                log = 'Failed to create comment request due to missing specialkey in mail {}'  # nopep8
                logger.error(self, log.format(exc.__str__()))
                continue
            except EmptyEmailBody as exc:
                log = 'Empty body, skip mail'
                logger.error(self, log)
                continue
            except Exception as exc:
                log = 'Failed to create comment request in mail fetcher error {}'  # nopep8
                logger.error(self, log.format(exc.__str__()))
                continue

            try:
                self._send_request(
                    mail=mail,
                    imapc=imapc,
                    method=method,
                    endpoint=endpoint,
                    json_body_dict=json_body_dict,
                )
            except requests.exceptions.Timeout as e:
                log = 'Timeout error to transmit fetched mail to tracim : {}'
                logger.error(self, log.format(str(e)))
            except requests.exceptions.RequestException as e:
                log = 'Fail to transmit fetched mail to tracim : {}'
                logger.error(self, log.format(str(e)))
Exemple #21
0
 def ensure_user_agenda_exists(self, user: User) -> bool:
     """
     Return true if agenda already exist, false if it was just create,
     raise Exception if agenda cannot be created.
     """
     logger.debug(
         self, "check for agenda existence of user {}".format(user.user_id))
     user_agenda_url = self.get_user_agenda_url(user, use_proxy=False)
     if not self._check_agenda_exist(user_agenda_url):
         self._create_agenda(agenda_url=user_agenda_url,
                             agenda_name=user.display_name,
                             agenda_description="")
         return False
     else:
         self._update_agenda_props(agenda_url=user_agenda_url,
                                   agenda_name=user.display_name,
                                   agenda_description="")
         return True
Exemple #22
0
 def _build_email_body_for_content(
     self,
     mako_template_filepath: str,
     role: UserRoleInWorkspace,
     content_in_context: ContentInContext,
     parent_in_context: typing.Optional[ContentInContext],
     workspace_in_context: WorkspaceInContext,
     actor: User,
     translator: Translator,
 ) -> str:
     """
     Build an email body and return it as a string
     :param mako_template_filepath: the absolute path to the mako template
     to be used for email body building
     :param role: the role related to user to whom the email must be sent.
     The role is required (and not the user only) in order to show in the
      mail why the user receive the notification
     :param content_in_context: the content item related to the notification
     :param parent_in_context: parent of the content item related to the
     notification
     :param actor: the user at the origin of the action / notification
     (for example the one who wrote a comment
     :return: the built email body as string. In case of multipart email,
      this method must be called one time for text and one time for html
     """
     logger.debug(
         self, "Building email content from MAKO template {}".format(
             mako_template_filepath))
     context = self._build_context_for_content_update(
         role=role,
         content_in_context=content_in_context,
         parent_in_context=parent_in_context,
         workspace_in_context=workspace_in_context,
         actor=actor,
         translator=translator,
     )
     body_content = self._render_template(
         mako_template_filepath=mako_template_filepath,
         context=context,
         translator=translator)
     return body_content
Exemple #23
0
    def _fetch(self, imapc: imapclient.IMAPClient) -> typing.List[MessageContainer]:
        """
        Get news message from mailbox
        :return: list of new mails
        """
        messages = []

        logger.debug(self, "Fetch unflagged messages")
        uids = imapc.search(["UNFLAGGED"])
        logger.debug(self, "Found {} unflagged mails".format(len(uids)))
        for msgid, data in imapc.fetch(uids, ["BODY.PEEK[]"]).items():
            # INFO - G.M - 2017-12-08 - Fetch BODY.PEEK[]
            # Retrieve all mail(body and header) but don't set mail
            # as seen because of PEEK
            # see rfc3501
            logger.debug(self, 'Fetch mail "{}"'.format(msgid))

            try:
                msg = message_from_bytes(data[b"BODY[]"])
            except KeyError as e:
                # INFO - G.M - 12-01-2018 - Fetch may return events response
                # In some specific case, fetch command may return events
                # response unrelated to fetch request.
                # This should happen only when someone-else use the mailbox
                # at the same time of the fetcher.
                # see https://github.com/mjs/imapclient/issues/334
                except_msg = "fetch response : {}".format(str(data))
                raise BadIMAPFetchResponse(except_msg) from e

            msg_container = MessageContainer(msg, msgid)
            messages.append(msg_container)

        return messages
Exemple #24
0
 def _create_agenda(self, agenda_url, agenda_name, agenda_description):
     logger.debug(self,
                  "create a new caldav agenda at url {}".format(agenda_url))
     # INFO - G.M - 2019-05-10 - we use str as pick key as it is determinist: same
     # result between run. default method use hash which use random hash for security concern
     body = CREATE_AGENDA_TEMPLATE.format(
         agenda_name=escape(agenda_name),
         agenda_color=Color(pick_for=agenda_name, pick_key=str).get_hex(),
         agenda_description=escape(agenda_description),
     )
     try:
         response = requests.request("mkcol",
                                     agenda_url,
                                     data=body.encode("utf-8"))
     except requests.exceptions.ConnectionError as exc:
         raise AgendaServerConnectionError() from exc
     if not response.status_code == 201:
         raise CannotCreateAgenda(
             "Agenda {} cannot be created:{},{}".format(
                 agenda_url, response.status_code, response.content))
     logger.info(self,
                 "new caldav agenda created at url {}".format(agenda_url))
Exemple #25
0
    def setUp(self) -> None:
        self._set_logger()
        logger.debug(self, 'Setup Test...')
        self.settings = plaster.get_settings(self.config_uri,
                                             self.config_section)
        self.config = testing.setUp(settings=self.settings)
        self.config.include('tracim_backend.models')
        DepotManager._clear()
        DepotManager.configure(
            'test', {'depot.backend': 'depot.io.memory.MemoryFileStorage'})
        settings = self.config.get_settings()
        self.app_config = CFG(settings)
        from tracim_backend.models import (
            get_engine,
            get_session_factory,
            get_tm_session,
        )

        self.engine = get_engine(settings)
        self.session_factory = get_session_factory(self.engine)
        self.init_database()
        self.session = get_tm_session(self.session_factory,
                                      transaction.manager)
Exemple #26
0
    def setUp(self) -> None:
        self._set_logger()
        logger.debug(self, "Setup Test...")
        self.settings = plaster.get_settings(self.config_uri,
                                             self.config_section)
        self.config = testing.setUp(settings=self.settings)
        DepotManager._clear()
        DepotManager.configure(
            "test", {"depot.backend": "depot.io.memory.MemoryFileStorage"})
        settings = self.config.get_settings()
        self.app_config = CFG(settings)
        init_models(self.config, self.app_config)
        from tracim_backend.models.setup_models import (
            get_engine,
            get_session_factory,
            get_tm_session,
        )

        self.engine = get_engine(self.app_config)
        self.session_factory = get_session_factory(self.engine)
        self.init_database()
        self.session = get_tm_session(self.session_factory,
                                      transaction.manager)
Exemple #27
0
 def __init__(self, settings: typing.Dict[str, typing.Any]):
     self.settings = settings
     self.config_naming = []  # type: typing.List[ConfigParam]
     logger.debug(self, "CONFIG_PROCESS:1: load config from settings")
     self.load_config()
     logger.debug(self, "CONFIG_PROCESS:2: check validity of config given")
     self._check_consistency()
     self.check_config_validity()
     logger.debug(self, "CONFIG_PROCESS:3: do post actions")
     self.do_post_check_action()
Exemple #28
0
 def _update_agenda_props(self, agenda_url, agenda_name,
                          agenda_description):
     logger.debug(
         self,
         "update existing caldav agenda props at url {}".format(agenda_url))
     # force renaming of agenda to be sure of consistency
     try:
         caldav_client = caldav.DAVClient(username="******",
                                          password="******",
                                          url=agenda_url)
         agenda = caldav.objects.Calendar(client=caldav_client,
                                          url=agenda_url)
         props = agenda.get_properties(
             [caldav.dav.DisplayName(),
              CalendarDescription()])
         # TODO - G.M - 2019-04-11 - Rewrote this better, we need to verify
         # if value are same but as props may be None but agenda_description
         # can be '' we need to convert thing in order that '' is same as None.
         if agenda_name != str(
                 props.get(caldav.dav.DisplayName().tag)
                 or "") or agenda_description != str(
                     props.get(CalendarDescription().tag) or ""):
             agenda.set_properties([
                 caldav.dav.DisplayName(agenda_name),
                 CalendarDescription(agenda_description)
             ])
             logger.debug(
                 self,
                 "props for calendar at url {} updated".format(agenda_url))
         else:
             logger.debug(
                 self, "No props to update for calendar at url {}".format(
                     agenda_url))
     except Exception as exc:
         raise AgendaPropsUpdateFailed(
             "Failed to update props of agenda") from exc
Exemple #29
0
 def publish_messages_for_event(self, event_id: int) -> None:
     logger.debug(self,
                  "publish event(id={}) synchronously".format(event_id))
     self._publish_messages_for_event(event_id)
Exemple #30
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)

                        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) 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 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)
Exemple #31
0
    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())
Exemple #32
0
    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)

        # NOTE BS 20200428: #2829: Email no longer required for User
        if emitter.email:
            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
            )
        else:
            logger.debug(
                self,
                "Skip share notification email to emitter '{}' because have no email".format(
                    emitter.username
                ),
            )

        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
            )
Exemple #33
0
    def notify_content_update(
            self,
            event_actor_id: int,
            event_content_id: int
    ) -> None:
        """
        Look for all users to be notified about the new content and send them an
        individual email
        :param event_actor_id: id of the user that has triggered the event
        :param event_content_id: related content_id
        :return:
        """
        # FIXME - D.A. - 2014-11-05
        # Dirty import. It's here in order to avoid circular import
        from tracim_backend.lib.core.content import ContentApi
        from tracim_backend.lib.core.user import UserApi
        user = UserApi(
            None,
            config=self.config,
            session=self.session,
            ).get_one(event_actor_id)
        logger.debug(self, 'Content: {}'.format(event_content_id))
        content_api = ContentApi(
            current_user=user,
            session=self.session,
            config=self.config,
            )
        content = ContentApi(
            session=self.session,
            current_user=user,  # nopep8 TODO - use a system user instead of the user that has triggered the event
            config=self.config,
            show_archived=True,
            show_deleted=True,
        ).get_one(event_content_id, content_type_list.Any_SLUG)
        workspace_api = WorkspaceApi(
            session=self.session,
            current_user=user,
            config=self.config,
        )
        workpace_in_context = workspace_api.get_workspace_with_context(workspace_api.get_one(content.workspace_id))  # nopep8
        main_content = content.parent if content.type == content_type_list.Comment.slug else content  # nopep8
        notifiable_roles = WorkspaceApi(
            current_user=user,
            session=self.session,
            config=self.config,
        ).get_notifiable_roles(content.workspace)

        if len(notifiable_roles) <= 0:
            logger.info(self, 'Skipping notification as nobody subscribed to in workspace {}'.format(content.workspace.label))
            return


        logger.info(self, 'Generating content {} notification email for {} user(s)'.format(
            content.content_id,
            len(notifiable_roles)
        ))
        # INFO - D.A. - 2014-11-06
        # The following email sender will send emails in the async task queue
        # This allow to build all mails through current thread but really send them (including SMTP connection)
        # In the other thread.
        #
        # This way, the webserver will return sooner (actually before notification emails are sent
        email_sender = EmailSender(
            self.config,
            self._smtp_config,
            self.config.EMAIL_NOTIFICATION_ACTIVATED
        )
        for role in notifiable_roles:
            logger.info(self,
                        'Generating content {} notification email to {}'.format(
                            content.content_id,
                            role.user.email
                        )
            )
            translator = Translator(app_config=self.config, default_lang=role.user.lang)  # nopep8
            _ = translator.get_translation
            to_addr = formataddr((role.user.display_name, role.user.email))
            # INFO - G.M - 2017-11-15 - set content_id in header to permit reply
            # references can have multiple values, but only one in this case.
            replyto_addr = self.config.EMAIL_NOTIFICATION_REPLY_TO_EMAIL.replace( # nopep8
                '{content_id}', str(main_content.content_id)
            )

            reference_addr = self.config.EMAIL_NOTIFICATION_REFERENCES_EMAIL.replace( #nopep8
                '{content_id}',str(main_content.content_id)
             )
            #
            #  INFO - D.A. - 2014-11-06
            # We do not use .format() here because the subject defined in the .ini file
            # may not include all required labels. In order to avoid partial format() (which result in an exception)
            # we do use replace and force the use of .__str__() in order to process LazyString objects
            #
            content_status = translator.get_translation(main_content.get_status().label)
            translated_subject = translator.get_translation(self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_SUBJECT)
            subject = translated_subject.replace(EST.WEBSITE_TITLE, self.config.WEBSITE_TITLE.__str__())
            subject = subject.replace(EST.WORKSPACE_LABEL, main_content.workspace.label.__str__())
            subject = subject.replace(EST.CONTENT_LABEL, main_content.label.__str__())
            subject = subject.replace(EST.CONTENT_STATUS_LABEL, content_status)
            reply_to_label = _('{username} & all members of {workspace}').format(  # nopep8
                username=user.display_name,
                workspace=main_content.workspace.label)

            message = MIMEMultipart('alternative')
            message['Subject'] = subject
            message['From'] = self._get_sender(user)
            message['To'] = to_addr
            message['Reply-to'] = formataddr((reply_to_label, replyto_addr))
            # INFO - G.M - 2017-11-15
            # References can theorically have label, but in pratice, references
            # contains only message_id from parents post in thread.
            # To link this email to a content we create a virtual parent
            # in reference who contain the content_id.
            message['References'] = formataddr(('', reference_addr))
            content_in_context = content_api.get_content_in_context(content)
            parent_in_context = None
            if content.parent_id:
                parent_in_context = content_api.get_content_in_context(content.parent) # nopep8

            body_html = self._build_email_body_for_content(
                self.config.EMAIL_NOTIFICATION_CONTENT_UPDATE_TEMPLATE_HTML,
                role,
                content_in_context,
                parent_in_context,
                workpace_in_context,
                user,
                translator,
            )

            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)

            self.log_email_notification(
                msg='an email was created to {}'.format(message['To']),
                action='{:8s}'.format('CREATED'),
                email_recipient=message['To'],
                email_subject=message['Subject'],
                config=self.config,
            )

            send_email_through(
                self.config,
                email_sender.send_mail,
                message
            )