session, user and accounts revokation

This commit is contained in:
Jonas Weinz 2019-04-01 16:13:44 +02:00
parent 392c38f09f
commit 61edbf8419
9 changed files with 131 additions and 26 deletions

View File

@ -38,6 +38,7 @@ communication with the web client is done by a (far from any standard and almost
"type": "match_update", "type": "match_update",
"data": { "data": {
"id": "...", "id": "...",
"revoke_time": <revoke_time>,
"match_state": <null| <match_state>> "match_state": <null| <match_state>>
} }
} }

View File

@ -8,6 +8,8 @@ from match import Match
from tools import debug, elo_p_win, elo_update from tools import debug, elo_p_win, elo_update
import datetime
import re import re
@ -60,10 +62,13 @@ class ConnectionHandler(object):
def __init__(self, def __init__(self,
session_manager: SessionManager, session_manager: SessionManager,
user_manager: UserManager, user_manager: UserManager,
match_manager: MatchManager): match_manager: MatchManager,
revoke_check_timedelta):
self.session_manager = session_manager self.session_manager = session_manager
self.user_manager = user_manager self.user_manager = user_manager
self.match_manager = match_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_user = {}
self.open_connections_by_id = {} self.open_connections_by_id = {}
@ -259,6 +264,7 @@ class ConnectionHandler(object):
"type": "match_update", "type": "match_update",
"data": { "data": {
"id": m.id, "id": m.id,
"revoke_time": m.revoke_time,
"match_state": state "match_state": state
} }
} }
@ -271,6 +277,7 @@ class ConnectionHandler(object):
"type": "match_update", "type": "match_update",
"data": { "data": {
"id": m.id, "id": m.id,
"revoke_time": m.revoke_time,
"match_state": state "match_state": state
} }
} }
@ -427,6 +434,7 @@ class ConnectionHandler(object):
"type": "match_update", "type": "match_update",
"data": { "data": {
"id": db_match['id'], "id": db_match['id'],
"revoke_time": match.get_sql_revoke_time(),
"match_state": json.loads(match.to_json_state()) "match_state": json.loads(match.to_json_state())
} }
})) }))
@ -460,6 +468,7 @@ class ConnectionHandler(object):
'type': 'match_update', 'type': 'match_update',
'data': { 'data': {
'id': match.id, 'id': match.id,
'revoke_time': match.get_sql_revoke_time(),
'match_state': json.loads(match_state) 'match_state': json.loads(match_state)
} }
})) }))
@ -477,6 +486,7 @@ class ConnectionHandler(object):
'type': 'match_update', 'type': 'match_update',
'data': { 'data': {
'id': match.id, 'id': match.id,
'revoke_time': match.get_sql_revoke_time(),
'match_state': json.loads(match_state) 'match_state': json.loads(match_state)
} }
})) }))
@ -536,6 +546,7 @@ class ConnectionHandler(object):
'type': 'match_update', 'type': 'match_update',
'data': { 'data': {
'id': match_id, 'id': match_id,
'revoke_time': match.get_sql_revoke_time(),
'match_state': json.loads(match_state) 'match_state': json.loads(match_state)
} }
}) })
@ -551,13 +562,17 @@ class ConnectionHandler(object):
match_state = None match_state = None
if match is not None: if match is not None:
match_state = match.to_json_state() match_state = match.to_json_state()
if match_state is not None:
await conn.send(json.dumps({ await conn.send(json.dumps({
'type': 'match_update', 'type': 'match_update',
'data': { 'data': {
'match_state': json.loads(match_state) 'id': match.id,
} 'revoke_time': match.get_sql_revoke_time(),
})) 'match_state': json.loads(match_state)
}
}))
async def _on_friend_request(self, conn, data): async def _on_friend_request(self, conn, data):
msg = "error in handling friend request" msg = "error in handling friend request"
@ -654,6 +669,14 @@ class ConnectionHandler(object):
async def disconnect(self, conn): async def disconnect(self, conn):
self._del_connection(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): async def handle_message(self, conn, msg_str):
msg = parse_message(msg_str) msg = parse_message(msg_str)
@ -665,6 +688,7 @@ class ConnectionHandler(object):
t = msg['type'] t = msg['type']
self.user_manager.touch_user(conn.user_name) self.user_manager.touch_user(conn.user_name)
self.session_manager.touch_session(conn.id)
if t == "match_request": if t == "match_request":
await self._on_match_req(conn, msg['data']) await self._on_match_req(conn, msg['data'])
@ -685,3 +709,5 @@ class ConnectionHandler(object):
else: else:
debug("could not interpret message: " + msg_str) debug("could not interpret message: " + msg_str)
await self.revoke_check()

View File

@ -35,8 +35,14 @@ class DatabaseConnection(object):
assert DatabaseConnection.instance is not None assert DatabaseConnection.instance is not None
DatabaseConnection.instance.commit() DatabaseConnection.instance.commit()
@staticmethod
def global_ping():
assert DatabaseConnection.instance is not None
DatabaseConnection.instance.connection.ping()
@staticmethod @staticmethod
def global_single_query(query, params=None): def global_single_query(query, params=None):
DatabaseConnection.global_ping()
if ';' in query: if ';' in query:
# Possible injection! # Possible injection!
raise SQLInjectionError() raise SQLInjectionError()
@ -51,6 +57,7 @@ class DatabaseConnection(object):
@staticmethod @staticmethod
def global_single_execution(sql_statement, params=None): def global_single_execution(sql_statement, params=None):
DatabaseConnection.global_ping()
if ';' in sql_statement: if ';' in sql_statement:
# Possible injection detected! # Possible injection detected!
raise SQLInjectionError() raise SQLInjectionError()

