diff --git a/server/exceptions.py b/server/exceptions.py index 5c5a103d6..48095d2a4 100644 --- a/server/exceptions.py +++ b/server/exceptions.py @@ -34,7 +34,9 @@ def __init__(self, ban_expiry, ban_reason, *args, **kwargs): def message(self): return ( f"You are banned from FAF {self._ban_duration_text()}.
" - f"Reason:
{self.ban_reason}" + f"Reason:
{self.ban_reason}

" + "If you would like to appeal this ban, please send an email to: " + "moderation@faforever.com" ) def _ban_duration_text(self): diff --git a/server/lobbyconnection.py b/server/lobbyconnection.py index c536c758d..e69613c89 100644 --- a/server/lobbyconnection.py +++ b/server/lobbyconnection.py @@ -516,16 +516,37 @@ async def command_auth(self, message): async with self._db.acquire() as conn: result = await conn.execute( - select(t_login.c.login) + select( + t_login.c.login, + lobby_ban.c.reason, + lobby_ban.c.expires_at + ) + .select_from(t_login.outerjoin(lobby_ban)) .where(t_login.c.id == player_id) + .order_by(lobby_ban.c.expires_at.desc()) ) row = result.fetchone() if not row: - self._logger.warning("User id not found in database possible fraudulent token: %s", player_id) + self._logger.warning( + "User id %s not found in database! Possible fraudulent " + "token: %s", + player_id, + token + ) raise AuthenticationError("Cannot find user id", auth_method) username = row.login + ban_reason = row.reason + ban_expiry = row.expires_at + + now = datetime.utcnow() + if ban_reason is not None and now < ban_expiry: + self._logger.debug( + "Rejected login from banned user: %s, %s, %s", + player_id, username, self.session + ) + raise BanError(ban_expiry, ban_reason) # DEPRECATED: IRC passwords are handled outside of the lobby server. # This message remains here for backwards compatibility, but the data diff --git a/tests/integration_tests/test_login.py b/tests/integration_tests/test_login.py index ee1e7c77b..179d71214 100644 --- a/tests/integration_tests/test_login.py +++ b/tests/integration_tests/test_login.py @@ -11,7 +11,7 @@ ) -async def test_server_invalid_login(lobby_server): +async def test_server_login_invalid(lobby_server): proto = await connect_client(lobby_server) # Try a user that doesn't exist await perform_login(proto, ("Cat", "epic")) @@ -39,7 +39,46 @@ async def test_server_ban(lobby_server, user): assert msg == { "command": "notice", "style": "error", - "text": "You are banned from FAF forever.
Reason:
Test permanent ban" + "text": ( + "You are banned from FAF forever.
Reason:
Test permanent ban" + "

If you would like to appeal this ban, please send an " + "email to: moderation@faforever.com" + ) + } + + +@pytest.mark.parametrize("user", [ + ("Dostya", 2), + ("ban_long_time", 203) +]) +async def test_server_ban_token(lobby_server, user, jwk_priv_key, jwk_kid): + user_name, user_id = user + proto = await connect_client(lobby_server) + await proto.send_message({ + "command": "auth", + "version": "1.0.0-dev", + "user_agent": "faf-client", + "token": jwt.encode({ + "sub": user_id, + "user_name": user_name, + "scp": ["lobby"], + "exp": int(time() + 1000), + "authorities": [], + "non_locked": True, + "jti": "", + "client_id": "" + }, jwk_priv_key, algorithm="RS256", headers={"kid": jwk_kid}), + "unique_id": "some_id" + }) + msg = await proto.read_message() + assert msg == { + "command": "notice", + "style": "error", + "text": ( + "You are banned from FAF forever.
Reason:
Test permanent ban" + "

If you would like to appeal this ban, please send an " + "email to: moderation@faforever.com" + ) } @@ -53,7 +92,7 @@ async def test_server_ban_revoked_or_expired(lobby_server, user): assert msg["login"] == user -async def test_server_valid_login(lobby_server): +async def test_server_login_valid(lobby_server): proto = await connect_client(lobby_server) await perform_login(proto, ("Rhiza", "puff_the_magic_dragon")) msg = await proto.read_message() @@ -98,7 +137,7 @@ async def test_server_valid_login(lobby_server): } -async def test_server_valid_login_admin(lobby_server): +async def test_server_login_valid_admin(lobby_server): proto = await connect_client(lobby_server) await perform_login(proto, ("test", "test_password")) msg = await proto.read_message() @@ -143,7 +182,7 @@ async def test_server_valid_login_admin(lobby_server): } -async def test_server_valid_login_moderator(lobby_server): +async def test_server_login_valid_moderator(lobby_server): proto = await connect_client(lobby_server) await perform_login(proto, ("moderator", "moderator")) msg = await proto.read_message() @@ -202,7 +241,7 @@ async def test_policy_server_contacted(lobby_server, policy_server, player_servi policy_server.verify.assert_called_once() -async def test_server_double_login(lobby_server): +async def test_server_login_double(lobby_server): proto = await connect_client(lobby_server) await perform_login(proto, ("test", "test_password")) msg = await proto.read_message() @@ -222,7 +261,7 @@ async def test_server_double_login(lobby_server): } -async def test_server_valid_login_with_token(lobby_server, jwk_priv_key, jwk_kid): +async def test_server_login_token_valid(lobby_server, jwk_priv_key, jwk_kid): proto = await connect_client(lobby_server) await proto.send_message({ "command": "auth", @@ -285,7 +324,7 @@ async def test_server_valid_login_with_token(lobby_server, jwk_priv_key, jwk_kid } -async def test_server_login_bad_id_in_token(lobby_server, jwk_priv_key, jwk_kid): +async def test_server_login_token_bad_id(lobby_server, jwk_priv_key, jwk_kid): proto = await connect_client(lobby_server) await proto.send_message({ "command": "auth", @@ -311,7 +350,7 @@ async def test_server_login_bad_id_in_token(lobby_server, jwk_priv_key, jwk_kid) } -async def test_server_login_expired_token(lobby_server, jwk_priv_key, jwk_kid): +async def test_server_login_token_expired(lobby_server, jwk_priv_key, jwk_kid): proto = await connect_client(lobby_server) await proto.send_message({ "command": "auth", @@ -333,7 +372,7 @@ async def test_server_login_expired_token(lobby_server, jwk_priv_key, jwk_kid): } -async def test_server_login_malformed_token(lobby_server, jwk_priv_key, jwk_kid): +async def test_server_login_token_malformed(lobby_server, jwk_priv_key, jwk_kid): """This scenario could only happen if the hydra signed a token that was missing critical data""" proto = await connect_client(lobby_server) @@ -355,7 +394,11 @@ async def test_server_login_malformed_token(lobby_server, jwk_priv_key, jwk_kid) } -async def test_server_login_lobby_scope_missing(lobby_server, jwk_priv_key, jwk_kid): +async def test_server_login_token_lobby_scope_missing( + lobby_server, + jwk_priv_key, + jwk_kid, +): """This scenario could only happen if the hydra signed a token that was missing critical data""" proto = await connect_client(lobby_server) diff --git a/tests/integration_tests/test_server.py b/tests/integration_tests/test_server.py index a1199e2d9..3f2fe7a7b 100644 --- a/tests/integration_tests/test_server.py +++ b/tests/integration_tests/test_server.py @@ -793,7 +793,11 @@ async def test_server_ban_prevents_hosting(lobby_server, database, command): assert msg == { "command": "notice", "style": "error", - "text": "You are banned from FAF forever.
Reason:
Test live ban" + "text": ( + "You are banned from FAF forever.
Reason:
Test live ban
" + "
If you would like to appeal this ban, please send an email " + "to: moderation@faforever.com" + ) } diff --git a/tests/unit_tests/test_lobbyconnection.py b/tests/unit_tests/test_lobbyconnection.py index 61af68d68..48e812ecc 100644 --- a/tests/unit_tests/test_lobbyconnection.py +++ b/tests/unit_tests/test_lobbyconnection.py @@ -1076,8 +1076,11 @@ async def test_abort_connection_if_banned( lobbyconnection.player.id = 203 with pytest.raises(BanError) as banned_error: await lobbyconnection.abort_connection_if_banned() - assert banned_error.value.message() == \ + assert banned_error.value.message() == ( "You are banned from FAF forever.
Reason:
Test permanent ban" + "

If you would like to appeal this ban, please send an email " + "to: moderation@faforever.com" + ) # test user who is banned for another 46 hours lobbyconnection.player.id = 204