Пример #1
0
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
Пример #2
0
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()
Пример #3
0
    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()
Пример #4
0
 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
Пример #5
0
    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)
Пример #6
0
    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']
Пример #7
0
    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"]}
Пример #8
0
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
Пример #9
0
    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
Пример #10
0
            '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']
Пример #11
0
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']
Пример #12
0
    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
Пример #13
0
        '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):