From 61edbf8419c9e792a3243f5fd96948cb8974add2 Mon Sep 17 00:00:00 2001 From: Jonas Weinz Date: Mon, 1 Apr 2019 16:13:44 +0200 Subject: [PATCH] session, user and accounts revokation --- README.md | 1 + connection_handler.py | 40 ++++++++++++++++++++++++------ database_connection.py | 7 ++++++ main.py | 8 +++--- match.py | 12 ++++++++- match_manager.py | 56 +++++++++++++++++++++++++++++++++--------- session_manager.py | 6 ++++- settings.py | 9 +++++++ user_manager.py | 18 ++++++++++++-- 9 files changed, 131 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 8a665cf..a33b5d3 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ communication with the web client is done by a (far from any standard and almost "type": "match_update", "data": { "id": "...", + "revoke_time": , "match_state": > } } diff --git a/connection_handler.py b/connection_handler.py index b03200d..1a02e68 100644 --- a/connection_handler.py +++ b/connection_handler.py @@ -8,6 +8,8 @@ from match import Match from tools import debug, elo_p_win, elo_update +import datetime + import re @@ -60,10 +62,13 @@ class ConnectionHandler(object): def __init__(self, session_manager: SessionManager, user_manager: UserManager, - match_manager: MatchManager): + match_manager: MatchManager, + revoke_check_timedelta): self.session_manager = session_manager self.user_manager = user_manager self.match_manager = match_manager + self.revoke_check_timedelta = revoke_check_timedelta + self.lastCheck = datetime.datetime.now() self.open_connections_by_user = {} self.open_connections_by_id = {} @@ -259,6 +264,7 @@ class ConnectionHandler(object): "type": "match_update", "data": { "id": m.id, + "revoke_time": m.revoke_time, "match_state": state } } @@ -271,6 +277,7 @@ class ConnectionHandler(object): "type": "match_update", "data": { "id": m.id, + "revoke_time": m.revoke_time, "match_state": state } } @@ -427,6 +434,7 @@ class ConnectionHandler(object): "type": "match_update", "data": { "id": db_match['id'], + "revoke_time": match.get_sql_revoke_time(), "match_state": json.loads(match.to_json_state()) } })) @@ -460,6 +468,7 @@ class ConnectionHandler(object): 'type': 'match_update', 'data': { 'id': match.id, + 'revoke_time': match.get_sql_revoke_time(), 'match_state': json.loads(match_state) } })) @@ -477,6 +486,7 @@ class ConnectionHandler(object): 'type': 'match_update', 'data': { 'id': match.id, + 'revoke_time': match.get_sql_revoke_time(), 'match_state': json.loads(match_state) } })) @@ -536,6 +546,7 @@ class ConnectionHandler(object): 'type': 'match_update', 'data': { 'id': match_id, + 'revoke_time': match.get_sql_revoke_time(), 'match_state': json.loads(match_state) } }) @@ -551,13 +562,17 @@ class ConnectionHandler(object): match_state = None if match is not None: match_state = match.to_json_state() + + if match_state is not None: - await conn.send(json.dumps({ - 'type': 'match_update', - 'data': { - 'match_state': json.loads(match_state) - } - })) + await conn.send(json.dumps({ + 'type': 'match_update', + 'data': { + 'id': match.id, + 'revoke_time': match.get_sql_revoke_time(), + 'match_state': json.loads(match_state) + } + })) async def _on_friend_request(self, conn, data): msg = "error in handling friend request" @@ -654,6 +669,14 @@ class ConnectionHandler(object): async def disconnect(self, conn): self._del_connection(conn) + async def revoke_check(self): + now = datetime.datetime.now() + if self.lastCheck + self.revoke_check_timedelta < now: + now = datetime.datetime.now() + self.match_manager.check_matches_lifespan() + self.session_manager.revoke_inactive_sessions() + self.user_manager.revoke_inactive_accounts() + async def handle_message(self, conn, msg_str): msg = parse_message(msg_str) @@ -665,6 +688,7 @@ class ConnectionHandler(object): t = msg['type'] self.user_manager.touch_user(conn.user_name) + self.session_manager.touch_session(conn.id) if t == "match_request": await self._on_match_req(conn, msg['data']) @@ -685,3 +709,5 @@ class ConnectionHandler(object): else: debug("could not interpret message: " + msg_str) + + await self.revoke_check() diff --git a/database_connection.py b/database_connection.py index 2df6201..f8667b3 100644 --- a/database_connection.py +++ b/database_connection.py @@ -35,8 +35,14 @@ class DatabaseConnection(object): assert DatabaseConnection.instance is not None DatabaseConnection.instance.commit() + @staticmethod + def global_ping(): + assert DatabaseConnection.instance is not None + DatabaseConnection.instance.connection.ping() + @staticmethod def global_single_query(query, params=None): + DatabaseConnection.global_ping() if ';' in query: # Possible injection! raise SQLInjectionError() @@ -51,6 +57,7 @@ class DatabaseConnection(object): @staticmethod def global_single_execution(sql_statement, params=None): + DatabaseConnection.global_ping() if ';' in sql_statement: # Possible injection detected! raise SQLInjectionError() diff --git a/main.py b/main.py index 2c1137e..d424429 100644 --- a/main.py +++ b/main.py @@ -14,10 +14,10 @@ import datetime from tools import debug -um = UserManager() -sm = SessionManager(datetime.timedelta(hours=12)) -mm = MatchManager(user_manager=um) -ch = ConnectionHandler(sm, um, mm) +um = UserManager(account_revoke_time) +sm = SessionManager(session_revove_time) +mm = MatchManager(um, match_revoke_time) +ch = ConnectionHandler(sm, um, mm, revoke_check_interval) DatabaseConnection(db_host, diff --git a/match.py b/match.py index 7d74ccc..cd55e59 100644 --- a/match.py +++ b/match.py @@ -5,6 +5,8 @@ import base64 from tools import debug +from database_connection import get_sql_time + # decoder/encoder took from: https://stackoverflow.com/a/19271311 @@ -36,7 +38,7 @@ class Match(object): FIELD_USER_B = 2 FIELD_DRAW = 3 - def __init__(self, n, match_id, player_a_name, player_b_name, json_state=None): + def __init__(self, n, match_id, revoke_time, player_a_name, player_b_name, json_state=None): self.n = n self.id = match_id self.complete_field = np.zeros(shape=(n*n, n*n), dtype=int) @@ -48,9 +50,13 @@ class Match(object): self.is_player_a = True self.player_a_name = player_a_name self.player_b_name = player_b_name + self.revoke_time = revoke_time if json_state is not None: self.from_json_state(json_state) + + def get_sql_revoke_time(self): + return get_sql_time(self.revoke_time) def from_json_state(self, json_state): match_obj = json.loads(json_state) @@ -155,6 +161,10 @@ class Match(object): return is_col or is_row or is_main_diag or is_sec_diag def move(self, move_dict): + if self.game_over: + debug("invalid move") + return False + sub_x = int(move_dict['sub_x']) sub_y = int(move_dict['sub_y']) x = int(move_dict['x']) diff --git a/match_manager.py b/match_manager.py index 8c66de2..9e3b77a 100644 --- a/match_manager.py +++ b/match_manager.py @@ -10,16 +10,18 @@ from tools import debug, elo_p_win, elo_update class MatchManager(object): - def __init__(self, user_manager: UserManager = None): + def __init__(self, user_manager: UserManager, match_lifespan_timedelta: datetime.timedelta): self.user_manager = user_manager - pass + self.match_lifespan_timedelta = match_lifespan_timedelta def get_match(self, id: str) -> Match: query = "SELECT * FROM matches WHERE id=%s" result = DatabaseConnection.global_single_query(query, (id)) if len(result) == 0: return None - match = Match(n=settings.n, match_id=id, player_a_name=result[0]['user_a'], + + revoke_time = result[0]['last_active'] + self.match_lifespan_timedelta + match = Match(n=settings.n, match_id=id, revoke_time=revoke_time, player_a_name=result[0]['user_a'], player_b_name=result[0]['user_b'], json_state=result[0]['match_state']) return match @@ -33,19 +35,22 @@ class MatchManager(object): if len(DatabaseConnection.global_single_query("SELECT id FROM matches WHERE id=%s", (match_id))) > 0: return self.create_new_match(user_a, user_b) - match = Match(n=settings.n, match_id=match_id, - player_a_name=user_a, player_b_name=user_b) now = datetime.datetime.now() + match = Match(n=settings.n, match_id=match_id, revoke_time=now + self.match_lifespan_timedelta, + player_a_name=user_a, player_b_name=user_b) + query = "INSERT INTO matches (id, user_a, user_b, match_state, active_user, last_active) VALUES (%s, %s, %s, %s, %s,%s)" DatabaseConnection.global_single_execution( query, (match_id, user_a, user_b, match.to_json_state(), match.get_current_player(), get_sql_time(now))) return match - def update_match(self, match_id: str, match: Match) -> None: - now = get_sql_time(datetime.datetime.now()) - query = "UPDATE matches SET match_state=%s, active_user=%s, last_active=%s WHERE id=%s" - DatabaseConnection.global_single_execution( - query, (match.to_json_state(), match.get_current_player(), now, match_id)) + def update_match(self, match_id: str, match: Match, update_in_db=True) -> None: + + if (update_in_db): + now = get_sql_time(datetime.datetime.now()) + query = "UPDATE matches SET match_state=%s, active_user=%s, last_active=%s WHERE id=%s" + DatabaseConnection.global_single_execution( + query, (match.to_json_state(), match.get_current_player(), now, match_id)) # check whether we have to update the elo values (game over triggered by the last move) if match.game_over: @@ -66,7 +71,7 @@ class MatchManager(object): self.user_manager.update_elo(player_won, new_elo_won) self.user_manager.update_elo(player_lost, new_elo_lost) - + debug( f"Match {match_id} is won by {player_won} over {player_lost}. Update elo-rankings: {elo_won}->{new_elo_won} and {elo_lost}->{new_elo_lost}") @@ -111,3 +116,32 @@ class MatchManager(object): def delete_match(self, match_id: str) -> None: query = "DELETE FROM matches WHERE id=%s" DatabaseConnection.global_single_execution(query, (match_id)) + + def check_matches_lifespan(self): + time_now = datetime.datetime.now() + time_revoke = time_now - self.match_lifespan_timedelta + + query = "SELECT * FROM matches WHERE last_active < %s" + tmp = DatabaseConnection.global_single_query( + query, (get_sql_time(time_revoke))) + + for entry in tmp: + revoke_time = entry['last_active'] + self.match_lifespan_timedelta + match = Match(n=settings.n, match_id=entry['id'], revoke_time=revoke_time, player_a_name=entry['user_a'], + player_b_name=entry['user_b'], json_state=entry['match_state']) + + if (not match.game_over): + match.game_over = True + + # check who is the current player who did not make the move in time: + match.player_won = match.player_b_name if match.is_player_a else match.player_a_name + + self.update_match(entry['id'], match, False) + + # delete matches from db: + + debug("deleting revoked sessions: " + str(tmp)) + + query = "DELETE FROM matches WHERE last_active < %s" + tmp = DatabaseConnection.global_single_execution( + query, (get_sql_time(time_revoke))) diff --git a/session_manager.py b/session_manager.py index 9b6d4c1..50a578f 100644 --- a/session_manager.py +++ b/session_manager.py @@ -3,6 +3,8 @@ from user import User import datetime import uuid +from tools import debug + class SessionManager(object): def __init__(self, session_lifespan_timedelta): @@ -65,4 +67,6 @@ class SessionManager(object): revoked_sessions = DatabaseConnection.global_single_query(query, (get_sql_time(revoke_time))) query = "DELETE FROM sessions WHERE last_seen < %s" DatabaseConnection.global_single_execution(query, (get_sql_time(revoke_time))) - return revoked_sessions + debug("delete revoked sessions: " + str(revoked_sessions)) + + diff --git a/settings.py b/settings.py index edc7d75..ff804e3 100644 --- a/settings.py +++ b/settings.py @@ -1,3 +1,5 @@ +import datetime + cert_file = None key_file = None @@ -15,5 +17,12 @@ db_charset = 'utf8mb4' elo_start_value = 1000 elo_default_k = 20 +# revoke times: +account_revoke_time = datetime.timedelta(days=45) +session_revove_time = datetime.timedelta(days=20) +match_revoke_time = datetime.timedelta(days=7) + +revoke_check_interval = datetime.timedelta(hours=1) + # field dimension (warning: this constant is not constantly used) n = 3 diff --git a/user_manager.py b/user_manager.py index 2cb1008..89ea5e8 100644 --- a/user_manager.py +++ b/user_manager.py @@ -1,13 +1,16 @@ -from database_connection import DatabaseConnection, SQLInjectionError +from database_connection import DatabaseConnection, SQLInjectionError, get_sql_time from user import User import datetime import hashlib import uuid from settings import elo_start_value +from tools import debug + class UserManager(object): - def __init__(self): + def __init__(self, account_lifespan_timedelta): + self.account_lifespan_timedelta = account_lifespan_timedelta pass def get_user(self, user_name): @@ -134,3 +137,14 @@ class UserManager(object): def get_all_users(self): query = "SELECT name, last_seen FROM users" return DatabaseConnection.global_single_query(query) + + def revoke_inactive_accounts(self): + revoke_time = datetime.datetime.now() - self.account_lifespan_timedelta + query = "SELECT * from users WHERE last_seen < %s" + revoked_sessions = DatabaseConnection.global_single_query(query, (get_sql_time(revoke_time))) + + for entry in revoked_sessions: + name = entry['name'] + self.delete_user(name) + + debug("deleted users: " + str(revoked_sessions))