async def test_auth_lost(http_client, http_request, oauth_response):
    http_request.return_value = oauth_response
    await http_client.authenticate_with_refresh_token("TOKEN")
    http_request.reset_mock()

    http_client._authorized_get = MagicMock()
    http_client._authorized_get.side_effect = AuthenticationRequired()

    http_client.__refresh_tokens = MagicMock()
    http_client.__refresh_tokens.side_effects = AuthenticationRequired()

    with pytest.raises(AuthenticationRequired):
        await http_client.get("url")
Example #2
0
 async def get_game_times(self):
     if not self.client.is_authenticated():
         raise AuthenticationRequired()
     game_times = []
     games_with_space = [
         game for game in self.games_collection if game.space_id
     ]
     try:
         tasks = [
             self.client.get_game_stats(game.space_id)
             for game in games_with_space
         ]
         stats = await asyncio.gather(*tasks)
         for st, game in zip(stats, games_with_space):
             statscards = st.get('Statscards', None)
             if statscards is None:
                 continue
             playtime, last_played = find_playtime(statscards,
                                                   default_total_time=0,
                                                   default_last_played=0)
             log.info(
                 f'Stats for {game.name}: playtime: {playtime}, last_played: {last_played}'
             )
             if playtime is not None and last_played is not None:
                 game_times.append(
                     GameTime(game.space_id, playtime, last_played))
     except ApplicationError as e:
         log.exception("Game times:" + repr(e))
         raise e
     except Exception as e:
         log.exception("Game times:" + repr(e))
     finally:
         return game_times
    async def request(self, method, url, *args, **kwargs):
        try:
            response = await self._session.request(method, url, *args,
                                                   **kwargs)
        except asyncio.TimeoutError:
            raise BackendTimeout()
        except aiohttp.ServerDisconnectedError:
            raise BackendNotAvailable()
        except aiohttp.ClientConnectionError:
            raise NetworkError()
        except aiohttp.ContentTypeError:
            raise UnknownBackendResponse()
        except aiohttp.ClientError:
            logging.exception(
                "Caught exception while running {} request for {}".format(
                    method, url))
            raise UnknownError()
        if response.status == HTTPStatus.UNAUTHORIZED:
            raise AuthenticationRequired()
        if response.status == HTTPStatus.FORBIDDEN:
            raise AccessDenied()
        if response.status == HTTPStatus.SERVICE_UNAVAILABLE:
            raise BackendNotAvailable()
        if response.status == HTTPStatus.TOO_MANY_REQUESTS:
            raise TooManyRequests()
        if response.status >= 500:
            raise BackendError()
        if response.status >= 400:
            logging.warning(
                "Got status {} while running {} request for {}".format(
                    response.status, method, url))
            raise UnknownError()

        return response
Example #4
0
    async def get(self, *args, **kwargs):
        if not self.authenticated:
            raise AuthenticationRequired()

        try:
            return await self._authorized_get(*args, **kwargs)
        except AuthenticationRequired:
            try:
                await self._refresh_tokens()
            except Exception:
                logging.exception("Failed to refresh tokens")
                if self._auth_lost_callback:
                    self._auth_lost_callback()
                raise AuthenticationRequired()

            return await self._authorized_get(*args, **kwargs)
 async def _validate_graph_response(self, response):
     response = await response.json()
     if "errors" in response:
         for error in response["errors"]:
             if '401' in error["message"]:
                 raise AuthenticationRequired()
     return response
Example #6
0
    async def get_owned_games(self):
        if not self.client.is_authenticated():
            raise AuthenticationRequired()

        if SYSTEM == System.WINDOWS:
            self._parse_local_games()
            self._parse_local_game_ownership()

        await self._parse_club_games()
        try:
            await self._parse_subscription_games()
        except Exception as e:
            log.warning(
                f"Parsing subscriptions failed, most likely account without subscription {repr(e)}"
            )

        self.owned_games_sent = True

        for game in self.games_collection:
            game.considered_for_sending = True

        return [
            game.as_galaxy_game() for game in self.games_collection
            if game.owned
        ]
    async def get_owned_games(self):
        if self._steam_id is None:
            raise AuthenticationRequired()

        games = await self._client.get_games(self._steam_id)

        owned_games = []

        try:
            for game in games:
                owned_games.append(
                    Game(str(game["appid"]), game["name"], [],
                         LicenseInfo(LicenseType.SinglePurchase, None)))
        except (KeyError, ValueError):
            logging.exception("Can not parse backend response")
            raise UnknownBackendResponse()

        self._own_games = games

        game_ids = list(map(lambda x: x.game_id, owned_games))
        other_games = await self.get_steam_sharing_games(game_ids)
        for i in other_games:
            owned_games.append(i)

        return owned_games
