Example #1
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')
            elif session_dict['error'] == 'deleted_client':
                # If the developer has outright deleted their Google OAuth app
                # ID. We treat this too as a case of 'invalid credentials'.
                raise OAuthError('deleted_client')
            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']
Example #2
0
    def _new_access_token_from_refresh_token(self, account):
        refresh_token = account.refresh_token
        if not refresh_token:
            raise OAuthError("refresh_token required")

        client_id, client_secret = account.get_client_info()

        access_token_url = self.OAUTH_ACCESS_TOKEN_URL

        data = urllib.parse.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 ValueError:
            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")
            elif session_dict["error"] == "deleted_client":
                # If the developer has outright deleted their Google OAuth app
                # ID. We treat this too as a case of 'invalid credentials'.
                raise OAuthError("deleted_client")
            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"]
Example #3
0
    def _get_authenticated_user(self, authorization_code):
        args = {
            'client_id': self.OAUTH_CLIENT_ID,
            'client_secret': self.OAUTH_CLIENT_SECRET,
            'redirect_uri': self.OAUTH_REDIRECT_URI,
            'code': authorization_code,
            'grant_type': 'authorization_code'
        }

        headers = {'Content-type': 'application/x-www-form-urlencoded',
                   'Accept': 'text/plain'}
        data = urllib.urlencode(args)
        resp = requests.post(self.OAUTH_ACCESS_TOKEN_URL, data=data,
                             headers=headers)

        session_dict = resp.json()

        if u'error' in session_dict:
            raise OAuthError(session_dict['error'])

        access_token = session_dict['access_token']
        validation_dict = self.validate_token(access_token)
        userinfo_dict = self._get_user_info(access_token)

        z = session_dict.copy()
        z.update(validation_dict)
        z.update(userinfo_dict)

        return z
Example #4
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,
                requests.exceptions.ConnectionError), e:
            log.error(e)
            raise ConnectionError()
Example #5
0
    def _get_authenticated_user(self, authorization_code):
        args = {
            "client_id": self.OAUTH_CLIENT_ID,
            "client_secret": self.OAUTH_CLIENT_SECRET,
            "redirect_uri": self.OAUTH_REDIRECT_URI,
            "code": authorization_code,
            "grant_type": "authorization_code",
        }

        headers = {
            "Content-type": "application/x-www-form-urlencoded",
            "Accept": "text/plain",
        }
        data = urllib.parse.urlencode(args)
        resp = requests.post(self.OAUTH_ACCESS_TOKEN_URL,
                             data=data,
                             headers=headers)

        session_dict = resp.json()

        if u"error" in session_dict:
            raise OAuthError(session_dict["error"])

        userinfo_dict = self._get_user_info(session_dict)

        z = session_dict.copy()
        z.update(userinfo_dict)

        return z
 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
Example #7
0
    def new_token(self, scope, client_ids=None):
        """
        Retrieves a new access token w/ access to the given scope.
        Returns a GToken namedtuple.

        If this comes across any invalid refresh_tokens, it'll set the
        auth_credentials' is_valid flag to False.

        If no valid auth tokens are available, throws an OAuthError.

        If client_ids is given, only looks at auth credentials with a client
        id in client_ids.

        """
        non_oauth_error = None

        possible_credentials = [
            auth_creds for auth_creds in self.valid_auth_credentials
            if scope in auth_creds.scopes and (
                client_ids is None or auth_creds.client_id in client_ids)
        ]

        # If more than one set of credentials is present, we don't want to
        # just use the same one each time.
        shuffle(possible_credentials)

        for auth_creds in possible_credentials:
            try:
                token, expires_in = self.auth_handler.new_token(
                    auth_creds.refresh_token, auth_creds.client_id,
                    auth_creds.client_secret)

                expires_in -= 10
                expiration = (datetime.utcnow() +
                              timedelta(seconds=expires_in))

                return GToken(token, expiration, auth_creds.scopes,
                              auth_creds.client_id, auth_creds.id)

            except OAuthError as e:
                log.error('Error validating',
                          account_id=self.id,
                          auth_creds_id=auth_creds.id,
                          logstash_tag='mark_invalid')
                auth_creds.is_valid = False

            except Exception as e:
                log.error('Error while getting access token: {}'.format(e),
                          account_id=self.id,
                          auth_creds_id=auth_creds.id,
                          exc_info=True)
                non_oauth_error = e

        if non_oauth_error:
            # Some auth credential might still be valid!
            raise non_oauth_error
        else:
            raise OAuthError("No valid tokens")
