Exemple #1
0
    def _open_crispin_connection(self, db_session):
        account = db_session.query(Account).get(self.account_id)
        try:
            conn = account.auth_handler.connect_account(account)
        except (IMAPClient.Error, socket.error, IMAP4.error):
            raise SearchBackendException(('Unable to connect to the IMAP '
                                          'server. Please retry in a '
                                          'couple minutes.'), 503)
        except ValidationError:
            raise SearchBackendException(("This search can't be performed "
                                          "because the account's credentials "
                                          "are out of date. Please "
                                          "reauthenticate and try again."), 403)

        try:
            acct_provider_info = provider_info(account.provider)
        except NotSupportedError:
            self.log.warn('Account provider not supported',
                          provider=account.provider)
            raise

        self.crispin_client = CrispinClient(self.account_id,
                                            acct_provider_info,
                                            account.email_address,
                                            conn,
                                            readonly=True)
Exemple #2
0
    def search_messages(self, db_session, search_query):
        account = db_session.query(Account).get(self.account_id)
        conn = account.auth_handler.connect_account(account)
        try:
            acct_provider_info = provider_info(account.provider)
        except NotSupportedError:
            self.log.warn('Account provider {} not supported.'.format(
                account.provider))
            raise

        crispin_client = CrispinClient(self.account_id,
                                       acct_provider_info,
                                       account.email_address,
                                       conn,
                                       readonly=True)
        self.log.info('Searching {} for `{}`'.format(account.email_address,
                                                     search_query))
        if ':' not in search_query:
            criteria = 'TEXT {}'.format(search_query)
        else:
            criteria = re.sub('(\w+:[ ]?)', format_key, search_query)

        all_messages = set()
        folders = db_session.query(Folder).filter(
            Folder.account_id == self.account_id).all()

        for folder in folders:
            all_messages.update(
                self.search_folder(db_session, crispin_client, folder,
                                   criteria))

        crispin_client.logout()

        return sorted(all_messages, key=lambda msg: msg.received_date)
Exemple #3
0
    def search_messages(self, db_session, search_query):
        account = db_session.query(Account).get(self.account_id)
        conn = account.auth_handler.connect_account(account)
        try:
            acct_provider_info = provider_info(account.provider)
        except NotSupportedError:
            self.log.warn('Account provider {} not supported.'
                          .format(account.provider))
            raise

        crispin_client = CrispinClient(self.account_id,
                                        acct_provider_info,
                                        account.email_address,
                                        conn,
                                        readonly=True)
        self.log.info('Searching {} for `{}`'
                      .format(account.email_address, search_query))
        if ':' not in search_query:
            criteria = 'TEXT {}'.format(search_query)
        else:
            criteria = re.sub('(\w+:[ ]?)', format_key, search_query)

        all_messages = set()
        folders = db_session.query(Folder).filter(
            Folder.account_id == self.account_id).all()

        for folder in folders:
            all_messages.update(self.search_folder(db_session,
                                                   crispin_client,
                                                   folder, criteria))

        crispin_client.logout()

        return sorted(all_messages, key=lambda msg: msg.received_date)
Exemple #4
0
 def _open_crispin_connection(self, db_session):
     account = db_session.query(Account).get(self.account_id)
     conn = account.auth_handler.connect_account(account)
     try:
         acct_provider_info = provider_info(account.provider)
     except NotSupportedError:
         self.log.warn('Account provider not supported',
                       provider=account.provider)
         raise
     self.crispin_client = CrispinClient(self.account_id,
                                         acct_provider_info,
                                         account.email_address,
                                         conn,
                                         readonly=True)
def patch_generic_client(monkeypatch, folders):
    monkeypatch.setattr(CrispinClient, '_fetch_folder_list',
                        lambda x: folders)

    conn = MockedIMAPClient(host='somehost')
    return CrispinClient(account_id=1, provider_info={},
                         email_address='*****@*****.**', conn=conn)
Exemple #6
0
def generic_client():
    conn = MockedIMAPClient(host="somehost")
    return CrispinClient(
        account_id=1,
        provider_info=None,
        email_address="*****@*****.**",
        conn=conn,
    )
Exemple #7
0
 def _open_crispin_connection(self, db_session):
     account = db_session.query(Account).get(self.account_id)
     conn = account.auth_handler.connect_account(account)
     try:
         acct_provider_info = provider_info(account.provider)
     except NotSupportedError:
         self.log.warn('Account provider not supported',
                       provider=account.provider)
         raise
     self.crispin_client = CrispinClient(self.account_id,
                                         acct_provider_info,
                                         account.email_address,
                                         conn,
                                         readonly=True)
