Exemplo n.º 1
0
def restore_user_session(sio: ServerApp, encrypted_session: bytes,
                         session_id: Optional[int]):
    try:
        decrypted_session: bytes = sio.fernet_encrypt.decrypt(
            encrypted_session)
        session = json.loads(decrypted_session.decode("utf-8"))

        user = User.get_by_id(session["user-id"])

        if "discord-access-token" in session:
            # TODO: test if the discord access token is still valid
            flask.session["DISCORD_OAUTH2_TOKEN"] = session[
                "discord-access-token"]
        sio.get_server().save_session(flask.request.sid, session)

        if session_id is not None:
            sio.join_game_session(
                GameSessionMembership.get_by_ids(user.id, session_id))

        return _create_client_side_session(sio, user)

    except (KeyError, peewee.DoesNotExist, json.JSONDecodeError):
        raise InvalidSession()

    except Exception:
        logger().exception("Error decoding user session")
        raise InvalidSession()
Exemplo n.º 2
0
def restore_user_session(sio: ServerApp, encrypted_session: bytes,
                         session_id: Optional[int]):
    try:
        decrypted_session: bytes = sio.fernet_encrypt.decrypt(
            encrypted_session)
        session = json.loads(decrypted_session.decode("utf-8"))

        if "discord-access-token" in session:
            user, result = _create_session_with_discord_token(
                sio, session["discord-access-token"])
        else:
            user = User.get_by_id(session["user-id"])
            sio.save_session(session)
            result = _create_client_side_session(sio, user)

        if session_id is not None:
            sio.join_game_session(
                GameSessionMembership.get_by_ids(user.id, session_id))

        return result

    except UserNotAuthorized:
        raise

    except (KeyError, peewee.DoesNotExist, json.JSONDecodeError,
            InvalidTokenError) as e:
        # InvalidTokenError: discord token expired and couldn't renew
        logger().info("Client at %s was unable to restore session: (%s) %s",
                      sio.current_client_ip(), str(type(e)), str(e))
        raise InvalidSession()

    except Exception:
        logger().exception("Error decoding user session")
        raise InvalidSession()
Exemplo n.º 3
0
def logout(sio: ServerApp):
    sio.leave_game_session()
    flask.session.pop("DISCORD_OAUTH2_TOKEN", None)
    with sio.session() as session:
        session.pop("discord-access-token", None)
        session.pop("user-id", None)

    _emit_user_session_update(sio)
Exemplo n.º 4
0
def disconnect_game_session(sio: ServerApp, session_id: int):
    current_user = sio.get_current_user()
    try:
        current_membership = GameSessionMembership.get_by_ids(
            current_user.id, session_id)
        current_membership.connection_state = "Offline"
        current_membership.save()
        _emit_session_meta_update(current_membership.session)
    except peewee.DoesNotExist:
        pass
    sio.leave_game_session()
Exemplo n.º 5
0
def _verify_has_admin(sio: ServerApp,
                      session_id: int,
                      admin_user_id: Optional[int],
                      *,
                      allow_when_no_admins: bool = False) -> None:
    """
    Checks if the logged user can do admin operations to the given session,
    :param session_id: The GameSessions id
    :param admin_user_id: An user id that is exceptionally authorized for this
    :param allow_when_no_admins: This action is authorized for non-admins if there are no admins.
    :return:
    """
    current_user = sio.get_current_user()
    try:
        current_membership = GameSessionMembership.get_by_ids(
            current_user.id, session_id)
    except peewee.DoesNotExist:
        raise NotAuthorizedForAction()

    if not (current_membership.admin or
            (admin_user_id is not None and current_user.id == admin_user_id)):
        if allow_when_no_admins and GameSessionMembership.select().where(
                GameSessionMembership.session == session_id,
                GameSessionMembership.admin == True).count() == 0:
            return
        raise NotAuthorizedForAction()
Exemplo n.º 6
0
def _duplicate_session(sio: ServerApp, session: GameSession, new_title: str):
    _verify_has_admin(sio, session.id, None)

    current_user = sio.get_current_user()
    _add_audit_entry(sio, session, f"Duplicated session as {new_title}")

    with database.db.atomic():
        new_session: GameSession = GameSession.create(
            name=new_title,
            password=session.password,
            creator=current_user,
            layout_description_json=session.layout_description_json,
            seed_hash=session.seed_hash,
            dev_features=session.dev_features,
        )
        for preset in session.presets:
            assert isinstance(preset, GameSessionPreset)
            GameSessionPreset.create(
                session=new_session,
                row=preset.row,
                preset=preset.preset,
            )
        GameSessionMembership.create(
            user=current_user,
            session=new_session,
            row=None,
            admin=True,
            connection_state="Offline",
        )
        GameSessionAudit.create(
            session=new_session,
            user=current_user,
            message=f"Duplicated from {session.name}",
        )
