Beispiel #1
0
    def setUpClass(cls):
        # There's no official Exchange server we can test against, and we can't really provide credentials for our
        # own test server to everyone on the Internet. Travis-CI uses the encrypted settings.yml.enc for testing.
        #
        # If you want to test against your own server and account, create your own settings.yml with credentials for
        # that server. 'settings.yml.sample' is provided as a template.
        try:
            with open(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'settings.yml')) as f:
                settings = safe_load(f)
        except FileNotFoundError:
            print('Skipping %s - no settings.yml file found' % cls.__name__)
            print('Copy settings.yml.sample to settings.yml and enter values for your test server')
            raise unittest.SkipTest('Skipping %s - no settings.yml file found' % cls.__name__)

        cls.settings = settings
        cls.verify_ssl = settings.get('verify_ssl', True)
        if not cls.verify_ssl:
            # Allow unverified TLS if requested in settings file
            BaseProtocol.HTTP_ADAPTER_CLS = NoVerifyHTTPAdapter

        # Create an account shared by all tests
        tz = zoneinfo.ZoneInfo('Europe/Copenhagen')
        cls.retry_policy = FaultTolerance(max_wait=600)
        config = Configuration(
            server=settings['server'],
            credentials=Credentials(settings['username'], settings['password']),
            retry_policy=cls.retry_policy,
        )
        cls.account = Account(primary_smtp_address=settings['account'], access_type=DELEGATE, config=config,
                              locale='da_DK', default_timezone=tz)
    def setUp(self):
        super().setUp()

        # Enable retries, to make tests more robust
        Autodiscovery.INITIAL_RETRY_POLICY = FaultTolerance(max_wait=5)
        Autodiscovery.RETRY_WAIT = 5

        # Each test should start with a clean autodiscover cache
        clear_cache()

        # Some mocking helpers
        self.domain = get_domain(self.account.primary_smtp_address)
        self.dummy_ad_endpoint = 'https://%s/Autodiscover/Autodiscover.xml' % self.domain
        self.dummy_ews_endpoint = 'https://expr.example.com/EWS/Exchange.asmx'
        self.dummy_ad_response = b'''\
<?xml version="1.0" encoding="utf-8"?>
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
    <Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
        <User>
            <AutoDiscoverSMTPAddress>%s</AutoDiscoverSMTPAddress>
        </User>
        <Account>
            <AccountType>email</AccountType>
            <Action>settings</Action>
            <Protocol>
                <Type>EXPR</Type>
                <EwsUrl>%s</EwsUrl>
            </Protocol>
        </Account>
    </Response>
</Autodiscover>''' % (self.account.primary_smtp_address.encode(),
                      self.dummy_ews_endpoint.encode())
        self.dummy_ews_response = b'''\
Beispiel #3
0
    def setUpClass(cls):
        # There's no official Exchange server we can test against, and we can't really provide credentials for our
        # own test server to everyone on the Internet. Travis-CI uses the encrypted settings.yml.enc for testing.
        #
        # If you want to test against your own server and account, create your own settings.yml with credentials for
        # that server. 'settings.yml.sample' is provided as a template.
        try:
            with open(
                    os.path.join(os.path.dirname(os.path.dirname(__file__)),
                                 "settings.yml")) as f:
                settings = safe_load(f)
        except FileNotFoundError:
            print(f"Skipping {cls.__name__} - no settings.yml file found")
            print(
                "Copy settings.yml.sample to settings.yml and enter values for your test server"
            )
            raise unittest.SkipTest(
                f"Skipping {cls.__name__} - no settings.yml file found")

        cls.settings = settings
        cls.verify_ssl = settings.get("verify_ssl", True)
        if not cls.verify_ssl:
            # Allow unverified TLS if requested in settings file
            BaseProtocol.HTTP_ADAPTER_CLS = NoVerifyHTTPAdapter

        # Create an account shared by all tests
        cls.tz = zoneinfo.ZoneInfo("Europe/Copenhagen")
        cls.retry_policy = FaultTolerance(max_wait=600)
        cls.config = Configuration(
            server=settings["server"],
            credentials=Credentials(settings["username"],
                                    settings["password"]),
            retry_policy=cls.retry_policy,
        )
        cls.account = cls.get_account()
Beispiel #4
0
    def test_get_service_authtype(self, m):
        with self.assertRaises(TransportError) as e:
            _ = self.get_test_protocol(auth_type=None).auth_type
        self.assertEqual(e.exception.args[0], "XXX")

        with self.assertRaises(RateLimitError) as e:
            _ = self.get_test_protocol(
                auth_type=None,
                retry_policy=FaultTolerance(max_wait=0.5)).auth_type
        self.assertEqual(e.exception.args[0], "Max timeout reached")
Beispiel #5
0
 def test_failed_login_via_account(self):
     Autodiscovery.INITIAL_RETRY_POLICY = FaultTolerance(max_wait=10)
     clear_cache()
     with self.assertRaises(AutoDiscoverFailed):
         Account(
             primary_smtp_address=self.account.primary_smtp_address,
             access_type=DELEGATE,
             credentials=Credentials(
                 self.account.protocol.credentials.username,
                 'WRONG_PASSWORD'),
             autodiscover=True,
             locale='da_DK',
         )
Beispiel #6
0
    def test_service_account_back_off(self):
        # Test back-off logic in FaultTolerance
        sa = FaultTolerance()

        # Initially, the value is None
        self.assertIsNone(sa.back_off_until)

        # Test a non-expired back off value
        in_a_while = datetime.datetime.now() + datetime.timedelta(seconds=10)
        sa.back_off_until = in_a_while
        self.assertEqual(sa.back_off_until, in_a_while)

        # Test an expired back off value
        sa.back_off_until = datetime.datetime.now()
        time.sleep(0.001)
        self.assertIsNone(sa.back_off_until)

        # Test the back_off() helper
        sa.back_off(10)
        # This is not a precise test. Assuming fast computers, there should be less than 1 second between the two lines.
        self.assertEqual(
            int(
                math.ceil((sa.back_off_until -
                           datetime.datetime.now()).total_seconds())), 10)

        # Test expiry
        sa.back_off(0)
        time.sleep(0.001)
        self.assertIsNone(sa.back_off_until)

        # Test default value
        sa.back_off(None)
        self.assertEqual(
            int(
                math.ceil((sa.back_off_until -
                           datetime.datetime.now()).total_seconds())), 60)
    def setUp(self):
        super().setUp()

        # Enable retries, to make tests more robust
        Autodiscovery.INITIAL_RETRY_POLICY = FaultTolerance(max_wait=5)
        Autodiscovery.RETRY_WAIT = 5

        # Each test should start with a clean autodiscover cache
        clear_cache()

        # Some mocking helpers
        self.domain = get_domain(self.account.primary_smtp_address)
        self.dummy_ad_endpoint = f"https://{self.domain}/Autodiscover/Autodiscover.xml"
        self.dummy_ews_endpoint = "https://expr.example.com/EWS/Exchange.asmx"
        self.dummy_ad_response = self.settings_xml(self.account.primary_smtp_address, self.dummy_ews_endpoint)
Beispiel #8
0
    def test_error_too_many_objects_opened(self, m):
        # Test that we can parse ErrorTooManyObjectsOpened via ResponseMessage and return
        version = mock_version(build=EXCHANGE_2010)
        protocol = mock_protocol(version=version, service_endpoint='example.com')
        account = mock_account(version=version, protocol=protocol)
        ws = FindFolder(account=account, folders=[None])
        xml = b'''\