Exemple #8
0
    def _open_crispin_connection(self, db_session):
        account = db_session.query(Account).get(self.account_id)
        try:
            conn = account.auth_handler.connect_account(account)
        except ValidationError:
            raise SearchBackendException(
                "This search can't be performed because the account's "
                "credentials are out of date. Please reauthenticate and try "
                "again.", 403)

        try:
            acct_provider_info = provider_info(account.provider)
        except NotSupportedError:
            self.log.warn('Account provider not supported',
                          provider=account.provider)
            raise

        self.crispin_client = CrispinClient(self.account_id,
                                            acct_provider_info,
                                            account.email_address,
                                            conn,
                                            readonly=True)
Exemple #9
0
    def verify_account(self, account):
        """
        Verifies a generic IMAP account by logging in and logging out to both
        the IMAP/ SMTP servers.

        Note:
        Raises exceptions from connect_account(), SMTPClient._get_connection()
        on error.

        Returns
        -------
        True: If the client can successfully connect to both.

        """
        # Verify IMAP login
        conn = self.connect_account(account)
        crispin = CrispinClient(account.id, account.provider_info,
                                account.email_address, conn)

        info = account.provider_info
        if "condstore" not in info:
            if self._supports_condstore(conn):
                account.supports_condstore = True
        try:
            conn.list_folders()
            account.folder_separator = crispin.folder_separator
            account.folder_prefix = crispin.folder_prefix
        except Exception as e:
            log.error("account_folder_list_failed",
                      account_id=account.id,
                      error=e.message)
            error_message = (
                "Full IMAP support is not enabled for this account. "
                "Please contact your domain "
                "administrator and try again.")
            raise UserRecoverableConfigError(error_message)
        finally:
            conn.logout()

        # Verify SMTP login
        try:
            # Check that SMTP settings work by establishing and closing and
            # SMTP session.
            smtp_client = SMTPClient(account)
            with smtp_client._get_connection():
                pass
        except socket.gaierror as exc:
            log.error('Failed to resolve SMTP server domain',
                      account_id=account.id,
                      error=exc)
            error_message = (
                "Couldn't resolve the SMTP server domain name. "
                "Please check that your SMTP settings are correct.")
            raise UserRecoverableConfigError(error_message)

        except socket.timeout as exc:
            log.error('TCP timeout when connecting to SMTP server',
                      account_id=account.id,
                      error=exc)

            error_message = (
                "Connection timeout when connecting to SMTP server. "
                "Please check that your SMTP settings are correct.")
            raise UserRecoverableConfigError(error_message)

        except Exception as exc:
            log.error('Failed to establish an SMTP connection',
                      smtp_endpoint=account.smtp_endpoint,
                      account_id=account.id,
                      error=exc)
            raise UserRecoverableConfigError("Please check that your SMTP "
                                             "settings are correct.")

        # Reset the sync_state to 'running' on a successful re-auth.
        # Necessary for API requests to proceed and an account modify delta to
        # be returned to delta/ streaming clients.
        # NOTE: Setting this does not restart the sync. Sync scheduling occurs
        # via the sync_should_run bit (set to True in update_account() above).
        account.sync_state = ('running'
                              if account.sync_state else account.sync_state)
        return True
