Ejemplo n.º 1
0
def _download_layout_description(sio: ServerApp, session: GameSession):
    try:
        # You must be a session member to do get the spoiler
        GameSessionMembership.get_by_ids(sio.get_current_user().id, session.id)
    except peewee.DoesNotExist:
        raise NotAuthorizedForAction()

    if session.layout_description_json is None:
        raise InvalidAction("Session does not contain a game")

    if not session.layout_description.permalink.spoiler:
        raise InvalidAction("Session does not contain a spoiler")

    return session.layout_description_json
Ejemplo n.º 2
0
def _get_preset(preset_json: dict) -> VersionedPreset:
    try:
        preset = VersionedPreset(preset_json)
        preset.get_preset()  # test if valid
        return preset
    except Exception as e:
        raise InvalidAction(f"invalid preset: {e}")
Ejemplo n.º 3
0
def _finish_session(sio: ServerApp, session: GameSession):
    _verify_has_admin(sio, session.id, None)
    if session.state != GameSessionState.IN_PROGRESS:
        raise InvalidAction("Session is not in progress")

    session.state = GameSessionState.FINISHED
    session.save()
Ejemplo n.º 4
0
def login_with_guest(sio: ServerApp, encrypted_login_request: bytes):
    if sio.guest_encrypt is None:
        raise NotAuthorizedForAction()

    try:
        login_request_bytes = sio.guest_encrypt.decrypt(encrypted_login_request)
    except cryptography.fernet.InvalidToken:
        raise NotAuthorizedForAction()

    try:
        login_request = json.loads(login_request_bytes.decode("utf-8"))
        name = login_request["name"]
        date = datetime.datetime.fromisoformat(login_request["date"])
    except (UnicodeDecodeError, json.JSONDecodeError, KeyError, ValueError) as e:
        raise InvalidAction(str(e))

    if _get_now() - date > datetime.timedelta(days=1):
        raise NotAuthorizedForAction()

    user: User = User.create(name=f"Guest: {name}")

    with sio.session() as session:
        session["user-id"] = user.id

    return _create_client_side_session(sio, user)
Ejemplo n.º 5
0
def _start_session(sio: ServerApp, session: GameSession):
    _verify_has_admin(sio, session.id, None)
    _verify_in_setup(session)
    if session.layout_description_json is None:
        raise InvalidAction("Unable to start session, no game is available.")

    num_players = GameSessionMembership.select().where(
        GameSessionMembership.session == session,
        GameSessionMembership.row != None).count()
    expected_players = session.num_rows
    if num_players != expected_players:
        raise InvalidAction(
            f"Unable to start session, there are {num_players} but expected {expected_players} "
            f"({session.num_rows} x {session.num_teams}).")

    session.state = GameSessionState.IN_PROGRESS
    session.save()
Ejemplo n.º 6
0
def _find_empty_row(session: GameSession) -> int:
    possible_rows = set(range(session.num_rows))
    for member in GameSessionMembership.non_observer_members(session):
        possible_rows.remove(member.row)

    for empty_row in sorted(possible_rows):
        return empty_row
    raise InvalidAction("Session is full")
Ejemplo n.º 7
0
def _change_layout_description(sio: ServerApp, session: GameSession,
                               description_json: Optional[dict]):
    _verify_has_admin(sio, session.id, None)
    _verify_in_setup(session)
    rows_to_update = []

    if description_json is None:
        description = None
    else:
        if session.generation_in_progress != sio.get_current_user():
            if session.generation_in_progress is None:
                raise InvalidAction(f"Not waiting for a layout.")
            else:
                raise InvalidAction(
                    f"Waiting for a layout from {session.generation_in_progress.name}."
                )

        _verify_no_layout_description(session)
        description = LayoutDescription.from_json_dict(description_json)
        if description.player_count != session.num_rows:
            raise InvalidAction(
                f"Description is for a {description.player_count} players,"
                f" while the session is for {session.num_rows}.")

        for permalink_preset, preset_row in zip(description.all_presets,
                                                session.presets):
            preset_row = typing.cast(GameSessionPreset, preset_row)
            if _get_preset(json.loads(
                    preset_row.preset)).get_preset() != permalink_preset:
                preset = VersionedPreset.with_preset(permalink_preset)
                if preset.game not in session.allowed_games:
                    raise InvalidAction(f"{preset.game} preset not allowed.")
                preset_row.preset = json.dumps(preset.as_json)
                rows_to_update.append(preset_row)

    with database.db.atomic():
        for preset_row in rows_to_update:
            preset_row.save()

        session.generation_in_progress = None
        session.layout_description = description
        session.save()
        _add_audit_entry(
            sio, session, "Removed generated game" if description is None else
            f"Set game to {description.shareable_word_hash}")
