def _user_info(provider_module, access_token): """ retrieves additional information about the user to store in the db""" log.info('fetching_user_info') try: user_info_url = provider_module.OAUTH_USER_INFO_URL args = {'access_token': access_token} response = requests.get(user_info_url + "?" + urllib.urlencode(args)) except RequestsConnectionError as e: log.error('user_info_fetch_failed', error=e) raise ConnectionError() except AttributeError: log.error('Provider module must have OAUTH_USER_INFO_URL.') raise userinfo_dict = response.json() if 'error' in userinfo_dict: assert userinfo_dict['error'] == 'invalid_token' log.error('user_info_fetch_failed', error=userinfo_dict['error'], error_description=userinfo_dict['error_description']) log.error('%s - %s' % (userinfo_dict['error'], userinfo_dict['error_description'])) raise OAuthValidationError() return userinfo_dict
def validate_token(provider_module, access_token): """ Helper function which will validate an access token. Returns ------- validation_dict if connecting and validation succeeds Raises ------ ConnectionError When unable to connect to oauth host OAuthErrro When authorization fails """ try: validation_url = provider_module.OAUTH_TOKEN_VALIDATION_URL except AttributeError: return _user_info(provider_module, access_token) try: response = requests.get(validation_url + '?access_token=' + access_token) except RequestsConnectionError, e: log.error('access token validation failed', error=e) raise ConnectionError()
def new_token(self, refresh_token, client_id=None, client_secret=None): if not refresh_token: raise OAuthError('refresh_token required') # If these aren't set on the Account object, use the values from # config so that the dev version of the sync engine continues to work. client_id = client_id or self.OAUTH_CLIENT_ID client_secret = client_secret or self.OAUTH_CLIENT_SECRET access_token_url = self.OAUTH_ACCESS_TOKEN_URL args = { 'refresh_token': refresh_token, 'client_id': client_id, 'client_secret': client_secret, 'grant_type': 'refresh_token' } try: headers = { 'Content-type': 'application/x-www-form-urlencoded', 'Accept': 'text/plain' } data = urllib.urlencode(args) response = requests.post(access_token_url, data=data, headers=headers) except (requests.exceptions.HTTPError, RequestsConnectionError), e: log.error(e) raise ConnectionError()
def new_token(self, refresh_token, client_id=None, client_secret=None): if refresh_token in self.connection_error_tokens: raise ConnectionError("Invalid connection!") if refresh_token in self.revoked_refresh_tokens: raise OAuthError("Invalid token") expires_in = 10000 return ACCESS_TOKEN, expires_in
def _new_access_token_from_authalligator(self, account, force_refresh): """ Return the access token based on an account created in AuthAlligator. """ assert account.secret.type == SecretType.AuthAlligator.value assert self.AUTHALLIGATOR_AUTH_KEY assert self.AUTHALLIGATOR_SERVICE_URL aa_client = AuthAlligatorApiClient( token=self.AUTHALLIGATOR_AUTH_KEY, service_url=self.AUTHALLIGATOR_SERVICE_URL, ) aa_data = json.loads(account.secret.secret) provider = ProviderType(aa_data["provider"]) username = aa_data["username"] account_key = aa_data["account_key"] try: if force_refresh: aa_response = aa_client.verify_account( provider=provider, username=username, account_key=account_key, ) aa_account = aa_response.account else: aa_response = aa_client.query_account( provider=provider, username=username, account_key=account_key, ) aa_account = aa_response except AccountError as exc: log.warn( "AccountError during AuthAlligator account query", account_id=account.id, error_code=exc.code and exc.code.value, error_message=exc.message, retry_in=exc.retry_in, ) if exc.code in ( AccountErrorCode.AUTHORIZATION_ERROR, AccountErrorCode.CONFIGURATION_ERROR, AccountErrorCode.DOES_NOT_EXIST, ): raise OAuthError( "Could not obtain access token from AuthAlligator") else: raise ConnectionError( "Temporary error while obtaining access token from AuthAlligator" ) else: now = datetime.datetime.now(pytz.UTC) expires_in = int( (aa_account.access_token_expires_at - now).total_seconds()) assert expires_in > 0 return (aa_account.access_token, expires_in)
def new_token(self, refresh_token, client_id=None, client_secret=None): if not refresh_token: raise OAuthError('refresh_token required') # If these aren't set on the Account object, use the values from # config so that the dev version of the sync engine continues to work. client_id = client_id or self.OAUTH_CLIENT_ID client_secret = client_secret or self.OAUTH_CLIENT_SECRET access_token_url = self.OAUTH_ACCESS_TOKEN_URL data = urllib.urlencode({ 'refresh_token': refresh_token, 'client_id': client_id, 'client_secret': client_secret, 'grant_type': 'refresh_token' }) headers = {'Content-type': 'application/x-www-form-urlencoded', 'Accept': 'text/plain'} try: response = requests.post(access_token_url, data=data, headers=headers) except requests.exceptions.ConnectionError as e: log.error('Network error renewing access token', error=e) raise ConnectionError() try: session_dict = response.json() except JSONDecodeError: log.error('Invalid JSON renewing on renewing token', response=response.text) raise ConnectionError('Invalid JSON response on renewing token') if 'error' in session_dict: if session_dict['error'] == 'invalid_grant': # This is raised if the user has revoked access to the # application (or if the refresh token is otherwise invalid). raise OAuthError('invalid_grant') else: # You can also get e.g. {"error": "internal_failure"} log.error('Error renewing access token', session_dict=session_dict) raise ConnectionError('Server error renewing access token') return session_dict['access_token'], session_dict['expires_in']
def _get_user_info(self, session_dict): access_token = session_dict["access_token"] request = urllib.request.Request( self.OAUTH_USER_INFO_URL, headers={"Authorization": "Bearer {}".format(access_token)}, ) try: response = urllib.request.urlopen(request) except urllib.error.HTTPError as e: if e.code == 401: raise OAuthError("Could not retrieve user info.") log.error("user_info_fetch_failed", error_code=e.code, error=e) raise ConnectionError() except urllib.error.URLError as e: log.error("user_info_fetch_failed", error=e) raise ConnectionError() userinfo_dict = json.loads(response.read()) return {"email": userinfo_dict["EmailAddress"]}
def try_fill_config_data(email_address, password): response = {} subdomains = ['mail.', 'imap.', 'smtp.', ''] domain = email_address.split('@')[1].lower() smtp_port = 587 imap_port = 993 log = get_logger() for imap_host in [subdomain + domain for subdomain in subdomains]: try: create_imap_connection(imap_host, imap_port, True, use_timeout=10) response['imap_server_host'] = imap_host response['imap_server_port'] = imap_port break except SSLNotSupportedError: create_imap_connection(imap_host, imap_port, False, use_timeout=10) response['imap_server_host'] = imap_host response['imap_server_port'] = imap_port response['ssl_required'] = False break except (IMAPClient.Error, socket.error): continue if 'imap_server_host' not in response: raise ConnectionError( 'IMAP connection failed. Check your password. ' 'If error persists try provide connection details') for smtp_host in [subdomain + domain for subdomain in subdomains]: try: with SMTPConnection(0, email_address, email_address, 'password', password, (smtp_host, smtp_port), True, log): response['smtp_server_host'] = smtp_host response['smtp_server_port'] = smtp_port break except: continue if 'smtp_server_host' not in response: raise ConnectionError( 'SMTP connection failed. Check your password. ' 'If error persists try provide connection details') return response
def _get_user_info(self, access_token): try: response = requests.get(self.OAUTH_USER_INFO_URL, params={'access_token': access_token}) except requests.exceptions.ConnectionError as e: log.error('user_info_fetch_failed', error=e) raise ConnectionError() userinfo_dict = response.json() if 'error' in userinfo_dict: assert userinfo_dict['error'] == 'invalid_token' log.error('user_info_fetch_failed', error=userinfo_dict['error'], error_description=userinfo_dict['error_description']) log.error('%s - %s' % (userinfo_dict['error'], userinfo_dict['error_description'])) raise OAuthError() return userinfo_dict
'Content-type': 'application/x-www-form-urlencoded', 'Accept': 'text/plain' } data = urllib.urlencode(args) response = requests.post(access_token_url, data=data, headers=headers) except (requests.exceptions.HTTPError, RequestsConnectionError), e: log.error(e) raise ConnectionError() try: session_dict = response.json() except JSONDecodeError: log.error("Couldn't convert response to json.", status_code=response.status_code, response=response.text) raise ConnectionError() if u'error' in session_dict: if session_dict['error'] == 'invalid_grant': log.error('refresh_token_invalid', client_id=client_id, provider=provider_module.PROVIDER) raise OAuthInvalidGrantError('Could not get new token') else: log.error('oauth_error', client_id=client_id, provider=provider_module.PROVIDER) raise OAuthError(session_dict['error']) return session_dict['access_token'], session_dict['expires_in']
class OAuthAuthHandler(AuthHandler): def connect_account(self, email, pw, imap_endpoint, account_id=None): """Provide a connection to a IMAP account. Raises ------ socket.error If we cannot connect to the IMAP host. IMAPClient.error If the credentials are invalid. """ host, port = imap_endpoint try: conn = IMAPClient(host, port=port, use_uid=True, ssl=True) except IMAPClient.AbortError as e: log.error('account_connect_failed', account_id=account_id, email=email, host=host, port=port, error="[ALERT] Can't connect to host - may be transient") raise TransientConnectionError(str(e)) except (IMAPClient.Error, gaierror, socket_error) as e: log.error('account_connect_failed', account_id=account_id, email=email, host=host, port=port, error='[ALERT] (Failure): {0}'.format(str(e))) raise ConnectionError(str(e)) conn.debug = False try: conn.oauth2_login(email, pw) except IMAPClient.AbortError as e: log.error('account_verify_failed', account_id=account_id, email=email, host=host, port=port, error="[ALERT] Can't connect to host - may be transient") raise TransientConnectionError(str(e)) except IMAPClient.Error as e: log.error('IMAP Login error during connection. ' 'Account: {}, error: {}'.format(email, e), account_id=account_id) if (str(e) == '[ALERT] Invalid credentials (Failure)' or str(e).startswith('[AUTHENTICATIONFAILED]')): raise ValidationError(str(e)) else: raise ConnectionError(str(e)) except SSLError as e: log.error('account_verify_failed', account_id=account_id, email=email, host=host, port=port, error='[ALERT] (Failure) SSL Connection error') raise ConnectionError(str(e)) return conn def verify_account(self, account): """Verifies a IMAP account by logging in.""" try: access_token = token_manager.get_token(account) conn = self.connect_account(account.email_address, access_token, account.imap_endpoint, account.id) conn.logout() except ValidationError: # Access token could've expired, refresh and try again. access_token = token_manager.get_token(account, force_refresh=True) conn = self.connect_account(account.email_address, access_token, account.imap_endpoint, account.id) conn.logout() return True def validate_token(self, access_token): """Implemented by subclasses.""" raise NotImplementedError def new_token(self, refresh_token, client_id=None, client_secret=None): if not refresh_token: raise OAuthError('refresh_token required') # If these aren't set on the Account object, use the values from # config so that the dev version of the sync engine continues to work. client_id = client_id or self.OAUTH_CLIENT_ID client_secret = client_secret or self.OAUTH_CLIENT_SECRET access_token_url = self.OAUTH_ACCESS_TOKEN_URL args = { 'refresh_token': refresh_token, 'client_id': client_id, 'client_secret': client_secret, 'grant_type': 'refresh_token' } try: headers = { 'Content-type': 'application/x-www-form-urlencoded', 'Accept': 'text/plain' } data = urllib.urlencode(args) response = requests.post(access_token_url, data=data, headers=headers) except (requests.exceptions.HTTPError, requests.exceptions.ConnectionError), e: log.error(e) raise ConnectionError() try: session_dict = response.json() except JSONDecodeError: raise ConnectionError("Invalid json: " + response.text) if u'error' in session_dict: raise OAuthError(session_dict['error']) return session_dict['access_token'], session_dict['expires_in']
def connect_account(self, email, credential, imap_endpoint, account_id=None): """Provide a connection to a generic IMAP account. Raises ------ ConnectionError If we cannot connect to the IMAP host. TransientConnectionError Sometimes the server bails out on us. Retrying may fix things. ValidationError If the credentials are invalid. """ host, port = imap_endpoint try: conn = IMAPClient(host, port=port, use_uid=True, ssl=True) except IMAPClient.AbortError as e: log.error('account_connect_failed', account_id=account_id, email=email, host=host, port=port, error="[ALERT] Can't connect to host - may be transient") raise TransientConnectionError(str(e)) except (IMAPClient.Error, gaierror, socket_error) as e: log.error('account_connect_failed', account_id=account_id, email=email, host=host, port=port, error='[ALERT] (Failure): {0}'.format(str(e))) raise ConnectionError(str(e)) conn.debug = False try: conn.login(email, credential) except IMAPClient.AbortError as e: log.error('account_verify_failed', account_id=account_id, email=email, host=host, port=port, error="[ALERT] Can't connect to host - may be transient") raise TransientConnectionError(str(e)) except IMAPClient.Error as e: log.error('account_verify_failed', account_id=account_id, email=email, host=host, port=port, error='[ALERT] Invalid credentials (Failure)') raise ValidationError(str(e)) except SSLError as e: log.error('account_verify_failed', account_id=account_id, email=email, host=host, port=port, error='[ALERT] SSL Connection error (Failure)') raise ConnectionError(str(e)) return conn
'grant_type': 'refresh_token' } try: headers = {'Content-type': 'application/x-www-form-urlencoded', 'Accept': 'text/plain'} data = urllib.urlencode(args) response = requests.post(access_token_url, data=data, headers=headers) except (requests.exceptions.HTTPError, RequestsConnectionError), e: log.error(e) raise ConnectionError() try: session_dict = response.json() except JSONDecodeError: raise ConnectionError("Invalid json: " + response.text) if u'error' in session_dict: if session_dict['error'] == 'invalid_grant': raise OAuthInvalidGrantError('Invalid refresh token.') else: raise OAuthError(session_dict['error']) return session_dict['access_token'], session_dict['expires_in'] # ------------------------------------------------------------------ # Console Support for providing link and reading response from user # ------------------------------------------------------------------ def authorize_link(provider_module, email_address=None):