Exemple #10
0
class IMAPSearchClient(object):

    def __init__(self, account):
        self.account_id = account.id
        self.log = get_logger().new(account_id=account.id,
                                    component='search')

    def _open_crispin_connection(self, db_session):
        account = db_session.query(Account).get(self.account_id)
        conn = account.auth_handler.connect_account(account)
        try:
            acct_provider_info = provider_info(account.provider)
        except NotSupportedError:
            self.log.warn('Account provider not supported',
                          provider=account.provider)
            raise
        self.crispin_client = CrispinClient(self.account_id,
                                            acct_provider_info,
                                            account.email_address,
                                            conn,
                                            readonly=True)

    def _close_crispin_connection(self):
        self.crispin_client.logout()

    def search_messages(self, db_session, search_query, offset=0, limit=40):
        self.log.info('Searching account for messages',
                      account_id=self.account_id,
                      query=search_query,
                      offset=offset,
                      limit=limit)

        imap_uids = self._search(db_session, search_query)
        query = db_session.query(Message) \
            .join(ImapUid) \
            .filter(ImapUid.account_id == self.account_id,
                    ImapUid.msg_uid.in_(imap_uids))\
            .order_by(desc(Message.received_date))\

        if offset:
            query = query.offset(offset)

        if limit:
            query = query.limit(limit)

        return query.all()

    def search_threads(self, db_session, search_query, offset=0, limit=40):
        self.log.info('Searching account for threads',
                      account_id=self.account_id,
                      query=search_query,
                      offset=offset,
                      limit=limit)

        imap_uids = self._search(db_session, search_query)
        query = db_session.query(Thread) \
            .join(Message) \
            .join(ImapUid) \
            .filter(ImapUid.account_id == self.account_id,
                    ImapUid.msg_uid.in_(imap_uids),
                    Thread.id == Message.thread_id)\
            .order_by(desc(Message.received_date))

        if offset:
            query = query.offset(offset)

        if limit:
            query = query.limit(limit)

        return query.all()

    def _search(self, db_session, search_query):
        self._open_crispin_connection(db_session)
        if ':' not in search_query:
            try:
                query = search_query.encode('ascii')
                criteria = 'TEXT {}'.format(query)
            except UnicodeEncodeError:
                criteria = u'TEXT {}'.format(search_query)
        else:
            criteria = re.sub('(\w+:[ ]?)', format_key, search_query)

        folders = db_session.query(Folder).filter(
            Folder.account_id == self.account_id).all()

        imap_uids = set()

        for folder in folders:
            imap_uids.update(self._search_folder(db_session,
                                                 folder, criteria))
        self._close_crispin_connection()
        return imap_uids

    def _search_folder(self, db_session, folder, criteria):
        self.crispin_client.select_folder(folder.name, uidvalidity_cb)
        try:
            if isinstance(criteria, unicode):
                matching_uids = self.crispin_client.conn. \
                    search(criteria=criteria, charset="UTF-8")
            else:
                matching_uids = self.crispin_client.conn. \
                    search(criteria=criteria)
        except IMAP4.error as e:
            self.log.warn('Search error', error=e)
            raise

        self.log.debug('Search found message for folder',
                       folder_name=folder.name,
                       matching_uids=len(matching_uids))

        return matching_uids
Exemple #11
0
class IMAPSearchClient(object):
    def __init__(self, account):
        self.account_id = account.id
        self.log = get_logger().new(account_id=account.id, component='search')

    def _open_crispin_connection(self, db_session):
        account = db_session.query(Account).get(self.account_id)
        conn = account.auth_handler.connect_account(account)
        try:
            acct_provider_info = provider_info(account.provider)
        except NotSupportedError:
            self.log.warn('Account provider not supported',
                          provider=account.provider)
            raise
        self.crispin_client = CrispinClient(self.account_id,
                                            acct_provider_info,
                                            account.email_address,
                                            conn,
                                            readonly=True)

    def _close_crispin_connection(self):
        self.crispin_client.logout()

    def search_messages(self, db_session, search_query, offset=0, limit=40):
        self.log.info('Searching account for messages',
                      account_id=self.account_id,
                      query=search_query,
                      offset=offset,
                      limit=limit)

        imap_uids = self._search(db_session, search_query)
        query = db_session.query(Message) \
            .join(ImapUid) \
            .filter(ImapUid.account_id == self.account_id,
                    ImapUid.msg_uid.in_(imap_uids))\
            .order_by(desc(Message.received_date))\

        if offset:
            query = query.offset(offset)

        if limit:
            query = query.limit(limit)

        return query.all()

    def search_threads(self, db_session, search_query, offset=0, limit=40):
        self.log.info('Searching account for threads',
                      account_id=self.account_id,
                      query=search_query,
                      offset=offset,
                      limit=limit)

        imap_uids = self._search(db_session, search_query)
        query = db_session.query(Thread) \
            .join(Message) \
            .join(ImapUid) \
            .filter(ImapUid.account_id == self.account_id,
                    ImapUid.msg_uid.in_(imap_uids),
                    Thread.id == Message.thread_id)\
            .order_by(desc(Message.received_date))

        if offset:
            query = query.offset(offset)

        if limit:
            query = query.limit(limit)

        return query.all()

    def _search(self, db_session, search_query):
        self._open_crispin_connection(db_session)
        if ':' not in search_query:
            try:
                query = search_query.encode('ascii')
                criteria = 'TEXT {}'.format(query)
            except UnicodeEncodeError:
                criteria = u'TEXT {}'.format(search_query)
        else:
            criteria = re.sub('(\w+:[ ]?)', format_key, search_query)

        folders = db_session.query(Folder).filter(
            Folder.account_id == self.account_id).all()

        imap_uids = set()

        for folder in folders:
            imap_uids.update(self._search_folder(db_session, folder, criteria))
        self._close_crispin_connection()
        return imap_uids

    def _search_folder(self, db_session, folder, criteria):
        self.crispin_client.select_folder(folder.name, uidvalidity_cb)
        try:
            if isinstance(criteria, unicode):
                matching_uids = self.crispin_client.conn. \
                    search(criteria=criteria, charset="UTF-8")
            else:
                matching_uids = self.crispin_client.conn. \
                    search(criteria=criteria)
        except IMAP4.error as e:
            self.log.warn('Search error', error=e)
            raise

        self.log.debug('Search found message for folder',
                       folder_name=folder.name,
                       matching_uids=len(matching_uids))

        return matching_uids