Ejemplo n.º 8
0
def _finish_session(sio: ServerApp, session: GameSession):
    _verify_has_admin(sio, session.id, None)
    if session.state != GameSessionState.IN_PROGRESS:
        raise InvalidAction("Session is not in progress")

    session.state = GameSessionState.FINISHED
    logger().info(f"{_describe_session(session)}: Finishing session.")
    session.save()
    _add_audit_entry(sio, session, f"Finished session")
Ejemplo n.º 9
0
def _change_layout_description(sio: ServerApp, session: GameSession,
                               description_json: Optional[dict]):
    _verify_has_admin(sio, session.id, None)
    _verify_in_setup(session)
    rows_to_update = []

    if description_json is None:
        description = None
    else:
        if session.generation_in_progress != sio.get_current_user():
            if session.generation_in_progress is None:
                raise InvalidAction(f"Not waiting for a layout.")
            else:
                raise InvalidAction(
                    f"Waiting for a layout from {session.generation_in_progress.name}."
                )

        _verify_no_layout_description(session)
        description = LayoutDescription.from_json_dict(description_json)
        permalink = description.permalink
        if permalink.player_count != session.num_rows:
            raise InvalidAction(
                f"Description is for a {permalink.player_count} players,"
                f" while the session is for {session.num_rows}.")

        for permalink_preset, preset_row in zip(permalink.presets.values(),
                                                session.presets):
            preset_row = typing.cast(GameSessionPreset, preset_row)
            if _get_preset(json.loads(
                    preset_row.preset)).get_preset() != permalink_preset:
                preset = VersionedPreset.with_preset(permalink_preset)
                if preset.game != RandovaniaGame.PRIME2:
                    raise InvalidAction("Only Prime 2 presets allowed.")
                preset_row.preset = json.dumps(preset.as_json)
                rows_to_update.append(preset_row)

    with database.db.atomic():
        for preset_row in rows_to_update:
            preset_row.save()

        session.generation_in_progress = None
        session.layout_description = description
        session.save()
Ejemplo n.º 10
0
def _change_row(sio: ServerApp, session: GameSession, arg: Tuple[int, dict]):
    if len(arg) != 2:
        raise InvalidAction("Missing arguments.")
    row_id, preset_json = arg
    _verify_has_admin(sio, session.id, sio.get_current_user().id)
    _verify_in_setup(session)
    _verify_no_layout_description(session)
    preset = _get_preset(preset_json)

    try:
        with database.db.atomic():
            preset_row = GameSessionPreset.get(
                GameSessionPreset.session == session,
                GameSessionPreset.row == row_id)
            preset_row.preset = json.dumps(preset.as_json)
            preset_row.save()

    except peewee.DoesNotExist:
        raise InvalidAction(f"invalid row: {row_id}")
Ejemplo n.º 11
0
def _delete_row(sio: ServerApp, session: GameSession, row_id: int):
    _verify_has_admin(sio, session.id, None)
    _verify_in_setup(session)
    _verify_no_layout_description(session)

    if session.num_rows < 2:
        raise InvalidAction("Can't delete row when there's only one")

    if row_id != session.num_rows - 1:
        raise InvalidAction(f"Can only delete the last row")

    with database.db.atomic():
        GameSessionPreset.delete().where(
            GameSessionPreset.session == session,
            GameSessionPreset.row == row_id).execute()
        GameSessionMembership.update(row=None).where(
            GameSessionMembership.session == session.id,
            GameSessionMembership.row == row_id,
        ).execute()
Ejemplo n.º 12
0
def _find_empty_row(session: GameSession) -> int:
    empty_row = 0
    for possible_slot in GameSessionMembership.non_observer_members(session):
        if empty_row != possible_slot.row:
            break
        else:
            empty_row += 1

    if empty_row >= session.num_rows:
        raise InvalidAction("Session is full")
    return empty_row