Exemplo n.º 7
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)
Exemplo n.º 8
0
def login_with_discord(sio: ServerApp, code: str):
    oauth = OAuth2Session(
        client_id=sio.app.config["DISCORD_CLIENT_ID"],
        scope=["identify"],
        redirect_uri=sio.app.config["DISCORD_REDIRECT_URI"],
    )
    access_token = oauth.fetch_token(
        "https://discord.com/api/oauth2/token",
        code=code,
        client_secret=sio.app.config["DISCORD_CLIENT_SECRET"],
    )

    flask.session["DISCORD_OAUTH2_TOKEN"] = access_token
    discord_user = sio.discord.fetch_user()

    user: User = User.get_or_create(discord_id=discord_user.id,
                                    defaults={"name": discord_user.name})[0]
    if user.name != discord_user.name:
        user.name = discord_user.name
        user.save()

    with sio.session() as session:
        session["user-id"] = user.id
        session["discord-access-token"] = access_token

    return _create_client_side_session(sio, user)
Exemplo n.º 9
0
def _create_client_side_session(sio: ServerApp, user: Optional[User]) -> dict:
    """

    :param user: If the session's user was already retrieved, pass it along to avoid an extra query.
    :return:
    """
    session = sio.get_session()
    encrypted_session = sio.fernet_encrypt.encrypt(
        json.dumps(session).encode("utf-8"))
    if user is None:
        user = User.get_by_id(session["user-id"])
    elif user.id != session["user-id"]:
        raise RuntimeError(f"Provided user does not match the session's user")

    logger().info(
        f"Client at {sio.current_client_ip()} is user {user.name} ({user.id})."
    )

    return {
        "user":
        user.as_json,
        "sessions": [
            membership.session.create_list_entry()
            for membership in GameSessionMembership.select().where(
                GameSessionMembership.user == user)
        ],
        "encoded_session_b85":
        base64.b85encode(encrypted_session),
    }
Exemplo n.º 10
0
def game_session_self_update(sio: ServerApp, session_id: int, inventory: str,
                             game_connection_state: str):
    current_user = sio.get_current_user()
    membership = GameSessionMembership.get_by_ids(current_user.id, session_id)

    membership.connection_state = f"Online, {game_connection_state}"
    membership.inventory = inventory
    membership.save()
    _emit_session_update(membership.session)
Exemplo n.º 11
0
def game_session_request_pickups(sio: ServerApp, session_id: int):
    current_user = sio.get_current_user()
    your_membership = GameSessionMembership.get_by_ids(current_user.id,
                                                       session_id)
    session: GameSession = your_membership.session

    if session.state == GameSessionState.SETUP:
        logger().info(f"Session {session_id}, Row {your_membership.row} "
                      f"requested pickups, but session is setup.")
        return None

    if your_membership.is_observer:
        logger().info(
            f"Session {session_id}, {current_user.name} requested pickups, but is an observer."
        )
        return None

    description = session.layout_description
    row_to_member_name = {
        member.row: member.effective_name
        for member in GameSessionMembership.non_observer_members(session)
    }

    resource_database = _get_resource_database(description,
                                               your_membership.row)

    result = []
    actions: List[GameSessionTeamAction] = list(
        _query_for_actions(your_membership))
    for action in actions:
        pickup_target = _get_pickup_target(description, action.provider_row,
                                           action.provider_location_index)

        if pickup_target is None:
            logging.error(
                f"Action {action} has a location index with nothing.")
            result.append(None)
        else:
            name = row_to_member_name.get(action.provider_row,
                                          f"Player {action.provider_row + 1}")
            result.append({
                "provider_name":
                name,
                "pickup":
                _base64_encode_pickup(pickup_target.pickup, resource_database),
            })

    logger().info(
        f"Session {session_id}, Row {your_membership.row} "
        f"requested pickups, returning {len(result)} elements for {resource_database.game_enum.value}."
    )

    return {
        "game": resource_database.game_enum.value,
        "pickups": result,
    }
Exemplo n.º 12
0
def create_game_session(sio: ServerApp, session_name: str):
    current_user = sio.get_current_user()

    with database.db.atomic():
        new_session = GameSession.create(
            name=session_name,
            password=None,
            creator=current_user,
        )
        GameSessionPreset.create(
            session=new_session,
            row=0,
            preset=json.dumps(PresetManager(None).default_preset.as_json))
        membership = GameSessionMembership.create(user=sio.get_current_user(),
                                                  session=new_session,
                                                  row=0,
                                                  admin=True)

    sio.join_game_session(membership)
    return new_session.create_session_entry()