Example #8
0
    def validate_token(self, access_token):
        response = requests.get(self.OAUTH_TOKEN_VALIDATION_URL,
                                params={'access_token': access_token})
        validation_dict = response.json()

        if 'error' in validation_dict:
            raise OAuthError(validation_dict['error'])

        return validation_dict
Example #9
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)
Example #10
0
    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)

        # 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
Example #11
0
    def get_client_info(self):
        """
        Obtain the client ID and secret for this OAuth account.

        Return:
            Tuple with (client_id, client_secret).
        """
        if not self.client_id or self.client_id == self.OAUTH_CLIENT_ID:
            return (self.OAUTH_CLIENT_ID, self.OAUTH_CLIENT_SECRET)
        else:
            raise OAuthError("No valid tokens.")
Example #12
0
    def update_account(self, account, account_data):
        account.email_address = account_data.email

        if account_data.secret_type:
            account.set_secret(account_data.secret_type,
                               account_data.secret_value)

        if not account.secret:
            raise OAuthError("No valid auth info.")

        account.sync_email = account_data.sync_email

        account.client_id = account_data.client_id
        account.scope = account_data.scope

        return account
Example #13
0
    def new_token(self, scope):
        """
        Retrieves a new access token w/ access to the given scope.
        Returns a GToken namedtuple.

        If this comes across any invalid refresh_tokens, it'll set the
        auth_credentials' is_valid flag to False.

        If no valid auth tokens are available, throws an OAuthError.
        """

        non_oauth_error = None

        for auth_creds in self.valid_auth_credentials:
            if scope in auth_creds.scopes:
                try:
                    token, expires_in = self.auth_handler.new_token(
                        auth_creds.refresh_token, auth_creds.client_id,
                        auth_creds.client_secret)

                    expires_in -= 10
                    expiration = (datetime.utcnow() +
                                  timedelta(seconds=expires_in))

                    return GToken(token, expiration, auth_creds.scopes,
                                  auth_creds.id)

                except OAuthError as e:
                    log.error('Error validating',
                              account_id=self.id,
                              auth_creds_id=auth_creds.id,
                              logstash_tag='mark_invalid')
                    auth_creds.is_valid = False

                except Exception as e:
                    log.error('Error while getting access token: {}'.format(e),
                              account_id=self.id,
                              auth_creds_id=auth_creds.id,
                              exc_info=True)
                    non_oauth_error = e

        if non_oauth_error:
            # Some auth credential might still be valid!
            raise non_oauth_error
        else:
            raise OAuthError("No valid tokens")
Example #14
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"]}
Example #15
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
Example #16
0
    def acquire_access_token(self, account, force_refresh=False):
        """
        Acquire a new access token for the given account.

        Args:
            force_refresh (bool): Whether a token refresh should be forced when
                requesting it from an external token service (AuthAlligator)

        Raises:
            OAuthError: If the token is no longer valid and syncing should stop.
            ConnectionError: If there was a temporary/connection error renewing
                the auth token.
        """
        if account.secret.type == SecretType.AuthAlligator.value:
            return self._new_access_token_from_authalligator(
                account, force_refresh)
        elif account.secret.type == SecretType.Token.value:
            # Any token requested from the refresh token is refreshed already.
            return self._new_access_token_from_refresh_token(account)
        else:
            raise OAuthError("No supported secret found.")
Example #17
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']
Example #18
0
 def get_token_for_email(self, account, force_refresh=False):
     if self.allow_auth:
         return 'foo'
     raise OAuthError()
Example #19
0
def raise_oauth_error(e):
    raise OAuthError(e)
Example #20
0
 def get_token(self, account, force_refresh=True):
     if self.allow_auth:
         # return a fake token.
         return "foo"
     raise OAuthError()
Example #21
0
    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()

        # See if we've already stored this refresh token
        match = [
            auth_creds for auth_creds in account.auth_credentials
            if auth_creds.refresh_token == new_refresh_token
        ]

        # For new refresh_tokens, create new GmailAuthCredentials entry
        if new_refresh_token and len(match) == 0:
            auth_creds = GmailAuthCredentials()
            auth_creds.gmailaccount = account
            auth_creds.scopes = response.get('scope')
            auth_creds.g_id_token = response.get('id_token')
            auth_creds.client_id = response.get('client_id')
            auth_creds.client_secret = response.get('client_secret')
            auth_creds.refresh_token = new_refresh_token

        return account
Example #22
0
 def raise_401(*args):
     raise OAuthError()
Example #23
0
    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)

        self.verify_config(account)

        # Ensure account has sync enabled.
        account.enable_sync()

        return account