diff --git a/driftbase/api/players/__init__.py b/driftbase/api/players/__init__.py index bc9ddceb..756bf745 100644 --- a/driftbase/api/players/__init__.py +++ b/driftbase/api/players/__init__.py @@ -1,7 +1,7 @@ import logging import marshmallow as ma -from drift.core.extensions.jwt import current_user +from drift.core.extensions.jwt import current_user, requires_roles from drift.core.extensions.urlregistry import Endpoints from flask import g, url_for, current_app from flask.views import MethodView @@ -18,7 +18,7 @@ summary, tickets, ) -from driftbase.models.db import CorePlayer, MatchPlayer +from driftbase.models.db import CorePlayer, MatchPlayer, User from driftbase.players import get_playergroup_ids from driftbase.utils import url_player from driftbase.schemas.players import PlayerSchema @@ -29,9 +29,11 @@ endpoints = Endpoints() + def _get_shoutout(): return current_app.extensions["shoutout"] + def _get_db(): return g.db @@ -40,17 +42,13 @@ class PlayersListArgs(ma.Schema): class Meta: strict = True - player_id = ma.fields.List( - ma.fields.Integer(), metadata=dict(description="Player ID's to filter for" - )) + player_id = ma.fields.List(ma.fields.Integer(), metadata=dict(description="Player ID's to filter for")) rows = ma.fields.Integer(metadata=dict(description="Number of rows to return, maximum of 100")) player_group = ma.fields.String( - metadata=dict(description="The player group the players should belong to (see player-group api)" - )) - key = ma.fields.List(ma.fields.String(), metadata=dict(description="Only return these columns")) - player_name = ma.fields.String( - metadata=dict(description="Player name to search for") + metadata=dict(description="The player group the players should belong to (see player-group api)") ) + key = ma.fields.List(ma.fields.String(), metadata=dict(description="Only return these columns")) + player_name = ma.fields.String(metadata=dict(description="Player name to search for")) class PlayerPatchArgs(ma.Schema): @@ -223,6 +221,51 @@ def _patch(self, player_id, args): return my_player +@bp.route('//banned', endpoint='ban') +class PlayerAPI(MethodView): + class PlayerBanSchema(ma.Schema): + banned = ma.fields.Boolean() + + @bp.response(http_client.OK, PlayerBanSchema()) + def get(self, player_id): + """ + Query for ban status + """ + user = g.db.query(User).join(CorePlayer).filter(CorePlayer.player_id == player_id).first() + if user is None: + return abort(http_client.NOT_FOUND) + return dict(banned=(user.status == "banned")) + + @requires_roles("game_service,service") + @bp.response(http_client.OK, PlayerBanSchema()) + def post(self, player_id): + """ + Ban a player + """ + user = g.db.query(User).join(CorePlayer).filter(CorePlayer.player_id == player_id).first() + if user is None: + return abort(http_client.NOT_FOUND) + if user.status != "banned": + user.status = "banned" + g.db.commit() + current_app.shoutout.message("player:banned", player_id=player_id) + return dict(banned=True) + + @requires_roles("game_service,service") + def delete(self, player_id): + """ + Lift a ban from a player + """ + user = g.db.query(User).join(CorePlayer).filter(CorePlayer.player_id == player_id).first() + if user is None: + return abort(http_client.NOT_FOUND) + if user.status == "banned": + user.status = "active" + g.db.commit() + current_app.shoutout.message("player:unbanned", player_id=player_id) + return dict(banned=False) + + @endpoints.register def endpoint_info(current_user): template_player_gamestate_url = url_for("player_gamestate.entry", player_id=1337, namespace="namespace", _external=True) diff --git a/driftbase/auth/authenticate.py b/driftbase/auth/authenticate.py index d9dafc73..595272a4 100644 --- a/driftbase/auth/authenticate.py +++ b/driftbase/auth/authenticate.py @@ -177,6 +177,9 @@ def authenticate(username, password, automatic_account_creation=True, fallback_u player_name = "" if my_identity.user_id: my_user = g.db.query(User).get(my_identity.user_id) + if my_user.status == "banned": + log.info(f"Logon identity is using a banned user {my_user.user_id}") + abort_unauthorized("User is banned") if my_user.status != "active": log.info(f"Logon identity is using an inactive user {my_user.user_id}, creating new one") my_user = None @@ -231,10 +234,11 @@ def authenticate(username, password, automatic_account_creation=True, fallback_u log.info(f"Player for user {my_user.user_id} has been created with player_id {my_player.player_id}" f" and uuid {my_player.player_uuid}") if "player" in user_roles: - current_app.extensions.get('shoutout').message("player_created", user_id=user_id, username=username, - player_id=my_player.player_id, - player_identity=my_identity.identity_id, - player_uuid=my_player.player_uuid.hex) + message_data = dict(user_id=user_id, username=username, player_id=my_player.player_id, + player_identity=my_identity.identity_id, player_uuid=my_player.player_uuid.hex) + current_app.shoutout.message("player:player_created", **message_data) + # Remove this next line once all services have been updated to listen to the 'player' topic event + current_app.shoutout.message("player_created", **message_data) if my_player: if my_player.player_uuid is None: @@ -273,7 +277,9 @@ def authenticate(username, password, automatic_account_creation=True, fallback_u player_identities.append( dict(identity_id=identity.identity_id, identity_type=identity.identity_type, name=identity.name)) message_data['identities'] = player_identities - current_app.extensions.get('shoutout').message("player_login", **message_data) + current_app.shoutout.message("player:player_login", **message_data) + # Remove this next line once all services have been updated to listen to the 'player' topic event + current_app.shoutout.message("player_login", **message_data) return ret diff --git a/tests/players/test_players.py b/tests/players/test_players.py index 2038d25f..839b2687 100644 --- a/tests/players/test_players.py +++ b/tests/players/test_players.py @@ -223,3 +223,32 @@ def test_players_keys(self): r = self.get(url) self.assertIsNotNone(r.json()[0]["player_id"]) self.assertIsNotNone(r.json()[0]["player_name"]) + + +class PlayersBanTest(BaseCloudkitTest): + def test_get_ban_status(self): + self.auth("test_get_ban_status") + r = self.get(self.endpoints["my_player"] + "/banned").json() + self.assertFalse(r["banned"]) + + def test_ban_player(self): + self.auth("test_ban_player") + ban_url = self.endpoints["players"] + f"/{self.player_id}/banned" + # Test banning fails without game_service role + self.post(ban_url, expected_status_code=http_client.FORBIDDEN) + with self.as_game_service(): + r = self.post(ban_url).json() + self.assertTrue(r["banned"]) + + def test_unban_player(self): + self.auth("test_unban_player") + ban_url = self.endpoints["players"] + f"/{self.player_id}/banned" + with self.as_game_service(): + self.post(ban_url) + r = self.get(ban_url).json() + self.assertTrue(r["banned"]) + # Test unbanning fails without game_service role + self.delete(ban_url, expected_status_code=http_client.FORBIDDEN) + with self.as_game_service(): + r = self.delete(ban_url).json() + self.assertFalse(r["banned"])