Ejemplo n.º 13
0
def game_session_collect_locations(sio: ServerApp, session_id: int,
                                   pickup_locations: Tuple[int, ...]):
    current_user = sio.get_current_user()
    session: GameSession = database.GameSession.get_by_id(session_id)
    membership = GameSessionMembership.get_by_ids(current_user.id, session_id)

    if session.state != GameSessionState.IN_PROGRESS:
        raise InvalidAction(
            "Unable to collect locations of sessions that aren't in progress")

    if membership.is_observer:
        raise InvalidAction("Observers can't collect locations")

    description = session.layout_description

    receiver_players = set()
    for location in pickup_locations:
        receiver_player = _collect_location(session, membership, description,
                                            location)
        if receiver_player is not None:
            receiver_players.add(receiver_player)

    if not receiver_players:
        return

    for receiver_player in receiver_players:
        try:
            receiver_membership = GameSessionMembership.get_by_session_position(
                session, row=receiver_player)
            flask_socketio.emit(
                "game_has_update", {
                    "session": session_id,
                    "row": receiver_player,
                },
                room=
                f"game-session-{receiver_membership.session.id}-{receiver_membership.user.id}"
            )
        except peewee.DoesNotExist:
            pass
    _emit_session_update(session)
Ejemplo n.º 14
0
def _change_row(sio: ServerApp, session: GameSession, arg: Tuple[int, dict]):
    if len(arg) != 2:
        raise InvalidAction("Missing arguments.")
    row_id, preset_json = arg
    _verify_has_admin(sio, session.id, sio.get_current_user().id)
    _verify_in_setup(session)
    _verify_no_layout_description(session)
    preset = _get_preset(preset_json)
    # if preset.game != RandovaniaGame.PRIME2:
    #     raise InvalidAction("Only Prime 2 presets allowed.")

    try:
        with database.db.atomic():
            preset_row = GameSessionPreset.get(
                GameSessionPreset.session == session,
                GameSessionPreset.row == row_id)
            preset_row.preset = json.dumps(preset.as_json)
            logger().info(f"Session {session.id}: Changing row {row_id}.")
            preset_row.save()

    except peewee.DoesNotExist:
        raise InvalidAction(f"invalid row: {row_id}")
Ejemplo n.º 15
0
def game_session_collect_locations(sio: ServerApp, session_id: int,
                                   pickup_locations: Tuple[int, ...]):
    current_user = sio.get_current_user()
    session: GameSession = database.GameSession.get_by_id(session_id)
    membership = GameSessionMembership.get_by_ids(current_user.id, session_id)

    if session.state != GameSessionState.IN_PROGRESS:
        raise InvalidAction(
            "Unable to collect locations of sessions that aren't in progress")

    if membership.is_observer:
        raise InvalidAction("Observers can't collect locations")

    logger().info(
        f"{_describe_session(session, membership)} found items {pickup_locations}"
    )
    description = session.layout_description

    receiver_players = set()
    for location in pickup_locations:
        receiver_player = _collect_location(session, membership, description,
                                            location)
        if receiver_player is not None:
            receiver_players.add(receiver_player)

    if not receiver_players:
        return

    for receiver_player in receiver_players:
        try:
            receiver_membership = GameSessionMembership.get_by_session_position(
                session, row=receiver_player)
            _emit_game_session_pickups_update(sio, receiver_membership)
        except peewee.DoesNotExist:
            pass
    _emit_session_actions_update(session)
Ejemplo n.º 16
0
def _update_layout_generation(sio: ServerApp, session: GameSession,
                              active: bool):
    _verify_has_admin(sio, session.id, None)
    _verify_in_setup(session)

    if active:
        if session.generation_in_progress is None:
            session.generation_in_progress = sio.get_current_user()
        else:
            raise InvalidAction(
                f"Generation already in progress by {session.generation_in_progress.name}."
            )
    else:
        session.generation_in_progress = None

    session.save()