Exemple #12
0
class IMAPSearchClient(object):

    def __init__(self, account):
        self.account = account
        self.account_id = account.id
        self.log = get_logger().new(account_id=account.id,
                                    component='search')

    def _open_crispin_connection(self, db_session):
        account = db_session.query(Account).get(self.account_id)
        try:
            conn = account.auth_handler.connect_account(account)
        except (IMAPClient.Error, socket.error, IMAP4.error):
            raise SearchBackendException(('Unable to connect to the IMAP '
                                          'server. Please retry in a '
                                          'couple minutes.'), 503)
        except ValidationError:
            raise SearchBackendException(("This search can't be performed "
                                          "because the account's credentials "
                                          "are out of date. Please "
                                          "reauthenticate and try again."), 403)

        try:
            acct_provider_info = provider_info(account.provider)
        except NotSupportedError:
            self.log.warn('Account provider not supported',
                          provider=account.provider)
            raise

        self.crispin_client = CrispinClient(self.account_id,
                                            acct_provider_info,
                                            account.email_address,
                                            conn,
                                            readonly=True)

    def _close_crispin_connection(self):
        self.crispin_client.logout()

    def search_messages(self, db_session, search_query, offset=0, limit=40):
        self.log.info('Searching account for messages',
                      account_id=self.account_id,
                      query=search_query,
                      offset=offset,
                      limit=limit)

        imap_uids = []
        for uids in self._search(db_session, search_query):
            imap_uids.extend(uids)

        query = db_session.query(Message) \
            .join(ImapUid) \
            .filter(ImapUid.account_id == self.account_id,
                    ImapUid.msg_uid.in_(imap_uids))\
            .order_by(desc(Message.received_date))\

        if offset:
            query = query.offset(offset)

        if limit:
            query = query.limit(limit)

        return query.all()

    def stream_messages(self, search_query):
        def g():
            encoder = APIEncoder()

            with session_scope(self.account_id) as db_session:
                for imap_uids in self._search(db_session, search_query):
                    query = db_session.query(Message) \
                        .join(ImapUid) \
                        .filter(ImapUid.account_id == self.account_id,
                                ImapUid.msg_uid.in_(imap_uids))\
                        .order_by(desc(Message.received_date))\

                    yield encoder.cereal(query.all()) + '\n'

        return g

    def search_threads(self, db_session, search_query, offset=0, limit=40):
        self.log.info('Searching account for threads',
                      account_id=self.account_id,
                      query=search_query,
                      offset=offset,
                      limit=limit)

        imap_uids = []
        for uids in self._search(db_session, search_query):
            imap_uids.extend(uids)

        query = db_session.query(Thread) \
            .join(Message) \
            .join(ImapUid) \
            .filter(ImapUid.account_id == self.account_id,
                    ImapUid.msg_uid.in_(imap_uids),
                    Thread.id == Message.thread_id)\
            .order_by(desc(Message.received_date))

        if offset:
            query = query.offset(offset)

        if limit:
            query = query.limit(limit)

        return query.all()

    def stream_threads(self, search_query):
        def g():
            encoder = APIEncoder()

            with session_scope(self.account_id) as db_session:
                for imap_uids in self._search(db_session, search_query):
                    query = db_session.query(Thread) \
                        .join(Message) \
                        .join(ImapUid) \
                        .filter(ImapUid.account_id == self.account_id,
                                ImapUid.msg_uid.in_(imap_uids),
                                Thread.id == Message.thread_id)\
                        .order_by(desc(Message.received_date))

                    yield encoder.cereal(query.all()) + '\n'

        return g

    def _search(self, db_session, search_query):
        self._open_crispin_connection(db_session)

        try:
            criteria = ['TEXT', search_query.encode('ascii')]
            charset = None
        except UnicodeEncodeError:
            criteria = [u'TEXT', search_query]
            charset = 'UTF-8'

        folders = []

        account_folders = db_session.query(Folder).filter(
            Folder.account_id == self.account_id)

        # We want to start the search with the 'inbox', 'sent'
        # and 'archive' folders, if they exist.
        for cname in ['inbox', 'sent', 'archive']:
            special_folder = db_session.query(Folder).filter(
                Folder.account_id == self.account_id,
                Folder.canonical_name == cname).one_or_none()

            if special_folder is not None:
                folders.append(special_folder)

                # Don't search the folder twice.
                account_folders = account_folders.filter(
                    Folder.id != special_folder.id)

        folders = folders + account_folders.all()

        for folder in folders:
            yield self._search_folder(folder, criteria, charset)

        self._close_crispin_connection()

    def _search_folder(self, folder, criteria, charset):
        try:
            self.crispin_client.select_folder(folder.name, uidvalidity_cb)
        except FolderMissingError:
            self.log.warn("Won't search missing IMAP folder", exc_info=True)
            return []
        except UidInvalid:
            self.log.error(("Got Uidvalidity error when searching. "
                            "Skipping."), exc_info=True)
            return []

        try:
            uids = self.crispin_client.conn.search(criteria, charset=charset)
        except IMAP4.error:
            self.log.warn('Search error', exc_info=True)
            raise SearchBackendException(('Unknown IMAP error when '
                                          'performing search.'), 503)

        self.log.debug('Search found messages for folder',
                       folder_name=folder.name,
                       uids=len(uids))
        return uids