Example #8
0
    async def install_game(self, game_id):
        if not self.authentication_client.is_authenticated():
            raise AuthenticationRequired()

        installed_game = self.installed_games.get(game_id, None)
        if installed_game and os.access(installed_game.install_path, os.F_OK):
            log.warning("Received install command on an already installed game")
            return await self.launch_game(game_id)

        if game_id in Blizzard.legacy_game_ids:
            if SYSTEM == pf.WINDOWS:
                platform = 'windows'
            elif SYSTEM == pf.MACOS:
                platform = 'macos'
            webbrowser.open(f"https://www.blizzard.com/download/confirmation?platform={platform}&locale=enUS&version=LIVE&id={game_id}")
            return
        try:
            self.local_client.refresh()
            log.info(f'Installing game of id {game_id}')
            self.local_client.install_game(game_id)
        except ClientNotInstalledError as e:
            log.warning(e)
            await self.open_battlenet_browser()
        except Exception as e:
            log.exception(f"Installing game {game_id} failed: {e}")
Example #9
0
    async def uninstall_game(self, game_id):
        if not self.authentication_client.is_authenticated():
            raise AuthenticationRequired()

        if game_id == 'wow_classic':
            # attempting to uninstall classic wow through protocol gives you a message that the game cannot
            # be uninstalled through protocol and you should use battle.net
            return self._open_battlenet_at_id(game_id)

        if SYSTEM == pf.MACOS:
            self._open_battlenet_at_id(game_id)
        else:
            try:
                installed_game = self.installed_games.get(game_id, None)

                if installed_game is None or not os.access(installed_game.install_path, os.F_OK):
                    log.error(f'Cannot uninstall {Blizzard[game_id].uid}')
                    self.update_local_game_status(LocalGame(game_id, LocalGameState.None_))
                    return

                if not isinstance(installed_game.info, ClassicGame):
                    if self.uninstaller is None:
                        raise FileNotFoundError('Uninstaller not found')

                uninstall_tag = installed_game.uninstall_tag
                client_lang = self.config_parser.locale_language
                self.uninstaller.uninstall_game(installed_game, uninstall_tag, client_lang)

            except Exception as e:
                log.exception(f'Uninstalling game {game_id} failed: {e}')
    async def do_request(self, method, *args, **kwargs):
        if not self.authenticated:
            raise AuthenticationRequired()

        try:
            if 'graph' in kwargs:
                return await self._validate_graph_response(await method(
                    *args, **kwargs))
            return await method(*args, **kwargs)
        except Exception as e:
            logging.exception(
                f"Received exception on authorized request: {repr(e)}")
            try:
                if not self._refreshing_task or self._refreshing_task.done():
                    self._refreshing_task = asyncio.create_task(
                        self._refresh_tokens())
                    await self._refreshing_task

                while not self._refreshing_task.done():
                    await asyncio.sleep(0.2)
            except AuthenticationRequired as e:
                logging.exception(
                    f"Failed to refresh tokens, received: {repr(e)}")
                if self._auth_lost_callback:
                    self._auth_lost_callback()
                raise
            except Exception as e:
                logging.exception(f"Got exception {repr(e)}")
                raise

            if 'graph' in kwargs:
                return await self._validate_graph_response(await method(
                    *args, **kwargs))
            return await method(*args, **kwargs)
Example #11
0
def handle_exception():
    """
    Context manager translating network related exceptions
    to custom :mod:`~galaxy.api.errors`.
    """
    try:
        yield
    except asyncio.TimeoutError:
        raise BackendTimeout()
    except aiohttp.ServerDisconnectedError:
        raise BackendNotAvailable()
    except aiohttp.ClientConnectionError:
        raise NetworkError()
    except aiohttp.ContentTypeError as error:
        raise UnknownBackendResponse(error.message)
    except aiohttp.ClientResponseError as error:
        if error.status == HTTPStatus.UNAUTHORIZED:
            raise AuthenticationRequired(error.message)
        if error.status == HTTPStatus.FORBIDDEN:
            raise AccessDenied(error.message)
        if error.status == HTTPStatus.SERVICE_UNAVAILABLE:
            raise BackendNotAvailable(error.message)
        if error.status == HTTPStatus.TOO_MANY_REQUESTS:
            raise TooManyRequests(error.message)
        if error.status >= 500:
            raise BackendError(error.message)
        if error.status >= 400:
            logger.warning("Got status %d while performing %s request for %s",
                           error.status, error.request_info.method,
                           str(error.request_info.url))
            raise UnknownError(error.message)
    except aiohttp.ClientError as e:
        logger.exception("Caught exception while performing request")
        raise UnknownError(repr(e))
    async def _authenticated_request(self, method, *args,
                                     **kwargs) -> ClientResponse:
        response = await super().request(method, *args, **kwargs)
        if response.status == HTTPStatus.FOUND:
            self._auth_lost_callback()
            raise AuthenticationRequired()

        return response