Exemplo n.º 13
0
def game_session_request_update(sio: ServerApp, session_id: int):
    current_user = sio.get_current_user()
    session: GameSession = GameSession.get_by_id(session_id)
    membership = GameSessionMembership.get_by_ids(current_user.id, session_id)

    _emit_session_meta_update(session)
    if session.layout_description is not None:
        _emit_session_actions_update(session)

    if not membership.is_observer and session.state != GameSessionState.SETUP:
        _emit_game_session_pickups_update(sio, membership)

    _emit_session_audit_update(session)
Exemplo n.º 14
0
def join_game_session(sio: ServerApp, session_id: int,
                      password: Optional[str]):
    session = GameSession.get_by_id(session_id)

    if session.password is not None:
        if password is None or _hash_password(password) != session.password:
            raise WrongPassword()
    elif password is not None:
        raise WrongPassword()

    membership = GameSessionMembership.get_or_create(
        user=sio.get_current_user(),
        session=session,
        defaults={
            "row": None,
            "admin": False
        })[0]

    _emit_session_update(session)
    sio.join_game_session(membership)

    return session.create_session_entry()
Exemplo n.º 15
0
def server_app_fixture(flask_app, skip_qtbot):
    flask_app.config['SECRET_KEY'] = "key"
    flask_app.config["DISCORD_CLIENT_ID"] = 1234
    flask_app.config["DISCORD_CLIENT_SECRET"] = 5678
    flask_app.config[
        "DISCORD_REDIRECT_URI"] = "http://127.0.0.1:5000/callback/"
    flask_app.config[
        "FERNET_KEY"] = b's2D-pjBIXqEqkbeRvkapeDn82MgZXLLQGZLTgqqZ--A='
    flask_app.config[
        "GUEST_KEY"] = b's2D-pjBIXqEqkbeRvkapeDn82MgZXLLQGZLTgqqZ--A='
    server = ServerApp(flask_app)
    server.metrics.summary = MagicMock()
    server.metrics.summary.return_value.side_effect = lambda x: x
    return server
Exemplo n.º 16
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
Exemplo n.º 17
0
def server_app_fixture(flask_app):
    pytest.importorskip("engineio.async_drivers.threading")

    flask_app.config['SECRET_KEY'] = "key"
    flask_app.config["DISCORD_CLIENT_ID"] = 1234
    flask_app.config["DISCORD_CLIENT_SECRET"] = 5678
    flask_app.config["DISCORD_REDIRECT_URI"] = "http://127.0.0.1:5000/callback/"
    flask_app.config["FERNET_KEY"] = b's2D-pjBIXqEqkbeRvkapeDn82MgZXLLQGZLTgqqZ--A='
    flask_app.config["GUEST_KEY"] = b's2D-pjBIXqEqkbeRvkapeDn82MgZXLLQGZLTgqqZ--A='
    flask_app.config["ENFORCE_ROLE"] = None
    server = ServerApp(flask_app)
    server.metrics.summary = MagicMock()
    server.metrics.summary.return_value.side_effect = lambda x: x
    return server
Exemplo n.º 18
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()
Exemplo n.º 19
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}")
Exemplo n.º 20
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()
Exemplo n.º 21
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}")
Exemplo n.º 22
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)
Exemplo n.º 23
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}")
Exemplo n.º 24
0
def setup_app(sio: ServerApp):
    sio.on("login_with_discord", login_with_discord)
    sio.on("login_with_guest", login_with_guest)
    sio.on("restore_user_session", restore_user_session)
    sio.on("logout", logout)

    @sio.app.route("/login")
    def browser_login_with_discord():
        return sio.discord.create_session()

    @sio.app.route("/login_callback")
    def browser_discord_login_callback():
        sio.discord.callback()
        discord_user = sio.discord.fetch_user()

        user: User = User.get(discord_id=discord_user.id)
        if user is None:
            return "Unknown user", 404

        return flask.redirect(flask.url_for("browser_me"))

    @sio.admin_route("/me")
    def browser_me(user: User):
        return f"Hello {user.name}. Admin? {user.admin}"
Exemplo n.º 25
0
def _create_session_with_discord_token(sio: ServerApp,
                                       access_token: str) -> Tuple[User, dict]:
    flask.session["DISCORD_OAUTH2_TOKEN"] = access_token
    discord_user = sio.discord.fetch_user()

    user: User = User.get_or_create(discord_id=discord_user.id,
                                    defaults={"name": discord_user.name})[0]
    if user.name != discord_user.name:
        user.name = discord_user.name
        user.save()

    if sio.enforce_role is not None:
        if not sio.enforce_role.verify_user(discord_user.id):
            logger().info(
                "User %s is not authorized for connecting to the server",
                discord_user.name)
            raise UserNotAuthorized()

    with sio.session() as session:
        session["user-id"] = user.id
        session["discord-access-token"] = access_token

    return user, _create_client_side_session(sio, user)
