Example #1
0
    def refresh_access_token(self, user_id: int, refresh_token: str):
        client = self.client_config["web"]
        user = self.get_user(user_id)
        credentials = Credentials(token=user["access_token"],
                                  refresh_token=user["refresh_token"],
                                  client_id=client["client_id"],
                                  client_secret=client["client_secret"],
                                  token_uri=client["token_uri"],
                                  scopes=YOUTUBE_SCOPES,
                                  expiry=user["token_expires"])
        try:
            credentials.refresh(Request())
        except RefreshError as error:
            # refresh error has error message as first arg and the actual error response from the api in the second arg
            error_body = error.args[1]
            if "error" in error_body and error_body["error"] == "invalid_grant":
                raise ExternalServiceInvalidGrantError(error_body)

            raise ExternalServiceAPIError(
                "Could not refresh API Token for Youtube user")
        external_service_oauth.update_token(
            user_id=user_id,
            service=self.service,
            access_token=credentials.token,
            refresh_token=credentials.refresh_token,
            expires_at=int(
                credentials.expiry.replace(tzinfo=timezone.utc).timestamp()))
        return self.get_user(user_id)
Example #2
0
 def test_notification_on_api_error(self, mock_make_api_request,
                                    mock_notify_error, mock_update):
     mock_make_api_request.side_effect = ExternalServiceAPIError(
         'api borked')
     app = listenbrainz.webserver.create_app()
     app.config['TESTING'] = False
     with app.app_context():
         spotify_read_listens.process_all_spotify_users()
         mock_notify_error.assert_called_once_with(self.user['id'],
                                                   'api borked')
         mock_update.assert_called_once()
Example #3
0
    def refresh_access_token(self, user_id: int, refresh_token: str):
        """ Refreshes the user token for the given spotify user.
        Args:
            user_id (int): the ListenBrainz row ID of the user whose token is to be refreshed
            refresh_token (str): the refresh token to use for refreshing access token
        Returns:
            user (dict): the same user with updated tokens
        Raises:
            SpotifyAPIError: if unable to refresh spotify user token
            SpotifyInvalidGrantError: if the user has revoked authorization to spotify
        Note: spotipy eats up the json body in case of error but we need it for checking
        whether the user has revoked our authorization. hence, we use our own
        code instead of spotipy to fetch refresh token.
        """
        retries = SPOTIFY_API_RETRIES
        response = None
        while retries > 0:
            response = _get_spotify_token("refresh_token", refresh_token)

            if response.status_code == 200:
                break
            elif response.status_code == 400:
                error_body = response.json()
                if "error" in error_body and error_body[
                        "error"] == "invalid_grant":
                    raise ExternalServiceInvalidGrantError(error_body)

            response = None  # some other error occurred
            retries -= 1

        if response is None:
            raise ExternalServiceAPIError(
                'Could not refresh API Token for Spotify user')

        response = response.json()
        access_token = response['access_token']
        if "refresh_token" in response:
            refresh_token = response['refresh_token']
        expires_at = int(time.time()) + response['expires_in']
        external_service_oauth.update_token(user_id=user_id,
                                            service=self.service,
                                            access_token=access_token,
                                            refresh_token=refresh_token,
                                            expires_at=expires_at)
        return self.get_user(user_id)
Example #4
0
def make_api_request(user: dict, endpoint: str, **kwargs):
    """ Make an request to the Spotify API for particular user at specified endpoint with args.

    Args:
        user: the user whose plays are to be imported.
        endpoint: the name of Spotipy function which makes request to the required API endpoint

    Returns:
        the response from the spotify API

    Raises:
        ExternalServiceAPIError: if we encounter errors from the Spotify API.
        ExternalServiceError: if we encounter a rate limit, even after retrying.
    """
    retries = 10
    delay = 1
    tried_to_refresh_token = False

    while retries > 0:
        try:
            spotipy_client = spotipy.Spotify(auth=user['access_token'])
            spotipy_call = getattr(spotipy_client, endpoint)
            recently_played = spotipy_call(**kwargs)
            break
        except (AttributeError, TypeError):
            current_app.logger.critical(
                "Invalid spotipy endpoint or arguments:", exc_info=True)
            return None
        except SpotifyException as e:
            retries -= 1
            if e.http_status == 429:
                # Rate Limit Problems -- the client handles these, but it can still give up
                # after a certain number of retries, so we look at the header and try the
                # request again, if the error is raised
                try:
                    time_to_sleep = int(e.headers.get('Retry-After', delay))
                except ValueError:
                    time_to_sleep = delay
                current_app.logger.warn(
                    'Encountered a rate limit, sleeping %d seconds and trying again...',
                    time_to_sleep)
                time.sleep(time_to_sleep)
                delay += 1
                if retries == 0:
                    raise ExternalServiceError('Encountered a rate limit.')

            elif e.http_status in (400, 403):
                current_app.logger.critical(
                    'Error from the Spotify API for user %s: %s',
                    user['musicbrainz_id'],
                    str(e),
                    exc_info=True)
                raise ExternalServiceAPIError(
                    'Error from the Spotify API while getting listens: %s',
                    str(e))

            elif e.http_status >= 500 and e.http_status < 600:
                # these errors are not our fault, most probably. so just log them and retry.
                current_app.logger.error(
                    'Error while trying to get listens for user %s: %s',
                    user['musicbrainz_id'],
                    str(e),
                    exc_info=True)
                if retries == 0:
                    raise ExternalServiceAPIError(
                        'Error from the spotify API while getting listens: %s',
                        str(e))

            elif e.http_status == 401:
                # if we get 401 Unauthorized from Spotify, that means our token might have expired.
                # In that case, try to refresh the token, if there is an error even while refreshing
                # give up and report to the user.
                # We only try to refresh the token once, if we still get 401 after that, we give up.
                if not tried_to_refresh_token:
                    user = SpotifyService().refresh_access_token(
                        user['user_id'], user['refresh_token'])
                    tried_to_refresh_token = True

                else:
                    raise ExternalServiceAPIError(
                        'Could not authenticate with Spotify, please unlink and link your account again.'
                    )
            elif e.http_status == 404:
                current_app.logger.error(
                    "404 while trying to get listens for user %s",
                    str(user),
                    exc_info=True)
                if retries == 0:
                    raise ExternalServiceError(
                        "404 while trying to get listens for user %s" %
                        str(user))
        except Exception as e:
            retries -= 1
            current_app.logger.error(
                'Unexpected error while getting listens: %s',
                str(e),
                exc_info=True)
            if retries == 0:
                raise ExternalServiceError(
                    'Unexpected error while getting listens: %s' % str(e))

    return recently_played