Example #13
0
    async def get_owned_games(self):
        if not self._api.is_authenticated:
            raise AuthenticationRequired()

        async with self._getting_owned_games:
            logging.debug('getting owned games')
            self._owned_games = await self._library_resolver()
            return [g.in_galaxy_format() for g in self._owned_games.values()]
 async def request(self, method, *args, **kwargs):
     if not self._access_token:
         raise AuthenticationRequired()
     try:
         return await self._oauth_request(method, *args, **kwargs)
     except AuthenticationRequired:
         await self._refresh_access_token()
         return await self._oauth_request(method, *args, **kwargs)
    async def get_friends(self):
        if self._steam_id is None:
            raise AuthenticationRequired()

        return [
            FriendInfo(user_id=user_id, user_name=user_name)
            for user_id, user_name in (await self._client.get_friends(self._steam_id)).items()
        ]
 async def get_friends(self):
     if not self.authentication_client.is_authenticated():
         raise AuthenticationRequired()
     friends_list = await self.social_features.get_friends()
     return [
         FriendInfo(user_id=friend.id.low, user_name='')
         for friend in friends_list
     ]
Example #17
0
    async def get_friends(self):
        if not self._http_client.is_authenticated():
            raise AuthenticationRequired()

        return [
            FriendInfo(user_id=str(user_id), user_name=str(user_name))
            for user_id, user_name in (
                await self._backend_client.get_friends(self._pid)).items()
        ]
Example #18
0
    async def get_owned_games(self):
        if not self.authentication_client.is_authenticated():
            raise AuthenticationRequired()

        def _parse_classic_games(classic_games):
            for classic_game in classic_games["classicGames"]:
                log.info(f"looking for {classic_game} in classic games")
                try:
                    blizzard_game = Blizzard[classic_game["localizedGameName"].replace(u'\xa0', ' ')]
                    log.info(f"match! {blizzard_game}")
                    classic_game["titleId"] = blizzard_game.uid
                    classic_game["gameAccountStatus"] = "Good"
                except KeyError:
                    continue

            return classic_games

        def _get_not_added_free_games(owned_games):
            owned_games_ids = []
            for game in owned_games:
                if "titleId" in game:
                    owned_games_ids.append(str(game["titleId"]))

            return [{"titleId": game.blizzard_id,
                    "localizedGameName": game.name,
                    "gameAccountStatus": "Free"}
                    for game in Blizzard.free_games if game.blizzard_id not in owned_games_ids]

        try:
            games = await self.backend_client.get_owned_games()
            classic_games = _parse_classic_games(await self.backend_client.get_owned_classic_games())
            owned_games = games["gameAccounts"] + classic_games["classicGames"]

            # Add wow classic if retail wow is present in owned games
            for owned_game in owned_games.copy():
                if 'titleId' in owned_game:
                    if owned_game['titleId'] == 5730135:
                        owned_games.append({'titleId': 'wow_classic',
                                            'localizedGameName': 'World of Warcraft Classic',
                                            'gameAccountStatus': owned_game['gameAccountStatus']})

            free_games_to_add = _get_not_added_free_games(owned_games)
            owned_games += free_games_to_add
            log.info(f"Owned games {owned_games} with free games")
            self.owned_games_cache = owned_games
            return [
                Game(
                    str(game["titleId"]),
                    game["localizedGameName"],
                    [],
                    LicenseInfo(License_Map[game["gameAccountStatus"]]),
                )
                for game in self.owned_games_cache if "titleId" in game
            ]
        except Exception as e:
            log.exception(f"failed to get owned games: {repr(e)}")
            raise
    async def get_owned_games(self, owned_title_ids=None, online_check_success=False):
        # Here is the actual implementation of getting the user's owned games:
        # -Get the list of games_played from rockstargames.com/auth/get-user.json.
        #   -If possible, use the launcher log to confirm which games are actual launcher games and which are
        #   Steam/Retail games.
        #   -If it is not possible to use the launcher log, then just use the list provided by the website.
        if owned_title_ids is None:
            owned_title_ids = []
        if not self.is_authenticated():
            raise AuthenticationRequired()

        # The log is in the Documents folder.
        current_log_count = 0
        log_file = None
        log_file_append = ""
        # The Rockstar Games Launcher generates 10 log files before deleting them in a FIFO fashion. Old log files are
        # given a number ranging from 1 to 9 in their name. In case the first log file does not have all of the games,
        # we need to check the other log files, if possible.
        while current_log_count < 10:
            # We need to prevent the log file check for Mac users.
            if not IS_WINDOWS:
                break
            try:
                if current_log_count != 0:
                    log_file_append = ".0" + str(current_log_count)
                log_file = os.path.join(self.documents_location, "Rockstar Games\\Launcher\\launcher" + log_file_append
                                        + ".log")
                if LOG_SENSITIVE_DATA:
                    log.debug("ROCKSTAR_LOG_LOCATION: Checking the file " + log_file + "...")
                else:
                    log.debug("ROCKSTAR_LOG_LOCATION: Checking the file ***...")  # The path to the Launcher log file
                    # likely contains the user's PC profile name (C:\Users\[Name]\Documents...).
                owned_title_ids = await self.parse_log_file(log_file, owned_title_ids, online_check_success)
                break
            except NoGamesInLogException:
                log.warning("ROCKSTAR_LOG_WARNING: There are no owned games listed in " + str(log_file) + ". Moving to "
                            "the next log file...")
                current_log_count += 1
            except NoLogFoundException:
                log.warning("ROCKSTAR_LAST_LOG_REACHED: There are no more log files that can be found and/or read "
                            "from. Assuming that the online list is correct...")
                break
            except Exception:
                # This occurs after ROCKSTAR_LOG_ERROR.
                break
        if current_log_count == 10:
            log.warning("ROCKSTAR_LAST_LOG_REACHED: There are no more log files that can be found and/or read "
                        "from. Assuming that the online list is correct...")

        for title_id in owned_title_ids:
            game = self.create_game_from_title_id(title_id)
            if game not in self.owned_games_cache:
                log.debug("ROCKSTAR_ADD_GAME: Adding " + title_id + " to owned games cache...")
                self.owned_games_cache.append(game)

        return self.owned_games_cache