Exemplo n.º 26
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)
Exemplo n.º 27
0
def setup_app(sio: ServerApp):
    sio.on("login_with_discord", login_with_discord)
    sio.on("login_with_guest", login_with_guest)
    sio.on("restore_user_session", restore_user_session)
    sio.on("logout", logout)
    sio.on("disconnect_game_session", disconnect_game_session)
Exemplo n.º 28
0
def disconnect_game_session(sio: ServerApp):
    sio.leave_game_session()
    _emit_user_session_update(sio)
Exemplo n.º 29
0
def setup_app(sio: ServerApp):
    sio.on("list_game_sessions", list_game_sessions, with_header_check=True)
    sio.on("create_game_session", create_game_session, with_header_check=True)
    sio.on("join_game_session", join_game_session, with_header_check=True)
    sio.on("disconnect_game_session", disconnect_game_session)
    sio.on("game_session_request_update", game_session_request_update)
    sio.on("game_session_admin_session", game_session_admin_session)
    sio.on("game_session_admin_player", game_session_admin_player)
    sio.on("game_session_collect_locations", game_session_collect_locations)
    sio.on("game_session_self_update", game_session_self_update)

    @sio.admin_route("/sessions")
    def admin_sessions(user):
        paginated_query = flask_utils.PaginatedQuery(
            GameSession.select().order_by(GameSession.creation_date.desc()),
            paginate_by=20,
            check_bounds=True,
        )

        lines = []
        for session in paginated_query.get_object_list():
            lines.append(
                "<tr><td><a href='{}'>{}</a></td><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>"
                .format(
                    flask.url_for('admin_session', session_id=session.id),
                    session.name,
                    session.creator.name,
                    session.creation_date,
                    session.state,
                    len(session.players),
                ))

        page = paginated_query.get_page()
        previous = "Previous"
        if page > 1:
            previous = "<a href='{}'>Previous</a>".format(
                flask.url_for(".admin_sessions", page=page - 1))

        next_link = "Next"
        if page < paginated_query.get_page_count():
            next_link = "<a href='{}'>Next</a>".format(
                flask.url_for(".admin_sessions", page=page + 1))

        return (
            "<table border='1'>"
            "<tr><th>Name</th><th>Creator</th><th>Creation Date</th><th>State</th><th>Num Players</th></tr>"
            "{content}</table>Page {page} of {num_pages}. {previous} / {next}."
        ).format(
            content="".join(lines),
            page=page,
            num_pages=paginated_query.get_page_count(),
            previous=previous,
            next=next_link,
        )

    @sio.admin_route("/session/<session_id>")
    def admin_session(user, session_id):
        session: GameSession = GameSession.get_by_id(session_id)

        rows = []
        presets = session.all_presets

        for player in session.players:
            player = typing.cast(GameSessionMembership, player)
            if player.is_observer:
                rows.append([
                    player.effective_name,
                    "Observer",
                    "",
                ])
            else:
                preset = presets[player.row]
                db = default_database.resource_database_for(preset.game)

                inventory = []
                if player.inventory is not None:
                    try:
                        parsed_inventory: list[dict] = BinaryInventory.parse(
                            player.inventory)
                    except construct.ConstructError:
                        # Handle old format in an adhoc way
                        # TODO 4.3: remove this code and purge all old inventory from the server db
                        items_by_id: dict[int, ItemResourceInfo] = {
                            item.extra["item_id"]: item
                            for item in db.item
                        }
                        parsed_inventory = [{
                            "name":
                            items_by_id[item["index"]].short_name,
                            **item,
                        } for item in OldBinaryInventory.parse(
                            player.inventory)]

                    for item in parsed_inventory:
                        if item["amount"] + item["capacity"] > 0:
                            inventory.append("{} x{}/{}".format(
                                db.get_item(item["name"]).long_name,
                                item["amount"], item["capacity"]))

                rows.append([
                    player.effective_name,
                    preset.name,
                    ", ".join(inventory),
                ])

        header = ["Name", "Preset", "Inventory"]

        return "<table border='1'><tr>{}</tr>{}</table>".format(
            "".join(f"<th>{h}</th>" for h in header),
            "".join("<tr>{}</tr>".format("".join(f"<td>{h}</td>" for h in r))
                    for r in rows),
        )
Exemplo n.º 30
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)