def verify_account(self, account): """Verifies a generic IMAP account by logging in and logging out. Note: Raises exceptions from connect_account() on error. Returns ------- True: If the client can successfully connect. """ conn = self.connect_account(account) info = account.provider_info if "condstore" not in info: if self._supports_condstore(conn): account.supports_condstore = True try: conn.list_folders() except Exception as e: log.error("account_folder_list_failed", email=account.email_address, account_id=account.id, error=e.message) raise UserRecoverableConfigError("Full IMAP support is not " "enabled for this account. " "Please contact your domain " "administrator and try again.") finally: conn.logout() try: # Check that SMTP settings work by establishing and closing and # SMTP session. smtp_client = SMTPClient(account) with smtp_client._get_connection(): pass except Exception as exc: log.error('Failed to establish an SMTP connection', email=account.email_address, account_id=account.id, error=exc) raise UserRecoverableConfigError("Please check that your SMTP " "settings are correct.") return True
def create_account(self, db_session, email_address, response): email_address = response.get('email') # See if the account exists in db, otherwise create it try: account = db_session.query(GmailAccount) \ .filter_by(email_address=email_address).one() except NoResultFound: namespace = Namespace() account = GmailAccount(namespace=namespace) # We only get refresh tokens on initial login (or failed credentials) # otherwise, we don't force the login screen and therefore don't get a # refresh token back from google. new_refresh_token = response.get('refresh_token') if new_refresh_token: account.refresh_token = new_refresh_token else: if not account.refresh_token or account.sync_state == 'invalid': # We got a new auth without a refresh token, so we need to back # out and force the auth flow, since we don't already have # a refresh (or the one we have doesn't work.) raise OAuthError("Missing refresh token") tok = response.get('access_token') expires_in = response.get('expires_in') token_manager.cache_token(account, tok, expires_in) account.scope = response.get('scope') account.email_address = email_address account.family_name = response.get('family_name') account.given_name = response.get('given_name') account.name = response.get('name') account.gender = response.get('gender') account.g_id = response.get('id') account.g_user_id = response.get('user_id') account.g_id_token = response.get('id_token') account.link = response.get('link') account.locale = response.get('locale') account.picture = response.get('picture') account.home_domain = response.get('hd') account.client_id = response.get('client_id') account.client_secret = response.get('client_secret') account.sync_contacts = response.get('contacts', True) account.sync_events = response.get('events', True) try: self.verify_config(account) except GmailSettingError as e: raise UserRecoverableConfigError(e) # Ensure account has sync enabled. account.enable_sync() return account
def update_account(self, account, response): account.email_address = response['email'] for attribute in [ 'name', 'imap_username', 'imap_password', 'smtp_username', 'smtp_password', 'password' ]: if response.get(attribute): setattr(account, attribute, response[attribute]) # Shim for back-compatability with legacy auth if response.get('imap_password'): # The new API sends separate IMAP/ SMTP credentials but we need to # set the legacy password attribute. # TODO[k]: Remove once column in dropped. account.password = response['imap_password'] else: # The old API does NOT send these but authentication now uses them # so update them. for attr in ('imap_username', 'smtp_username'): if attr not in response: setattr(account, attr, response['email']) for attr in ('imap_password', 'smtp_password'): if attr not in response: setattr(account, attr, response['password']) account.date = datetime.datetime.utcnow() if self.provider_name == 'custom': for attribute in ('imap_server_host', 'smtp_server_host'): old_value = getattr(account, '_{}'.format(attribute), None) new_value = response.get(attribute) if (new_value and old_value and new_value != old_value): # Before updating the domain name, check if: # 1/ they have the same parent domain # 2/ they direct to the same IP. if not matching_subdomains(new_value, old_value): raise UserRecoverableConfigError( "Updating the IMAP/SMTP servers is not permitted. Please " "verify that the server names you entered are correct. " "If your IMAP/SMTP server has in fact changed, please " "contact Nylas support to update it. More details here: " "https://support.nylas.com/hc/en-us/articles/218006767" ) # If all those conditions are met, update the address. setattr(account, '_{}'.format(attribute), new_value) account.ssl_required = response.get('ssl_required', True) # Ensure account has sync enabled after authing. account.enable_sync() return account
def create_account(self, db_session, email_address, response): email_address = response.get('email') # See if the account exists in db, otherwise create it try: account = db_session.query(GmailAccount) \ .filter_by(email_address=email_address).one() except NoResultFound: namespace = Namespace() account = GmailAccount(namespace=namespace) # We only get refresh tokens on initial login (or failed credentials) # otherwise, we don't force the login screen and therefore don't get a # refresh token back from google. new_refresh_token = response.get('refresh_token') if new_refresh_token: account.refresh_token = new_refresh_token tok = response.get('access_token') expires_in = response.get('expires_in') account.set_access_token(tok, expires_in) account.scope = response.get('scope') account.email_address = email_address account.family_name = response.get('family_name') account.given_name = response.get('given_name') account.name = response.get('name') account.gender = response.get('gender') account.g_id = response.get('id') account.g_user_id = response.get('user_id') account.g_id_token = response.get('id_token') account.link = response.get('link') account.locale = response.get('locale') account.picture = response.get('picture') account.home_domain = response.get('hd') account.client_id = response.get('client_id') account.client_secret = response.get('client_secret') try: self.verify_config(account) except GmailSettingError as e: raise UserRecoverableConfigError(e) # Hack to ensure that account syncs get restarted if they were stopped # because of e.g. invalid credentials and the user re-auths. # TODO(emfree): remove after status overhaul. if account.sync_state != 'running': account.sync_state = None return account
def create_account(self, db_session, email_address, response): # Override create_account to persist the 'login hint' email_address # rather than the canonical email that is contained in response. # This allows us to trigger errors by authing with addresses of the # format: # [email protected] # Since verify_config throws an Exception if no specific case is # triggered, this account is never committed. namespace = Namespace() account = GmailAccount(namespace=namespace) account.email_address = email_address try: self.verify_config(account) except GmailSettingError as e: print e raise UserRecoverableConfigError(e) return account
def update_account(self, account, response): account.email_address = response['email'] for attribute in [ 'name', 'imap_username', 'imap_password', 'smtp_username', 'smtp_password', 'password' ]: if response.get(attribute): setattr(account, attribute, response[attribute]) # Shim for back-compatability with legacy auth if response.get('imap_password'): # The new API sends separate IMAP/ SMTP credentials but we need to # set the legacy password attribute. # TODO[k]: Remove once column in dropped. account.password = response['imap_password'] else: # The old API does NOT send these but authentication now uses them # so update them. for attr in ('imap_username', 'smtp_username'): if attr not in response: setattr(account, attr, response['email']) for attr in ('imap_password', 'smtp_password'): if attr not in response: setattr(account, attr, response['password']) account.date = datetime.datetime.utcnow() if self.provider_name == 'custom': for attribute in ('imap_server_host', 'smtp_server_host'): value = getattr(account, '_{}'.format(attribute), None) if (response.get(attribute) and value and response[attribute] != value): raise UserRecoverableConfigError( "Updating IMAP/ SMTP endpoints is not permitted. " "Please contact Nylas support to do so.") account.ssl_required = response.get('ssl_required', True) # Ensure account has sync enabled after authing. account.enable_sync() return account
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
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) info = account.provider_info if "condstore" not in info: if self._supports_condstore(conn): account.supports_condstore = True try: conn.list_folders() except Exception as e: log.error("account_folder_list_failed", email=account.email_address, 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', email=account.email_address, 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', email=account.email_address, 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', email=account.email_address, smtp_endpoint=account.smtp_endpoint, account_id=account.id, error=exc) raise UserRecoverableConfigError("Please check that your SMTP " "settings are correct.") return True
def create_account(self, db_session, email_address, response): email_address = response.get('email') # See if the account exists in db, otherwise create it try: account = db_session.query(GmailAccount) \ .filter_by(email_address=email_address).one() except NoResultFound: namespace = Namespace() account = GmailAccount(namespace=namespace) # We only get refresh tokens on initial login (or failed credentials) # otherwise, we don't force the login screen and therefore don't get a # refresh token back from google. new_refresh_token = response.get('refresh_token') if new_refresh_token: account.refresh_token = new_refresh_token else: if (len(account.valid_auth_credentials) == 0 or account.sync_state == 'invalid'): # We got a new auth without a refresh token, so we need to back # out and force the auth flow, since we don't already have # a refresh (or the ones we have don't work.) raise OAuthError("No valid refresh tokens") account.email_address = email_address account.family_name = response.get('family_name') account.given_name = response.get('given_name') account.name = response.get('name') account.gender = response.get('gender') account.g_id = response.get('id') account.g_user_id = response.get('user_id') account.link = response.get('link') account.locale = response.get('locale') account.picture = response.get('picture') account.home_domain = response.get('hd') account.sync_contacts = (account.sync_contacts or response.get('contacts', True)) account.sync_events = (account.sync_events or response.get('events', True)) # These values are deprecated and should not be used, along # with the account's refresh_token. Access all these values # through the GmailAuthCredentials objects instead. account.client_id = response.get('client_id') account.client_secret = response.get('client_secret') account.scope = response.get('scope') account.g_id_token = response.get('id_token') # Don't need to actually save these now # tok = response.get('access_token') # expires_in = response.get('expires_in') client_id = response.get('client_id') or OAUTH_CLIENT_ID client_secret = response.get('client_secret') or OAUTH_CLIENT_SECRET if new_refresh_token: # See if we already have credentials for this client_id/secret # pair. If those don't exist, make a new GmailAuthCredentials auth_creds = next( (auth_creds for auth_creds in account.auth_credentials if (auth_creds.client_id == client_id and auth_creds.client_secret == client_secret)), GmailAuthCredentials()) auth_creds.gmailaccount = account auth_creds.scopes = response.get('scope') auth_creds.g_id_token = response.get('id_token') auth_creds.client_id = client_id auth_creds.client_secret = client_secret auth_creds.refresh_token = new_refresh_token auth_creds.is_valid = True db_session.add(auth_creds) try: self.verify_config(account) except GmailSettingError as e: raise UserRecoverableConfigError(e) # Ensure account has sync enabled. account.enable_sync() return account