View File

@ -14,10 +14,10 @@ import datetime
from tools import debug from tools import debug
um = UserManager() um = UserManager(account_revoke_time)
sm = SessionManager(datetime.timedelta(hours=12)) sm = SessionManager(session_revove_time)
mm = MatchManager(user_manager=um) mm = MatchManager(um, match_revoke_time)
ch = ConnectionHandler(sm, um, mm) ch = ConnectionHandler(sm, um, mm, revoke_check_interval)
DatabaseConnection(db_host, DatabaseConnection(db_host,

View File

@ -5,6 +5,8 @@ import base64
from tools import debug from tools import debug
from database_connection import get_sql_time
# decoder/encoder took from: https://stackoverflow.com/a/19271311 # decoder/encoder took from: https://stackoverflow.com/a/19271311
@ -36,7 +38,7 @@ class Match(object):
FIELD_USER_B = 2 FIELD_USER_B = 2
FIELD_DRAW = 3 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.n = n
self.id = match_id self.id = match_id
self.complete_field = np.zeros(shape=(n*n, n*n), dtype=int) 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.is_player_a = True
self.player_a_name = player_a_name self.player_a_name = player_a_name
self.player_b_name = player_b_name self.player_b_name = player_b_name
self.revoke_time = revoke_time
if json_state is not None: if json_state is not None:
self.from_json_state(json_state) 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): def from_json_state(self, json_state):
match_obj = json.loads(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 return is_col or is_row or is_main_diag or is_sec_diag
def move(self, move_dict): def move(self, move_dict):
if self.game_over:
debug("invalid move")
return False
sub_x = int(move_dict['sub_x']) sub_x = int(move_dict['sub_x'])
sub_y = int(move_dict['sub_y']) sub_y = int(move_dict['sub_y'])
x = int(move_dict['x']) x = int(move_dict['x'])

View File

@ -10,16 +10,18 @@ from tools import debug, elo_p_win, elo_update
class MatchManager(object): 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 self.user_manager = user_manager
pass self.match_lifespan_timedelta = match_lifespan_timedelta
def get_match(self, id: str) -> Match: def get_match(self, id: str) -> Match:
query = "SELECT * FROM matches WHERE id=%s" query = "SELECT * FROM matches WHERE id=%s"
result = DatabaseConnection.global_single_query(query, (id)) result = DatabaseConnection.global_single_query(query, (id))
if len(result) == 0: if len(result) == 0:
return None 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']) player_b_name=result[0]['user_b'], json_state=result[0]['match_state'])
return match 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: 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) 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() 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)" 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( DatabaseConnection.global_single_execution(
query, (match_id, user_a, user_b, match.to_json_state(), match.get_current_player(), get_sql_time(now))) query, (match_id, user_a, user_b, match.to_json_state(), match.get_current_player(), get_sql_time(now)))
return match return match
def update_match(self, match_id: str, match: Match) -> None: def update_match(self, match_id: str, match: Match, update_in_db=True) -> None:
now = get_sql_time(datetime.datetime.now())
query = "UPDATE matches SET match_state=%s, active_user=%s, last_active=%s WHERE id=%s" if (update_in_db):
DatabaseConnection.global_single_execution( now = get_sql_time(datetime.datetime.now())
query, (match.to_json_state(), match.get_current_player(), now, match_id)) 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) # check whether we have to update the elo values (game over triggered by the last move)
if match.game_over: 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_won, new_elo_won)
self.user_manager.update_elo(player_lost, new_elo_lost) self.user_manager.update_elo(player_lost, new_elo_lost)
debug( 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}") 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: def delete_match(self, match_id: str) -> None:
query = "DELETE FROM matches WHERE id=%s" query = "DELETE FROM matches WHERE id=%s"
DatabaseConnection.global_single_execution(query, (match_id)) 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)))

View File

@ -3,6 +3,8 @@ from user import User
import datetime import datetime
import uuid import uuid
from tools import debug
class SessionManager(object): class SessionManager(object):
def __init__(self, session_lifespan_timedelta): 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))) revoked_sessions = DatabaseConnection.global_single_query(query, (get_sql_time(revoke_time)))
query = "DELETE FROM sessions WHERE last_seen < %s" query = "DELETE FROM sessions WHERE last_seen < %s"
DatabaseConnection.global_single_execution(query, (get_sql_time(revoke_time))) DatabaseConnection.global_single_execution(query, (get_sql_time(revoke_time)))
return revoked_sessions debug("delete revoked sessions: " + str(revoked_sessions))

View File

@ -1,3 +1,5 @@
import datetime
cert_file = None cert_file = None
key_file = None key_file = None
@ -15,5 +17,12 @@ db_charset = 'utf8mb4'
elo_start_value = 1000 elo_start_value = 1000
elo_default_k = 20 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) # field dimension (warning: this constant is not constantly used)
n = 3 n = 3

View File

@ -1,13 +1,16 @@
from database_connection import DatabaseConnection, SQLInjectionError from database_connection import DatabaseConnection, SQLInjectionError, get_sql_time
from user import User from user import User
import datetime import datetime
import hashlib import hashlib
import uuid import uuid
from settings import elo_start_value from settings import elo_start_value
from tools import debug
class UserManager(object): class UserManager(object):
def __init__(self): def __init__(self, account_lifespan_timedelta):
self.account_lifespan_timedelta = account_lifespan_timedelta
pass pass
def get_user(self, user_name): def get_user(self, user_name):
@ -134,3 +137,14 @@ class UserManager(object):
def get_all_users(self): def get_all_users(self):
query = "SELECT name, last_seen FROM users" query = "SELECT name, last_seen FROM users"
return DatabaseConnection.global_single_query(query) 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))