def test_protocol_default_values(self): # Test that retry_policy and auth_type always get a value regardless of how we create an Account c = Credentials(self.settings["username"], self.settings["password"]) a = Account( self.account.primary_smtp_address, autodiscover=False, config=Configuration( server=self.settings["server"], credentials=c, ), ) self.assertIsNotNone(a.protocol.auth_type) self.assertIsNotNone(a.protocol.retry_policy) a = Account( self.account.primary_smtp_address, autodiscover=True, config=Configuration( server=self.settings["server"], credentials=c, ), ) self.assertIsNotNone(a.protocol.auth_type) self.assertIsNotNone(a.protocol.retry_policy) a = Account(self.account.primary_smtp_address, autodiscover=True, credentials=c) self.assertIsNotNone(a.protocol.auth_type) self.assertIsNotNone(a.protocol.retry_policy)
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 get_account(cls): return Account( primary_smtp_address=cls.settings["account"], access_type=DELEGATE, config=cls.config, locale="da_DK", default_timezone=cls.tz, )
def test_autodiscover_from_account(self, m): # Test that autodiscovery via account creation works # 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_failed_login_via_account(self): 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_validation(self): with self.assertRaises(ValueError) as e: # Must have valid email address Account(primary_smtp_address='blah') self.assertEqual(str(e.exception), "primary_smtp_address 'blah' is not an email address") with self.assertRaises(AttributeError) as e: # Non-autodiscover requires a config Account(primary_smtp_address='*****@*****.**', autodiscover=False) self.assertEqual(str(e.exception), 'non-autodiscover requires a config') with self.assertRaises(ValueError) as e: # access type must be one of ACCESS_TYPES Account(primary_smtp_address='*****@*****.**', access_type=123) self.assertEqual(str(e.exception), "'access_type' 123 must be one of ('impersonation', 'delegate')") with self.assertRaises(ValueError) as e: # locale must be a string Account(primary_smtp_address='*****@*****.**', locale=123) self.assertEqual(str(e.exception), "Expected 'locale' to be a string, got 123") with self.assertRaises(ValueError) as e: # default timezone must be an EWSTimeZone Account(primary_smtp_address='*****@*****.**', default_timezone=123) self.assertEqual(str(e.exception), "Expected 'default_timezone' to be an EWSTimeZone, got 123") with self.assertRaises(ValueError) as e: # config must be a Configuration Account(primary_smtp_address='*****@*****.**', config=123) self.assertEqual(str(e.exception), "Expected 'config' to be a Configuration, got 123")
def test_validation(self): with self.assertRaises(ValueError) as e: # Must have valid email address Account(primary_smtp_address="blah") self.assertEqual(str(e.exception), "primary_smtp_address 'blah' is not an email address") with self.assertRaises(AttributeError) as e: # Non-autodiscover requires a config Account(primary_smtp_address="*****@*****.**", autodiscover=False) self.assertEqual(str(e.exception), "non-autodiscover requires a config") with self.assertRaises(ValueError) as e: Account(primary_smtp_address="*****@*****.**", access_type=123) self.assertEqual(str(e.exception), "'access_type' 123 must be one of ['delegate', 'impersonation']") with self.assertRaises(TypeError) as e: # locale must be a string Account(primary_smtp_address="*****@*****.**", locale=123) self.assertEqual(str(e.exception), "'locale' 123 must be of type <class 'str'>") with self.assertRaises(TypeError) as e: # default timezone must be an EWSTimeZone Account(primary_smtp_address="*****@*****.**", default_timezone=123) self.assertEqual( str(e.exception), "'default_timezone' 123 must be of type <class 'exchangelib.ewsdatetime.EWSTimeZone'>" ) with self.assertRaises(TypeError) as e: # config must be a Configuration Account(primary_smtp_address="*****@*****.**", config=123) self.assertEqual( str(e.exception), "'config' 123 must be of type <class 'exchangelib.configuration.Configuration'>" )
def test_tzlocal_failure(self, m): a = Account( primary_smtp_address=self.account.primary_smtp_address, access_type=DELEGATE, config=Configuration( service_endpoint=self.account.protocol.service_endpoint, credentials=Credentials(self.account.protocol.credentials.username, "WRONG_PASSWORD"), version=self.account.version, auth_type=self.account.protocol.auth_type, retry_policy=self.retry_policy, ), autodiscover=False, ) self.assertEqual(a.default_timezone, UTC)
def test_login_failure_and_credentials_update(self): # Create an account that does not need to create any connections account = Account( primary_smtp_address=self.account.primary_smtp_address, access_type=DELEGATE, config=Configuration( service_endpoint=self.account.protocol.service_endpoint, credentials=Credentials(self.account.protocol.credentials.username, 'WRONG_PASSWORD'), version=self.account.version, auth_type=self.account.protocol.auth_type, retry_policy=self.retry_policy, ), autodiscover=False, locale='da_DK', ) # Should fail when credentials are wrong, but UnauthorizedError is caught and retried. Mock the needed methods import exchangelib.util _orig1 = exchangelib.util._may_retry_on_error _orig2 = exchangelib.util._raise_response_errors def _mock1(response, retry_policy, wait): if response.status_code == 401: return False return _orig1(response, retry_policy, wait) def _mock2(response, protocol): if response.status_code == 401: raise UnauthorizedError('Invalid credentials for %s' % response.url) return _orig2(response, protocol) exchangelib.util._may_retry_on_error = _mock1 exchangelib.util._raise_response_errors = _mock2 try: with self.assertRaises(UnauthorizedError): account.root.refresh() finally: exchangelib.util._may_retry_on_error = _orig1 exchangelib.util._raise_response_errors = _orig2 # Cannot update from Configuration object with self.assertRaises(AttributeError): account.protocol.config.credentials = self.account.protocol.credentials # Should succeed after credentials update account.protocol.credentials = self.account.protocol.credentials account.root.refresh()
def test_login_failure_and_credentials_update(self): # Create an account that does not need to create any connections account = Account( primary_smtp_address=self.account.primary_smtp_address, access_type=DELEGATE, config=Configuration( service_endpoint=self.account.protocol.service_endpoint, credentials=Credentials( self.account.protocol.credentials.username, 'WRONG_PASSWORD'), version=self.account.version, auth_type=self.account.protocol.auth_type, retry_policy=self.retry_policy, ), autodiscover=False, locale='da_DK', ) # Should fail when credentials are wrong, but UnauthorizedError is caught and retried. Mock the needed methods class Mock1(FaultTolerance): def may_retry_on_error(self, response, wait): if response.status_code == 401: return False return super().may_retry_on_error(response, wait) def raise_response_errors(self, response): if response.status_code == 401: raise UnauthorizedError('Invalid credentials for %s' % response.url) return super().raise_response_errors(response) try: account.protocol.config.retry_policy = Mock1() with self.assertRaises(UnauthorizedError): account.root.refresh() finally: account.protocol.config.retry_policy = self.retry_policy # Cannot update from Configuration object with self.assertRaises(AttributeError): account.protocol.config.credentials = self.account.protocol.credentials # Should succeed after credentials update account.protocol.credentials = self.account.protocol.credentials account.root.refresh()
def initEventStorage(): # Open sqlite database, up to now it is used to store calendarNames / Outlook categories and Mattermost groups in which calendar events should be posted. # The calendar events are stored in an Outlook calendar and also in the sqlite database up to now. try: conn = sqlite3.connect(calendarSettings['DatabaseName']) cursor = conn.cursor() except Exception as e: print( "Could not open sqlite database due to following exception:\n {0} \n {1}" .format(e.__doc__, e.message)) # Open outlook calendar try: config = Configuration(username=outlookSettings['Username'], password=outlookSettings['Password']) account = Account(primary_smtp_address=outlookSettings['Email'], config=config, autodiscover=True, access_type=DELEGATE) except Exception as e: print( "Could not open Outlook calendar due to following exception:\n {0} \n {1}" .format(e.__doc__, e.message)) # Set timezone needed later, note: only Copenhagen is mapped but it is the same timezone as Berlin tz = EWSTimeZone.timezone('Europe/Copenhagen') # Create table which contains subcalendars # calendarName: Name which is refered to add an event, calendarMattermostGroup: group in which events should be published # calendarName is rewritten in an Outlook calendar category to be stored in Outlook calendar try: createCalendarTable = "CREATE TABLE IF NOT EXISTS 'calendarTable' (id INTEGER PRIMARY KEY AUTOINCREMENT, calendarName TEXT, calendarMattermostGroup TEXT)" conn.execute(createCalendarTable) except Exception as e: print( "Could not access / create sqlite calendar due to following exception:\n {0} \n {1}" .format(e.__doc__, e.message)) return conn, cursor, tz, account
def main(): parser = argparse.ArgumentParser(parents=[tools.argparser]) parser.add_argument( '--date', help= 'What date to start looking at the calendar? Use format YYYY-MM-DD.') parser.add_argument( '--look_ahead_days', help='How many days to look ahead from the starting date?') parser.add_argument('--name', help='Which person are you?') parser.add_argument('--google_calendar', action='store_true') parser.add_argument('--outlook_calendar', action='store_true') parser.add_argument( '--spreadsheet_id', help='The ID of the ECBU Luminate Support Weekly Schedule spreadsheet', default='1RgDgDRcyAFDdkEyRH7m_4QOtJ7e-kv324hEWE4JuwgI') parser.add_argument( '--exchange_username', help= 'The username you use in Outlook, should be [email protected]' ) parser.add_argument( '--primary_smtp_address', help= 'Your Outlook email address, should be [email protected]' ) parser.add_argument('--exchange_password', help='The password you use in Outlook') flags = parser.parse_args() print("Running with args: " + str(sys.argv)) if not flags.google_calendar or flags.outlook_calendar: print( "You need to specify --google_calendar and/or --outlook_calendar") return today = arrow.get(flags.date, 'YYYY-MM-DD') dates = [ today.replace(days=+n) for n in range(0, int(flags.look_ahead_days)) ] credentials = get_credentials(flags) http = credentials.authorize(httplib2.Http()) sheets_service = discovery.build('sheets', 'v4', http=http) google_calendar_service = None if flags.google_calendar: google_calendar_service = discovery.build('calendar', 'v3', http=http) exchange_account = None if flags.outlook_calendar: exchange_credentials = Credentials(username=flags.exchange_username, password=flags.exchange_password) exchange_account = Account( primary_smtp_address=flags.primary_smtp_address, credentials=exchange_credentials, autodiscover=True, access_type=DELEGATE) for date in dates: row = row_for_name(sheets_service, flags.spreadsheet_id, flags.name, date) if not row: print( "Could not find row for {name} on {date}, will skip to next day" .format(name=flags.name, date=date)) continue midnight = arrow.Arrow(date.year, date.month, date.day, tzinfo='America/Chicago') appointments = appointments_from_google_sheet(sheets_service, flags.spreadsheet_id, row, midnight) if google_calendar_service: events_made = create_google_calendar_events( appointments, google_calendar_service) if events_made == 0: print("No shifts found for {0}".format(date)) if exchange_account: events_made = create_outlook_calendar_events( appointments, exchange_account) if events_made == 0: print("No shifts found for {0}".format(date))
def main(): parser = argparse.ArgumentParser(parents=[tools.argparser]) parser.add_argument( '--date', help= 'What date to start looking at the calendar? Use format YYYY-MM-DD.') parser.add_argument( '--look_ahead_days', help='How many days to look ahead from the starting date?') parser.add_argument('--first_name', help='Name of first person on lunch date') parser.add_argument('--second_name', help='Name of second person on lunch date') parser.add_argument('--google_calendar', action='store_true') parser.add_argument('--outlook_calendar', action='store_true') parser.add_argument( '--spreadsheet_id', help='The ID of the ECBU Luminate Support Weekly Schedule spreadsheet', default='1RgDgDRcyAFDdkEyRH7m_4QOtJ7e-kv324hEWE4JuwgI') parser.add_argument( '--exchange_username', help= 'The username you use in Outlook, should be [email protected]' ) parser.add_argument( '--primary_smtp_address', help= 'Your Outlook email address, should be [email protected]' ) parser.add_argument('--exchange_password', help='The password you use in Outlook') flags = parser.parse_args() print("Running with args: " + str(sys.argv)) if not flags.google_calendar or flags.outlook_calendar: print( "You need to specify --google_calendar and/or --outlook_calendar") return today = arrow.get(flags.date, 'YYYY-MM-DD') dates = [ today.replace(days=+n) for n in range(0, int(flags.look_ahead_days)) ] credentials = get_credentials(flags) http = credentials.authorize(httplib2.Http()) sheets_service = discovery.build('sheets', 'v4', http=http) google_calendar_service = None if flags.google_calendar: google_calendar_service = discovery.build('calendar', 'v3', http=http) exchange_account = None if flags.outlook_calendar: exchange_credentials = Credentials(username=flags.exchange_username, password=flags.exchange_password) exchange_account = Account( primary_smtp_address=flags.primary_smtp_address, credentials=exchange_credentials, autodiscover=True, access_type=DELEGATE) for date in dates: midnight = arrow.Arrow(date.year, date.month, date.day, tzinfo='America/Chicago') first_row = row_for_name(sheets_service, flags.spreadsheet_id, flags.first_name, date) if not first_row: print( "Could not find row for {name} on {date}, will skip to next day" .format(name=flags.first_name, date=date)) continue second_row = row_for_name(sheets_service, flags.spreadsheet_id, flags.second_name, date) if not second_row: print( "Could not find row for {name} on {date}, will skip to next day" .format(name=flags.second_name, date=date)) continue first_appointments = appointments_from_google_sheet( sheets_service, flags.spreadsheet_id, first_row, midnight) second_appointments = appointments_from_google_sheet( sheets_service, flags.spreadsheet_id, second_row, midnight) if date.weekday() in [5, 6]: # skip weekends continue # if no appointments are found, don't try to schedule lunch if not first_appointments or not second_appointments: print("No schedule yet defined for {0}".format(date)) continue lunch_ranges = [Range(LUNCH_EARLIEST, LUNCH_LATEST)] for appointment in first_appointments + second_appointments: if appointment.appointment_type in ['F', 'C', 'PTO']: new_lunch_ranges = [] appointment_range = Range( fractional_hour(appointment.start_time), fractional_hour(appointment.end_time)) for lunch_range in lunch_ranges: new_lunch_ranges.extend( lunch_range.subtract(appointment_range)) lunch_ranges = new_lunch_ranges # cut out ranges that are too short lunch_ranges = [r for r in lunch_ranges if r.length() >= 1.5] lunch_appointments = [ Appointment( midnight.replace( hour=hour_minute_from_fractional_hour(r.start)[0], minute=hour_minute_from_fractional_hour(r.start)[1]), midnight.replace( hour=hour_minute_from_fractional_hour(r.end)[0], minute=hour_minute_from_fractional_hour(r.end)[1]), LUNCH) for r in lunch_ranges ] if google_calendar_service: events_made = create_google_calendar_events( lunch_appointments, google_calendar_service) if events_made == 0: print("No shifts found for {0}".format(date)) if exchange_account: events_made = create_outlook_calendar_events( lunch_appointments, exchange_account) if events_made == 0: print("No shifts found for {0}".format(date))
def library(): lib = Exchange() lib.account = Account(primary_smtp_address="*****@*****.**") return lib