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'''\
def test_autodiscover_path_1_5_valid_redirect_url_invalid_response(self, m): # Test steps 1 -> -> 5 -> Invalid response from redirect URL clear_cache() d = Autodiscovery(email=self.account.primary_smtp_address, credentials=self.account.protocol.credentials) redirect_url = "https://httpbin.org/Autodiscover/Autodiscover.xml" m.post(self.dummy_ad_endpoint, status_code=200, content=self.redirect_url_xml(redirect_url)) m.head(redirect_url, status_code=501) m.post(redirect_url, status_code=501) with self.assertRaises(AutoDiscoverFailed): # Fails in step 5 with invalid response ad_response, _ = d.discover()
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', )
def test_autodiscover_path_1_5_invalid_redirect_url(self, m): # Test steps 1 -> -> 5 -> Invalid redirect URL clear_cache() d = Autodiscovery(email=self.account.primary_smtp_address, credentials=self.account.protocol.credentials) m.post( self.dummy_ad_endpoint, status_code=200, content=self.redirect_url_xml(f"https://{get_random_hostname()}/EWS/Exchange.asmx"), ) with self.assertRaises(AutoDiscoverFailed): # Fails in step 5 with invalid redirect URL ad_response, _ = d.discover()
def test_autodiscover_empty_cache(self): # A live test of the entire process with an empty cache clear_cache() ad_response, protocol = exchangelib.autodiscover.discovery.discover( email=self.account.primary_smtp_address, credentials=self.account.protocol.credentials, retry_policy=self.retry_policy, ) self.assertEqual(ad_response.autodiscover_smtp_address, self.account.primary_smtp_address) self.assertEqual(protocol.service_endpoint.lower(), self.account.protocol.service_endpoint.lower()) self.assertEqual(protocol.version.build, self.account.protocol.version.build)
def test_autodiscover_direct_gc(self, m): # Test garbage collection of the autodiscover cache clear_cache() c = Credentials('leet_user', 'cannaguess') autodiscover_cache[('example.com', c)] = AutodiscoverProtocol( config=Configuration( service_endpoint= 'https://example.com/Autodiscover/Autodiscover.xml', credentials=c, auth_type=NTLM, retry_policy=FailFast(), )) self.assertEqual(len(autodiscover_cache), 1) autodiscover_cache.__del__()
def test_close_autodiscover_connections(self, m): # A live test that we can close TCP connections clear_cache() c = Credentials('leet_user', 'cannaguess') autodiscover_cache[('example.com', c)] = AutodiscoverProtocol( config=Configuration( service_endpoint= 'https://example.com/Autodiscover/Autodiscover.xml', credentials=c, auth_type=NTLM, retry_policy=FailFast(), )) self.assertEqual(len(autodiscover_cache), 1) close_connections()
def test_autodiscover_path_1_5_valid_redirect_url_valid_response(self, m): # Test steps 1 -> -> 5 -> Valid response from redirect URL -> 5 clear_cache() d = Autodiscovery(email=self.account.primary_smtp_address, credentials=self.account.protocol.credentials) redirect_hostname = "httpbin.org" redirect_url = f"https://{redirect_hostname}/Autodiscover/Autodiscover.xml" ews_url = f"https://{redirect_hostname}/EWS/Exchange.asmx" email = f"john@redirected.{redirect_hostname}" m.post(self.dummy_ad_endpoint, status_code=200, content=self.redirect_url_xml(redirect_url)) m.head(redirect_url, status_code=200) m.post(redirect_url, status_code=200, content=self.settings_xml(email, ews_url)) ad_response, _ = d.discover() self.assertEqual(ad_response.autodiscover_smtp_address, email) self.assertEqual(ad_response.protocol.ews_url, ews_url)
def test_autodiscover_path_1_2_3_invalid301_4(self, m): # Test steps 1 -> 2 -> 3 -> invalid 301 URL -> 4 clear_cache() d = Autodiscovery(email=self.account.primary_smtp_address, credentials=self.account.protocol.credentials) m.post(self.dummy_ad_endpoint, status_code=501) m.post(f"https://autodiscover.{self.domain}/Autodiscover/Autodiscover.xml", status_code=501) m.get( f"http://autodiscover.{self.domain}/Autodiscover/Autodiscover.xml", status_code=301, headers=dict(location="XXX"), ) with self.assertRaises(AutoDiscoverFailed): # Fails in step 4 with invalid SRV entry ad_response, _ = d.discover()
def test_autodiscover_path_1_2_5(self, m): # Test steps 1 -> 2 -> 5 clear_cache() d = Autodiscovery(email=self.account.primary_smtp_address, credentials=self.account.protocol.credentials) ews_url = f"https://xxx.{self.domain}/EWS/Exchange.asmx" email = f"xxxd@{self.domain}" m.post(self.dummy_ad_endpoint, status_code=501) m.post( f"https://autodiscover.{self.domain}/Autodiscover/Autodiscover.xml", status_code=200, content=self.settings_xml(email, ews_url), ) ad_response, _ = d.discover() self.assertEqual(ad_response.autodiscover_smtp_address, email) self.assertEqual(ad_response.protocol.ews_url, ews_url)
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)
def test_autodiscover_from_account(self, m): # Test that autodiscovery via account creation works clear_cache() # Mock the default endpoint that we test in step 1 of autodiscovery m.post(self.dummy_ad_endpoint, status_code=200, content=self.dummy_ad_response) # Also mock the EWS URL. We try to guess its auth method as part of autodiscovery m.post(self.dummy_ews_endpoint, status_code=200, content=self.dummy_ews_response) self.assertEqual(len(autodiscover_cache), 0) account = Account( primary_smtp_address=self.account.primary_smtp_address, config=Configuration( credentials=self.account.protocol.credentials, retry_policy=self.retry_policy, ), autodiscover=True, locale='da_DK', ) self.assertEqual(account.primary_smtp_address, self.account.primary_smtp_address) self.assertEqual(account.protocol.service_endpoint.lower(), self.dummy_ews_endpoint.lower()) # Make sure cache is full self.assertEqual(len(autodiscover_cache), 1) self.assertTrue((account.domain, self.account.protocol.credentials, True) in autodiscover_cache) # Test that autodiscover works with a full cache account = Account( primary_smtp_address=self.account.primary_smtp_address, config=Configuration( credentials=self.account.protocol.credentials, retry_policy=self.retry_policy, ), autodiscover=True, locale='da_DK', ) self.assertEqual(account.primary_smtp_address, self.account.primary_smtp_address) # Test cache manipulation key = (account.domain, self.account.protocol.credentials, True) self.assertTrue(key in autodiscover_cache) del autodiscover_cache[key] self.assertFalse(key in autodiscover_cache)
def test_magic(self, m): # Just test we don't fail when calling repr() and str(). Insert a dummy cache entry for testing clear_cache() c = Credentials('leet_user', 'cannaguess') autodiscover_cache[('example.com', c)] = AutodiscoverProtocol( config=Configuration( service_endpoint= 'https://example.com/Autodiscover/Autodiscover.xml', credentials=c, auth_type=NTLM, retry_policy=FailFast(), )) self.assertEqual(len(autodiscover_cache), 1) str(autodiscover_cache) repr(autodiscover_cache) for protocol in autodiscover_cache._protocols.values(): str(protocol) repr(protocol)
def test_autodiscover_redirect(self, m): # Test various aspects of autodiscover redirection. Mock all HTTP responses because we can't force a live server # to send us into the correct code paths. # Mock the default endpoint that we test in step 1 of autodiscovery m.post(self.dummy_ad_endpoint, status_code=200, content=self.dummy_ad_response) # Also mock the EWS URL. We try to guess its auth method as part of autodiscovery m.post(self.dummy_ews_endpoint, status_code=200) discovery = Autodiscovery( email=self.account.primary_smtp_address, credentials=self.account.protocol.credentials, retry_policy=self.retry_policy, ) discovery.discover() # Make sure we discover a different return address m.post(self.dummy_ad_endpoint, status_code=200, content=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>[email protected]</AutoDiscoverSMTPAddress> </User> <Account> <AccountType>email</AccountType> <Action>settings</Action> <Protocol> <Type>EXPR</Type> <EwsUrl>https://expr.example.com/EWS/Exchange.asmx</EwsUrl> </Protocol> </Account> </Response> </Autodiscover>''') # Also mock the EWS URL. We try to guess its auth method as part of autodiscovery m.post('https://expr.example.com/EWS/Exchange.asmx', status_code=200) ad_response, p = discovery.discover() self.assertEqual(ad_response.autodiscover_smtp_address, '*****@*****.**') # Make sure we discover an address redirect to the same domain. We have to mock the same URL with two different # responses. We do that with a response list. redirect_addr_content = 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"> <Account> <Action>redirectAddr</Action> <RedirectAddr>redirect_me@%s</RedirectAddr> </Account> </Response> </Autodiscover>''' % self.domain.encode() settings_content = 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>redirected@%s</AutoDiscoverSMTPAddress> </User> <Account> <AccountType>email</AccountType> <Action>settings</Action> <Protocol> <Type>EXPR</Type> <EwsUrl>https://redirected.%s/EWS/Exchange.asmx</EwsUrl> </Protocol> </Account> </Response> </Autodiscover>''' % (self.domain.encode(), self.domain.encode()) # Also mock the EWS URL. We try to guess its auth method as part of autodiscovery m.post('https://redirected.%s/EWS/Exchange.asmx' % self.domain, status_code=200) m.post(self.dummy_ad_endpoint, [ dict(status_code=200, content=redirect_addr_content), dict(status_code=200, content=settings_content), ]) ad_response, p = discovery.discover() self.assertEqual(ad_response.autodiscover_smtp_address, 'redirected@%s' % self.domain) self.assertEqual( ad_response.ews_url, 'https://redirected.%s/EWS/Exchange.asmx' % self.domain) # Test that we catch circular redirects on the same domain with a primed cache. Just mock the endpoint to # return the same redirect response on every request. self.assertEqual(len(autodiscover_cache), 1) m.post(self.dummy_ad_endpoint, status_code=200, content=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"> <Account> <Action>redirectAddr</Action> <RedirectAddr>foo@%s</RedirectAddr> </Account> </Response> </Autodiscover>''' % self.domain.encode()) self.assertEqual(len(autodiscover_cache), 1) with self.assertRaises(AutoDiscoverCircularRedirect): discovery.discover() # Test that we also catch circular redirects when cache is empty clear_cache() self.assertEqual(len(autodiscover_cache), 0) with self.assertRaises(AutoDiscoverCircularRedirect): discovery.discover() # Test that we can handle being asked to redirect to an address on a different domain m.post(self.dummy_ad_endpoint, status_code=200, content=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"> <Account> <Action>redirectAddr</Action> <RedirectAddr>[email protected]</RedirectAddr> </Account> </Response> </Autodiscover>''') m.post('https://example.com/Autodiscover/Autodiscover.xml', status_code=200, content=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>[email protected]</AutoDiscoverSMTPAddress> </User> <Account> <AccountType>email</AccountType> <Action>settings</Action> <Protocol> <Type>EXPR</Type> <EwsUrl>https://redirected.example.com/EWS/Exchange.asmx</EwsUrl> </Protocol> </Account> </Response> </Autodiscover>''') # Also mock the EWS URL. We try to guess its auth method as part of autodiscovery m.post('https://redirected.example.com/EWS/Exchange.asmx', status_code=200) ad_response, p = discovery.discover() self.assertEqual(ad_response.autodiscover_smtp_address, '*****@*****.**') self.assertEqual(ad_response.ews_url, 'https://redirected.example.com/EWS/Exchange.asmx')
def test_autodiscover_redirect(self, m): # Test various aspects of autodiscover redirection. Mock all HTTP responses because we can't force a live server # to send us into the correct code paths. # Mock the default endpoint that we test in step 1 of autodiscovery m.post(self.dummy_ad_endpoint, status_code=200, content=self.dummy_ad_response) discovery = Autodiscovery( email=self.account.primary_smtp_address, credentials=self.account.protocol.credentials, ) discovery.discover() # Make sure we discover a different return address m.post( self.dummy_ad_endpoint, status_code=200, content=self.settings_xml("*****@*****.**", "https://expr.example.com/EWS/Exchange.asmx"), ) ad_response, _ = discovery.discover() self.assertEqual(ad_response.autodiscover_smtp_address, "*****@*****.**") # Make sure we discover an address redirect to the same domain. We have to mock the same URL with two different # responses. We do that with a response list. m.post( self.dummy_ad_endpoint, [ dict(status_code=200, content=self.redirect_address_xml(f"redirect_me@{self.domain}")), dict( status_code=200, content=self.settings_xml( f"redirected@{self.domain}", f"https://redirected.{self.domain}/EWS/Exchange.asmx" ), ), ], ) ad_response, _ = discovery.discover() self.assertEqual(ad_response.autodiscover_smtp_address, f"redirected@{self.domain}") self.assertEqual(ad_response.protocol.ews_url, f"https://redirected.{self.domain}/EWS/Exchange.asmx") # Test that we catch circular redirects on the same domain with a primed cache. Just mock the endpoint to # return the same redirect response on every request. self.assertEqual(len(autodiscover_cache), 1) m.post(self.dummy_ad_endpoint, status_code=200, content=self.redirect_address_xml(f"foo@{self.domain}")) self.assertEqual(len(autodiscover_cache), 1) with self.assertRaises(AutoDiscoverCircularRedirect): discovery.discover() # Test that we also catch circular redirects when cache is empty clear_cache() self.assertEqual(len(autodiscover_cache), 0) with self.assertRaises(AutoDiscoverCircularRedirect): discovery.discover() # Test that we can handle being asked to redirect to an address on a different domain # Don't use example.com to redirect - it does not resolve or answer on all ISPs ews_hostname = "httpbin.org" redirect_email = f"john@redirected.{ews_hostname}" ews_url = f"https://{ews_hostname}/EWS/Exchange.asmx" m.post(self.dummy_ad_endpoint, status_code=200, content=self.redirect_address_xml(f"john@{ews_hostname}")) m.post( f"https://{ews_hostname}/Autodiscover/Autodiscover.xml", status_code=200, content=self.settings_xml(redirect_email, ews_url), ) ad_response, _ = discovery.discover() self.assertEqual(ad_response.autodiscover_smtp_address, redirect_email) self.assertEqual(ad_response.protocol.ews_url, ews_url) # Test redirect via HTTP 301 clear_cache() redirect_url = f"https://{ews_hostname}/OtherPath/Autodiscover.xml" redirect_email = f"john@otherpath.{ews_hostname}" ews_url = f"https://xxx.{ews_hostname}/EWS/Exchange.asmx" discovery.email = self.account.primary_smtp_address m.post(self.dummy_ad_endpoint, status_code=301, headers=dict(location=redirect_url)) m.post(redirect_url, status_code=200, content=self.settings_xml(redirect_email, ews_url)) m.head(redirect_url, status_code=200) ad_response, _ = discovery.discover() self.assertEqual(ad_response.autodiscover_smtp_address, redirect_email) self.assertEqual(ad_response.protocol.ews_url, ews_url)