Source code for app.services.game_access
from typing import Any
from app.core.exceptions import ForbiddenError, NotFoundError
[docs]
def can_access_user(
user: Any,
*,
api_key: str | None = None,
oauth_user_id: str | None = None,
is_admin: bool = False,
) -> bool:
"""
Return whether the caller context is allowed to access a user.
Admin tokens can access every user. API-key callers are scoped to users
created with their key. OAuth non-admin callers pass through here so that
game-level enforcement (get_authorized_game) gates individual data points.
"""
if is_admin:
return True
if api_key and getattr(user, "apiKey_used", None) == api_key:
return True
# Users don't store oauth_user_id; OAuth non-admin scope is enforced at
# game level within the calling service method.
if oauth_user_id and not api_key:
return True
return False
[docs]
async def get_authorized_user(
user_repository,
external_user_id: str,
*,
api_key: str | None = None,
oauth_user_id: str | None = None,
is_admin: bool = False,
) -> Any:
"""
Load a user and assert the caller is allowed to access them.
Args:
user_repository: Repository used to look the user up.
external_user_id (str): External identifier of the user.
api_key (str | None): Caller's API key, for ownership scoping.
oauth_user_id (str | None): Caller's OAuth subject, for scoping.
is_admin (bool): Whether the caller has the admin role.
Returns:
Any: The authorized user entity.
Raises:
NotFoundError: If the user does not exist.
ForbiddenError: If the caller may not access the user.
"""
user = await user_repository.read_by_column(
"externalUserId", external_user_id, not_found_raise_exception=True
)
if not can_access_user(
user,
api_key=api_key,
oauth_user_id=oauth_user_id,
is_admin=is_admin,
):
raise ForbiddenError(detail="You do not have permission to access this user")
return user
[docs]
def can_access_game(
game: Any,
*,
api_key: str | None = None,
oauth_user_id: str | None = None,
is_admin: bool = False,
) -> bool:
"""
Return whether the caller context is allowed to access a game.
Admin bearer tokens can access every game. Non-admin callers are scoped to
games created with their API key prefix or their OAuth subject.
"""
if is_admin:
return True
if api_key and getattr(game, "apiKey_used", None) == api_key:
return True
if oauth_user_id and getattr(game, "oauth_user_id", None) == oauth_user_id:
return True
return False
[docs]
async def get_authorized_game(
game_repository,
game_id,
*,
api_key: str | None = None,
oauth_user_id: str | None = None,
is_admin: bool = False,
) -> Any:
"""
Load a game and assert the caller is allowed to access it.
Args:
game_repository: Repository used to look the game up.
game_id: Internal identifier of the game.
api_key (str | None): Caller's API key, for ownership scoping.
oauth_user_id (str | None): Caller's OAuth subject, for scoping.
is_admin (bool): Whether the caller has the admin role.
Returns:
Any: The authorized game entity.
Raises:
NotFoundError: If the game does not exist.
ForbiddenError: If the caller may not access the game.
"""
game = await game_repository.read_by_id(
game_id,
not_found_raise_exception=False,
)
if not game:
raise NotFoundError(detail=f"Game not found by gameId: {game_id}")
if not can_access_game(
game,
api_key=api_key,
oauth_user_id=oauth_user_id,
is_admin=is_admin,
):
raise ForbiddenError(detail="You do not have permission to access this game")
return game