Example #20
0
    async def start_game_times_import(self, game_ids):
        if not self._http_client.is_authenticated():
            raise AuthenticationRequired()

        _, self._last_played_games = await asyncio.gather(
            self._get_offers(
                game_ids),  # update local cache ignoring return value
            self._backend_client.get_lastplayed_games(self._pid))

        await super().start_game_times_import(game_ids)
Example #21
0
    async def prepare_game_times_context(self, game_ids: List[str]) -> Any:
        if self._user_info_cache.steam_id is None:
            raise AuthenticationRequired()

        if not self._times_cache.import_in_progress:
            await self._steam_client.refresh_game_times()
        else:
            logger.info("Game stats import already in progress")
        await self._times_cache.wait_ready(10 * 60) # Don't block future imports in case we somehow don't receive one of the responses
        logger.info("Finished game times context prepare")
Example #22
0
 def handle_status_code(status_code):
     if status_code == HTTPStatus.UNAUTHORIZED:
         raise AuthenticationRequired()
     if status_code == HTTPStatus.FORBIDDEN:
         raise AccessDenied()
     if status_code == HTTPStatus.SERVICE_UNAVAILABLE:
         raise BackendNotAvailable()
     if status_code >= 500:
         raise BackendError()
     if status_code >= 400:
         raise UnknownError()
Example #23
0
    async def get_owned_games(self):
        if not self._http_client.is_authenticated():
            raise AuthenticationRequired()

        owned_offers = await self._get_owned_offers()

        games = []
        for offer in owned_offers:
            game = Game(offer["offerId"], offer["i18n"]["displayName"], None,
                        LicenseInfo(LicenseType.SinglePurchase, None))
            games.append(game)

        return games
 async def _refresh_access_token(self):
     try:
         self._access_token = await self._get_access_token()
         if not self._access_token:
             raise UnknownBackendResponse("Empty access token")
     except (BackendNotAvailable, BackendTimeout, BackendError,
             NetworkError):
         logging.warning("Failed to refresh token for independent reasons")
         raise
     except InvalidCredentials:
         logging.exception("Failed to refresh token")
         self._auth_lost()
         raise AuthenticationRequired()