<s:Envelope
        xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Header>
        <h:ServerVersionInfo
                xmlns:h="http://schemas.microsoft.com/exchange/services/2006/types"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xmlns="http://schemas.microsoft.com/exchange/services/2006/types" MajorVersion="15" MinorVersion="0"
                MajorBuildNumber="1497" MinorBuildNumber="6" Version="V2_23"/>
    </s:Header>
    <s:Body
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <m:FindFolderResponse
                xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
                xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
            <m:ResponseMessages>
                <m:FindFolderResponseMessage ResponseClass="Error">
                    <m:MessageText>Too many concurrent connections opened.</m:MessageText>
                    <m:ResponseCode>ErrorTooManyObjectsOpened</m:ResponseCode>
                    <m:DescriptiveLinkKey>0</m:DescriptiveLinkKey>
                </m:FindFolderResponseMessage>
            </m:ResponseMessages>
        </m:FindFolderResponse>
    </s:Body>
</s:Envelope>'''
        header, body = ws._get_soap_parts(response=MockResponse(xml))
        # Just test that we can parse the error
        with self.assertRaises(ErrorTooManyObjectsOpened):
            list(ws._get_elements_in_response(response=ws._get_soap_messages(body=body)))

        # Test that it gets converted to an ErrorServerBusy exception. This happens deep inside EWSService methods
        # so it's easier to only mock the response.
        self.account.root  # Needed to get past the GetFolder request
        m.post(self.account.protocol.service_endpoint, content=xml)
        self.account.protocol.config.retry_policy = FaultTolerance(max_wait=0)
        with self.assertRaises(ErrorServerBusy) as e:
            list(FolderCollection(account=self.account, folders=[self.account.root]).find_folders())
        self.assertEqual(e.exception.back_off, None)  # ErrorTooManyObjectsOpened has no BackOffMilliseconds value
Beispiel #9
0
    def test_error_too_many_objects_opened(self, m):
        # Test that we can parse ErrorTooManyObjectsOpened via ResponseMessage and return
        version = mock_version(build=EXCHANGE_2010)
        protocol = mock_protocol(version=version,
                                 service_endpoint="example.com")
        account = mock_account(version=version, protocol=protocol)
        ws = FindFolder(account=account)
        xml = b"""\
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Body>
        <m:FindFolderResponse
                xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
                xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
            <m:ResponseMessages>
                <m:FindFolderResponseMessage ResponseClass="Error">
                    <m:MessageText>Too many concurrent connections opened.</m:MessageText>
                    <m:ResponseCode>ErrorTooManyObjectsOpened</m:ResponseCode>
                    <m:DescriptiveLinkKey>0</m:DescriptiveLinkKey>
                </m:FindFolderResponseMessage>
            </m:ResponseMessages>
        </m:FindFolderResponse>
    </s:Body>
</s:Envelope>"""
        # Just test that we can parse the error
        with self.assertRaises(ErrorTooManyObjectsOpened):
            list(ws.parse(xml))

        # Test that it gets converted to an ErrorServerBusy exception. This happens deep inside EWSService methods
        # so it's easier to only mock the response.
        self.account.root  # Needed to get past the GetFolder request
        m.post(self.account.protocol.service_endpoint, content=xml)
        orig_policy = self.account.protocol.config.retry_policy
        try:
            self.account.protocol.config.retry_policy = FaultTolerance(
                max_wait=0)
            with self.assertRaises(ErrorServerBusy) as e:
                list(
                    FolderCollection(account=self.account,
                                     folders=[self.account.root
                                              ]).find_folders())
            self.assertEqual(
                e.exception.back_off, None
            )  # ErrorTooManyObjectsOpened has no BackOffMilliseconds value
        finally:
            self.account.protocol.config.retry_policy = orig_policy
Beispiel #10
0
 def test_pickle(self):
     # Test that we can pickle various objects
     item = Message(folder=self.account.inbox, subject='XXX', categories=self.categories).save()
     attachment = FileAttachment(name='pickle_me.txt', content=b'')
     for o in (
         FaultTolerance(max_wait=3600),
         self.account.protocol,
         attachment,
         self.account.root,
         self.account.inbox,
         self.account,
         item,
     ):
         with self.subTest(o=o):
             pickled_o = pickle.dumps(o)
             unpickled_o = pickle.loads(pickled_o)
             self.assertIsInstance(unpickled_o, type(o))
             if not isinstance(o, (Account, Protocol, FaultTolerance)):
                 # __eq__ is not defined on some classes
                 self.assertEqual(o, unpickled_o)
Beispiel #11
0
    def test_post_ratelimited(self):
        url = 'https://example.com'

        protocol = self.account.protocol
        orig_policy = protocol.config.retry_policy
        RETRY_WAIT = exchangelib.util.RETRY_WAIT
        MAX_REDIRECTS = exchangelib.util.MAX_REDIRECTS

        session = protocol.get_session()
        try:
            # Make sure we fail fast in error cases
            protocol.config.retry_policy = FailFast()

            # Test the straight, HTTP 200 path
            session.post = mock_post(url, 200, {}, 'foo')
            r, session = post_ratelimited(protocol=protocol,
                                          session=session,
                                          url='http://',
                                          headers=None,
                                          data='')
            self.assertEqual(r.content, b'foo')

            # Test exceptions raises by the POST request
            for err_cls in CONNECTION_ERRORS:
                session.post = mock_session_exception(err_cls)
                with self.assertRaises(err_cls):
                    r, session = post_ratelimited(protocol=protocol,
                                                  session=session,
                                                  url='http://',
                                                  headers=None,
                                                  data='')

            # Test bad exit codes and headers
            session.post = mock_post(url, 401, {})
            with self.assertRaises(UnauthorizedError):
                r, session = post_ratelimited(protocol=protocol,
                                              session=session,
                                              url='http://',
                                              headers=None,
                                              data='')
            session.post = mock_post(url, 999, {'connection': 'close'})
            with self.assertRaises(TransportError):
                r, session = post_ratelimited(protocol=protocol,
                                              session=session,
                                              url='http://',
                                              headers=None,
                                              data='')
            session.post = mock_post(
                url, 302, {
                    'location':
                    '/ews/genericerrorpage.htm?aspxerrorpath=/ews/exchange.asmx'
                })
            with self.assertRaises(TransportError):
                r, session = post_ratelimited(protocol=protocol,
                                              session=session,
                                              url='http://',
                                              headers=None,
                                              data='')
            session.post = mock_post(url, 503, {})
            with self.assertRaises(TransportError):
                r, session = post_ratelimited(protocol=protocol,
                                              session=session,
                                              url='http://',
                                              headers=None,
                                              data='')

            # No redirect header
            session.post = mock_post(url, 302, {})
            with self.assertRaises(TransportError):
                r, session = post_ratelimited(protocol=protocol,
                                              session=session,
                                              url=url,
                                              headers=None,
                                              data='')
            # Redirect header to same location
            session.post = mock_post(url, 302, {'location': url})
            with self.assertRaises(TransportError):
                r, session = post_ratelimited(protocol=protocol,
                                              session=session,
                                              url=url,
                                              headers=None,
                                              data='')
            # Redirect header to relative location
            session.post = mock_post(url, 302, {'location': url + '/foo'})
            with self.assertRaises(RedirectError):
                r, session = post_ratelimited(protocol=protocol,
                                              session=session,
                                              url=url,
                                              headers=None,
                                              data='')
            # Redirect header to other location and allow_redirects=False
            session.post = mock_post(url, 302,
                                     {'location': 'https://contoso.com'})
            with self.assertRaises(TransportError):
                r, session = post_ratelimited(protocol=protocol,
                                              session=session,
                                              url=url,
                                              headers=None,
                                              data='')
            # Redirect header to other location and allow_redirects=True
            exchangelib.util.MAX_REDIRECTS = 0
            session.post = mock_post(url, 302,
                                     {'location': 'https://contoso.com'})
            with self.assertRaises(TransportError):
                r, session = post_ratelimited(protocol=protocol,
                                              session=session,
                                              url=url,
                                              headers=None,
                                              data='',
                                              allow_redirects=True)

            # CAS error
            session.post = mock_post(url, 999, {'X-CasErrorCode': 'AAARGH!'})
            with self.assertRaises(CASError):
                r, session = post_ratelimited(protocol=protocol,
                                              session=session,
                                              url=url,
                                              headers=None,
                                              data='')

            # Allow XML data in a non-HTTP 200 response
            session.post = mock_post(url, 500, {},
                                     '<?xml version="1.0" ?><foo></foo>')
            r, session = post_ratelimited(protocol=protocol,
                                          session=session,
                                          url=url,
                                          headers=None,
                                          data='')
            self.assertEqual(r.content, b'<?xml version="1.0" ?><foo></foo>')

            # Bad status_code and bad text
            session.post = mock_post(url, 999, {})
            with self.assertRaises(TransportError):
                r, session = post_ratelimited(protocol=protocol,
                                              session=session,
                                              url=url,
                                              headers=None,
                                              data='')

            # Test rate limit exceeded
            exchangelib.util.RETRY_WAIT = 1
            protocol.config.retry_policy = FaultTolerance(
                max_wait=0.5)  # Fail after first RETRY_WAIT
            session.post = mock_post(url, 503, {'connection': 'close'})
            # Mock renew_session to return the same session so the session object's 'post' method is still mocked
            protocol.renew_session = lambda s: s
            with self.assertRaises(RateLimitError) as rle:
                r, session = post_ratelimited(protocol=protocol,
                                              session=session,
                                              url='http://',
                                              headers=None,
                                              data='')
            self.assertEqual(rle.exception.status_code, 503)
            self.assertEqual(rle.exception.url, url)
            self.assertTrue(1 <= rle.exception.total_wait <
                            2)  # One RETRY_WAIT plus some overhead

            # Test something larger than the default wait, so we retry at least once
            protocol.retry_policy.max_wait = 3  # Fail after second RETRY_WAIT
            session.post = mock_post(url, 503, {'connection': 'close'})
            with self.assertRaises(RateLimitError) as rle:
                r, session = post_ratelimited(protocol=protocol,
                                              session=session,
                                              url='http://',
                                              headers=None,
                                              data='')
            self.assertEqual(rle.exception.status_code, 503)
            self.assertEqual(rle.exception.url, url)
            # We double the wait for each retry, so this is RETRY_WAIT + 2*RETRY_WAIT plus some overhead
            self.assertTrue(3 <= rle.exception.total_wait < 4,
                            rle.exception.total_wait)
        finally:
            protocol.retire_session(
                session)  # We have patched the session, so discard it
            # Restore patched attributes and functions
            protocol.config.retry_policy = orig_policy
            exchangelib.util.RETRY_WAIT = RETRY_WAIT
            exchangelib.util.MAX_REDIRECTS = MAX_REDIRECTS

            try:
                delattr(protocol, 'renew_session')
            except AttributeError:
                pass
Beispiel #12
0
 def setUpClass(cls):
     super(AutodiscoverTest, cls).setUpClass()
     AutodiscoverProtocol.INITIAL_RETRY_POLICY = FaultTolerance(max_wait=30)