Exemple #13
0
class IMAPSearchClient(object):

    def __init__(self, account):
        self.account_id = account.id
        self.log = get_logger().new(account_id=account.id,
                                    component='search')

    def _open_crispin_connection(self, db_session):
        account = db_session.query(Account).get(self.account_id)
        try:
            conn = account.auth_handler.connect_account(account)
        except ValidationError:
            raise SearchBackendException(
                "This search can't be performed because the account's "
                "credentials are out of date. Please reauthenticate and try "
                "again.", 403)

        try:
            acct_provider_info = provider_info(account.provider)
        except NotSupportedError:
            self.log.warn('Account provider not supported',
                          provider=account.provider)
            raise

        self.crispin_client = CrispinClient(self.account_id,
                                            acct_provider_info,
                                            account.email_address,
                                            conn,
                                            readonly=True)

    def _close_crispin_connection(self):
        self.crispin_client.logout()

    def search_messages(self, db_session, search_query, offset=0, limit=40):
        self.log.info('Searching account for messages',
                      account_id=self.account_id,
                      query=search_query,
                      offset=offset,
                      limit=limit)

        imap_uids = self._search(db_session, search_query)
        query = db_session.query(Message) \
            .join(ImapUid) \
            .filter(ImapUid.account_id == self.account_id,
                    ImapUid.msg_uid.in_(imap_uids))\
            .order_by(desc(Message.received_date))\

        if offset:
            query = query.offset(offset)

        if limit:
            query = query.limit(limit)

        return query.all()

    def search_threads(self, db_session, search_query, offset=0, limit=40):
        self.log.info('Searching account for threads',
                      account_id=self.account_id,
                      query=search_query,
                      offset=offset,
                      limit=limit)

        imap_uids = self._search(db_session, search_query)
        query = db_session.query(Thread) \
            .join(Message) \
            .join(ImapUid) \
            .filter(ImapUid.account_id == self.account_id,
                    ImapUid.msg_uid.in_(imap_uids),
                    Thread.id == Message.thread_id)\
            .order_by(desc(Message.received_date))

        if offset:
            query = query.offset(offset)

        if limit:
            query = query.limit(limit)

        return query.all()

    def _search(self, db_session, search_query):
        self._open_crispin_connection(db_session)

        try:
            criteria = ['TEXT', search_query.encode('ascii')]
            charset = None
        except UnicodeEncodeError:
            criteria = [u'TEXT', search_query]
            charset = 'UTF-8'

        folders = db_session.query(Folder).filter(
            Folder.account_id == self.account_id).all()

        imap_uids = set()
        for folder in folders:
            imap_uids.update(self._search_folder(folder,
                                                 criteria,
                                                 charset))
        self._close_crispin_connection()
        return imap_uids

    def _search_folder(self, folder, criteria, charset):
        self.crispin_client.select_folder(folder.name, uidvalidity_cb)
        try:
            uids = self.crispin_client.conn.search(criteria, charset=charset)
        except IMAP4.error as e:
            self.log.warn('Search error', error=e)
            raise

        self.log.debug('Search found messages for folder',
                       folder_name=folder.name,
                       uids=len(uids))
        return uids