Example #25
0
    def request_url(self, url):
        session = requests.Session()
        session.headers.update({
            'User-Agent':
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36'
        })
        r = session.get(url, cookies=self._auth_cookies, allow_redirects=True)
        logger.info('url: %s (%s)' % (r.url, len(r.history)))
        if r.url.startswith('https://accounts.google.com/'):
            #if not r.url.startswith(url):
            logger.info('%s <=> %s' % (r.url, url))
            raise AuthenticationRequired()

        return r
Example #26
0
    async def do_request(self,
                         method,
                         url,
                         data=None,
                         json=True,
                         headers=None,
                         ignore_failure=False):
        loop = asyncio.get_event_loop()
        if not headers:
            headers = self._authentication_client.session.headers
        try:
            if data is None:
                data = {}
            params = {
                "method": method,
                "url": url,
                "data": data,
                "timeout": self._authentication_client.timeout,
                "headers": headers
            }
            try:
                response = await loop.run_in_executor(
                    None,
                    functools.partial(
                        self._authentication_client.session.request, **params))
            except requests.Timeout:
                raise BackendTimeout()

            if not ignore_failure:
                if response.status_code == HTTPStatus.UNAUTHORIZED:
                    raise AuthenticationRequired()
                if response.status_code == HTTPStatus.FORBIDDEN:
                    raise AccessDenied()
                if response.status_code == HTTPStatus.SERVICE_UNAVAILABLE:
                    raise BackendNotAvailable()
                if response.status_code >= 500:
                    raise BackendError()
                if response.status_code >= 400:
                    raise UnknownError()

            if json:
                return response.json()
            else:
                return response

        except Exception as e:
            log.exception(
                f"Request exception: {str(e)}; url: {url}, method: {method}, data: {data}, headers: {headers}"
            )
            raise
async def test_refresh_token(http_client, http_request, oauth_response):
    http_request.return_value = oauth_response
    await http_client.authenticate_with_refresh_token("TOKEN")
    http_request.reset_mock()

    authorized_response = MagicMock()
    authorized_response.status = 200

    http_client._authorized_get = AsyncMock()
    http_client._authorized_get.side_effect = [AuthenticationRequired(), authorized_response]

    http_client._authenticate = AsyncMock()

    response = await http_client.get("url")
    assert response.status == authorized_response.status
Example #28
0
 async def _refresh_access_token(self):
     try:
         self._access_token = await self.get_access_token(
             self._refresh_token)
         if not self._access_token:
             raise Exception
     except (BackendNotAvailable, BackendTimeout, BackendError,
             NetworkError):
         logging.warning("Failed to refresh token for independent reasons")
         raise
     except Exception:
         logging.exception("Failed to refresh token")
         if self._auth_lost_callback:
             self._auth_lost_callback()
         raise AuthenticationRequired()
Example #29
0
    async def get_friends(self):
        if self._user_info_cache.steam_id is None:
            raise AuthenticationRequired()

        friends_ids = await self._steam_client.get_friends()
        friends_infos = await self._steam_client.get_friends_info(friends_ids)
        friends_nicknames = await self._steam_client.get_friends_nicknames()

        friends = []
        for friend_id in friends_infos:
            friend = galaxy_user_info_from_user_info(str(friend_id), friends_infos[friend_id])
            if str(friend_id) in friends_nicknames:
                friend.user_name += f" ({friends_nicknames[friend_id]})"
            friends.append(friend)
        return friends
    async def _authenticate(self, grant_type, secret):
        headers = {
            "Authorization": "basic " + self._BASIC_AUTH_CREDENTIALS,
            "User-Agent": self.LAUNCHER_USER_AGENT
        }
        data = {"grant_type": grant_type, "token_type": "eg1"}
        data[grant_type] = secret

        try:
            with handle_exception():
                try:
                    response = await self._session.request("POST",
                                                           self._OAUTH_URL,
                                                           headers=headers,
                                                           data=data)
                except aiohttp.ClientResponseError as e:
                    logging.error(e)
                    if e.status == 400:  # override 400 meaning for auth purpose
                        raise AuthenticationRequired()
        except AuthenticationRequired as e:
            logging.exception(
                f"Authentication failed, grant_type: {grant_type}, exception: {repr(e)}"
            )
            raise AuthenticationRequired()
        result = await response.json()
        try:
            self._access_token = result["access_token"]
            self._refresh_token = result["refresh_token"]
            self._account_id = result["account_id"]

            credentials = {"refresh_token": self._refresh_token}
            self._store_credentials(credentials)
        except KeyError:
            logging.exception(
                "Could not parse backend response when authenticating")
            raise UnknownBackendResponse()