Ejemplo n.º 17
0
def game_session_admin_player(sio: ServerApp, session_id: int, user_id: int,
                              action: str, arg):
    _verify_has_admin(sio, session_id, user_id)
    action: SessionAdminUserAction = SessionAdminUserAction(action)

    session: GameSession = database.GameSession.get_by_id(session_id)
    membership = GameSessionMembership.get_by_ids(user_id, session_id)

    if action == SessionAdminUserAction.KICK:
        _add_audit_entry(
            sio, session, f"Kicked {membership.effective_name}"
            if membership.user != sio.get_current_user() else "Left session")
        membership.delete_instance()
        if not list(session.players):
            session.delete_instance(recursive=True)
            logger().info(
                f"{_describe_session(session)}. Kicking user {user_id} and deleting session."
            )
        else:
            logger().info(
                f"{_describe_session(session)}. Kicking user {user_id}.")

    elif action == SessionAdminUserAction.MOVE:
        offset: int = arg
        if membership.is_observer is None:
            raise InvalidAction("Player is an observer")

        new_row = membership.row + offset
        if new_row < 0:
            raise InvalidAction("New position is negative")
        if new_row >= session.num_rows:
            raise InvalidAction("New position is beyond num of rows")

        team_members = [None] * session.num_rows
        for member in GameSessionMembership.non_observer_members(session):
            team_members[member.row] = member

        while (0 <= new_row <
               session.num_rows) and team_members[new_row] is not None:
            new_row += offset

        if new_row < 0 or new_row >= session.num_rows:
            raise InvalidAction("No empty slots found in this direction")

        with database.db.atomic():
            logger().info(
                f"{_describe_session(session)}, User {user_id}. "
                f"Performing {action}, new row is {new_row}, from {membership.row}."
            )
            membership.row = new_row
            membership.save()

    elif action == SessionAdminUserAction.SWITCH_IS_OBSERVER:
        if membership.is_observer:
            membership.row = _find_empty_row(session)
        else:
            membership.row = None
        logger().info(
            f"{_describe_session(session)}, User {user_id}. Performing {action}, "
            f"new row is {membership.row}.")
        membership.save()

    elif action == SessionAdminUserAction.SWITCH_ADMIN:
        # Must be admin for this
        _verify_has_admin(sio, session_id, None, allow_when_no_admins=True)
        num_admins = GameSessionMembership.select().where(
            GameSessionMembership.session == session_id,
            GameSessionMembership.admin == True).count()

        if membership.admin and num_admins <= 1:
            raise InvalidAction("can't demote the only admin")

        membership.admin = not membership.admin
        _add_audit_entry(
            sio, session,
            f"Made {membership.effective_name} {'' if membership.admin else 'not '}an admin"
        )
        logger().info(
            f"{_describe_session(session)}, User {user_id}. Performing {action}, "
            f"new status is {membership.admin}.")
        membership.save()

    elif action == SessionAdminUserAction.CREATE_PATCHER_FILE:
        player_names = {i: f"Player {i + 1}" for i in range(session.num_rows)}

        for member in GameSessionMembership.non_observer_members(session):
            player_names[member.row] = member.effective_name

        layout_description = session.layout_description
        players_config = PlayersConfiguration(
            player_index=membership.row,
            player_names=player_names,
        )
        preset = layout_description.get_preset(players_config.player_index)
        cosmetic_patches = preset.game.data.layout.cosmetic_patches.from_json(
            arg)

        _add_audit_entry(sio, session,
                         f"Made an ISO for row {membership.row + 1}")

        data_factory = preset.game.patch_data_factory(layout_description,
                                                      players_config,
                                                      cosmetic_patches)
        try:
            return data_factory.create_data()
        except Exception as e:
            logger().exception("Error when creating patch data")
            raise InvalidAction(f"Unable to export game: {e}")

    elif action == SessionAdminUserAction.ABANDON:
        # FIXME
        raise InvalidAction("Abandon is NYI")

    _emit_session_meta_update(session)
Ejemplo n.º 18
0
def _reset_session(sio: ServerApp, session: GameSession):
    raise InvalidAction("Restart session is not yet implemented.")
Ejemplo n.º 19
0
async def test_handle_network_errors_success(skip_qtbot, qapp):
    callee = AsyncMock()
    callee.return_value = MagicMock()
    data = MagicMock()

    # Run
    wrapped = qt_network_client.handle_network_errors(callee)
    result = await wrapped(qapp, "foo", data)

    # Assert
    callee.assert_awaited_once_with(qapp, "foo", data)
    assert result is callee.return_value


@pytest.mark.parametrize(["exception", "title", "message"], [
    (InvalidAction("something"), "Invalid action",
     "Invalid Action: something"),
    (ServerError(), "Server error",
     "An error occurred on the server while processing your request."),
    (NotAuthorizedForAction(), "Unauthorized",
     "You're not authorized to perform that action."),
    (NotLoggedIn(), "Unauthenticated", "You must be logged in."),
    (RequestTimeout("5s timeout"), "Connection Error",
     "<b>Timeout while communicating with the server:</b><br /><br />Request timed out: 5s timeout<br />"
     "Further attempts will wait for longer."),
])
async def test_handle_network_errors_exception(skip_qtbot, qapp, mocker,
                                               exception, title, message):
    mock_dialog = mocker.patch("randovania.gui.lib.async_dialog.warning",
                               new_callable=AsyncMock)
    callee = AsyncMock()
Ejemplo n.º 20
0
def _verify_no_layout_description(session: GameSession):
    if session.layout_description_json is not None:
        raise InvalidAction("Session has a generated game")
Ejemplo n.º 21
0
def _verify_in_setup(session: GameSession):
    if session.state != GameSessionState.SETUP:
        raise InvalidAction("Session is not in setup")
Ejemplo n.º 22
0
def game_session_admin_player(sio: ServerApp, session_id: int, user_id: int,
                              action: str, arg):
    _verify_has_admin(sio, session_id, user_id)
    action: SessionAdminUserAction = SessionAdminUserAction(action)

    session: GameSession = database.GameSession.get_by_id(session_id)
    membership = GameSessionMembership.get_by_ids(user_id, session_id)

    if action == SessionAdminUserAction.KICK:
        membership.delete_instance()
        if not list(session.players):
            session.delete_instance(recursive=True)
            logger().info(
                f"Session {session_id}. Kicking user {user_id} and deleting session."
            )
        else:
            logger().info(f"Session {session_id}. Kicking user {user_id}.")

    elif action == SessionAdminUserAction.MOVE:
        offset: int = arg
        if membership.is_observer is None:
            raise InvalidAction("Player is an observer")

        new_row = membership.row + offset
        if new_row < 0:
            raise InvalidAction("New position is negative")
        if new_row >= session.num_rows:
            raise InvalidAction("New position is beyond num of rows")

        team_members = [None] * session.num_rows
        for member in GameSessionMembership.non_observer_members(session):
            team_members[member.row] = member

        while (0 <= new_row <
               session.num_rows) and team_members[new_row] is not None:
            new_row += offset

        if new_row < 0 or new_row >= session.num_rows:
            raise InvalidAction("No empty slots found in this direction")

        with database.db.atomic():
            logger().info(
                f"Session {session_id}, User {user_id}. "
                f"Performing {action}, new row is {new_row}, from {membership.row}."
            )
            membership.row = new_row
            membership.save()

    elif action == SessionAdminUserAction.SWITCH_IS_OBSERVER:
        if membership.is_observer:
            membership.row = _find_empty_row(session)
        else:
            membership.row = None
        logger().info(
            f"Session {session_id}, User {user_id}. Performing {action}, new row is {membership.row}."
        )
        membership.save()

    elif action == SessionAdminUserAction.SWITCH_ADMIN:
        # Must be admin for this
        _verify_has_admin(sio, session_id, None, allow_when_no_admins=True)
        num_admins = GameSessionMembership.select().where(
            GameSessionMembership.session == session_id,
            GameSessionMembership.admin == True).count()

        if membership.admin and num_admins <= 1:
            raise InvalidAction("can't demote the only admin")

        membership.admin = not membership.admin
        logger().info(
            f"Session {session_id}, User {user_id}. Performing {action}, new status is {membership.admin}."
        )
        membership.save()

    elif action == SessionAdminUserAction.CREATE_PATCHER_FILE:
        cosmetic_patches = CosmeticPatches.from_json_dict(arg)
        player_names = {i: f"Player {i + 1}" for i in range(session.num_rows)}

        for member in GameSessionMembership.non_observer_members(session):
            player_names[member.row] = member.effective_name

        players_config = PlayersConfiguration(
            player_index=membership.row,
            player_names=player_names,
        )
        return patcher_file.create_patcher_file(session.layout_description,
                                                players_config,
                                                cosmetic_patches)

    elif action == SessionAdminUserAction.ABANDON:
        # FIXME
        raise InvalidAction("Abandon is NYI")

    _emit_session_update(session)