hardening server
This commit is contained in:
parent
86b766e0b5
commit
e1d31cc291
@ -6,12 +6,14 @@ from user_manager import UserManager
|
||||
from match_manager import MatchManager
|
||||
from match import Match
|
||||
|
||||
from tools import debug
|
||||
|
||||
|
||||
def parse_message(msg: str):
|
||||
# TODO: make it more robust by validating each part of a message
|
||||
msg_obj = json.loads(msg)
|
||||
if "type" not in msg_obj or "data" not in msg_obj:
|
||||
print("got strange message")
|
||||
debug("got strange message")
|
||||
return None
|
||||
|
||||
return msg_obj
|
||||
@ -33,7 +35,7 @@ class Connection(object):
|
||||
try:
|
||||
await self.websocket.send(msg)
|
||||
except Exception as e:
|
||||
print("error sending message to user " +
|
||||
debug("error sending message to user " +
|
||||
self.user_name + ". Reason: " + str(e))
|
||||
|
||||
async def close(self):
|
||||
@ -44,7 +46,7 @@ class Connection(object):
|
||||
self.is_closed = True
|
||||
|
||||
except Exception as e:
|
||||
print("error closing session to user " +
|
||||
debug("error closing session to user " +
|
||||
self.user_name + ". Reason: " + str(e))
|
||||
|
||||
|
||||
@ -114,7 +116,7 @@ class ConnectionHandler(object):
|
||||
socket: websockets.WebSocketServerProtocol,
|
||||
login_msg: str):
|
||||
msg = parse_message(login_msg)
|
||||
print("new incomming connection...")
|
||||
debug("new incomming connection...")
|
||||
if msg is None:
|
||||
return None
|
||||
|
||||
@ -395,7 +397,7 @@ class ConnectionHandler(object):
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print("error processing match request: " + str(data) + str(e))
|
||||
debug("error processing match request: " + str(data) + str(e))
|
||||
|
||||
async def _on_match_state_req(self, conn, data):
|
||||
db_matches = self.match_manager.get_matches_for_user(conn.user_name)
|
||||
@ -432,7 +434,7 @@ class ConnectionHandler(object):
|
||||
if match is not None:
|
||||
match_state = match.to_json_state()
|
||||
|
||||
print(match_state)
|
||||
debug(match_state)
|
||||
|
||||
await conn.send(json.dumps({
|
||||
'type': 'match_update',
|
||||
@ -580,7 +582,7 @@ class ConnectionHandler(object):
|
||||
async def handle_message(self, conn, msg_str):
|
||||
msg = parse_message(msg_str)
|
||||
|
||||
print("incoming message" + str(msg))
|
||||
debug("incoming message" + str(msg))
|
||||
|
||||
if msg is None:
|
||||
return None
|
||||
@ -607,4 +609,4 @@ class ConnectionHandler(object):
|
||||
await self._on_unfriend_request(conn, msg['data'])
|
||||
|
||||
else:
|
||||
print("could not interpret message: " + msg_str)
|
||||
debug("could not interpret message: " + msg_str)
|
||||
|
@ -36,23 +36,30 @@ class DatabaseConnection(object):
|
||||
DatabaseConnection.instance.commit()
|
||||
|
||||
@staticmethod
|
||||
def global_single_query(query):
|
||||
def global_single_query(query, params=None):
|
||||
if ';' in query:
|
||||
# Possible injection!
|
||||
raise SQLInjectionError()
|
||||
|
||||
with DatabaseConnection.global_cursor() as c:
|
||||
c.execute(query)
|
||||
if params is None:
|
||||
c.execute(query)
|
||||
else:
|
||||
c.execute(query, params)
|
||||
|
||||
return c.fetchall()
|
||||
|
||||
@staticmethod
|
||||
def global_single_execution(sql_statement):
|
||||
def global_single_execution(sql_statement, params=None):
|
||||
if ';' in sql_statement:
|
||||
# Possible injection detected!
|
||||
raise SQLInjectionError()
|
||||
|
||||
with DatabaseConnection.global_cursor() as c:
|
||||
c.execute(sql_statement)
|
||||
if params is None:
|
||||
c.execute(sql_statement)
|
||||
else:
|
||||
c.execute(sql_statement, params)
|
||||
DatabaseConnection.global_commit()
|
||||
|
||||
def __init__(self,
|
||||
@ -84,7 +91,7 @@ class DatabaseConnection(object):
|
||||
":" +
|
||||
str(port) +
|
||||
"\nCheck the configuration in settings.py!\n")
|
||||
raise Exception('could not connec to database')
|
||||
raise Exception('could not connect to database')
|
||||
|
||||
def get_cursor(self):
|
||||
return self.connection.cursor()
|
||||
@ -96,6 +103,7 @@ class DatabaseConnection(object):
|
||||
def commit(self):
|
||||
self.connection.commit()
|
||||
|
||||
|
||||
def test_connection():
|
||||
import settings
|
||||
DatabaseConnection(settings.db_host,
|
||||
@ -103,4 +111,4 @@ def test_connection():
|
||||
settings.db_user,
|
||||
settings.db_pw,
|
||||
settings.db_db,
|
||||
settings.db_charset)
|
||||
settings.db_charset)
|
||||
|
23
main.py
23
main.py
@ -12,6 +12,8 @@ from user_manager import UserManager
|
||||
from database_connection import DatabaseConnection
|
||||
import datetime
|
||||
|
||||
from tools import debug
|
||||
|
||||
um = UserManager()
|
||||
sm = SessionManager(datetime.timedelta(hours=12))
|
||||
mm = MatchManager()
|
||||
@ -29,7 +31,6 @@ DatabaseConnection(db_host,
|
||||
async def socket_worker(websocket, path):
|
||||
connection = None
|
||||
|
||||
|
||||
try:
|
||||
raw_msg = await websocket.recv()
|
||||
|
||||
@ -37,21 +38,25 @@ async def socket_worker(websocket, path):
|
||||
|
||||
if connection is None:
|
||||
return
|
||||
|
||||
print("successfull logged in user: " + connection.user_name)
|
||||
|
||||
|
||||
debug("successfull logged in user: " + connection.user_name)
|
||||
|
||||
async for m in websocket:
|
||||
await ch.handle_message(connection, m)
|
||||
|
||||
except websockets.ConnectionClosed as e:
|
||||
# nothing suspicious here
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
# TODO: each disconnect is an exception so far
|
||||
if connection is not None:
|
||||
print("catched exception in worker for user: " +
|
||||
debug("catched exception in worker for user: " +
|
||||
connection.user_name + ": " + str(e))
|
||||
else:
|
||||
print("catched exception in worker for unknown user: " + str(e))
|
||||
|
||||
print(traceback.print_exc())
|
||||
debug("catched exception in worker for unknown user: " + str(e))
|
||||
|
||||
debug(traceback.debug_exc())
|
||||
|
||||
finally:
|
||||
id = None
|
||||
@ -62,7 +67,7 @@ async def socket_worker(websocket, path):
|
||||
|
||||
if connection is None:
|
||||
id = "unknown_user"
|
||||
print("close connection to user: " + id)
|
||||
debug("close connection to user: " + id)
|
||||
|
||||
if cert_file is not None and key_file is not None:
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
|
6
match.py
6
match.py
@ -3,6 +3,8 @@ import numpy as np
|
||||
import json
|
||||
import base64
|
||||
|
||||
from tools import debug
|
||||
|
||||
# decoder/encoder took from: https://stackoverflow.com/a/19271311
|
||||
|
||||
|
||||
@ -158,7 +160,7 @@ class Match(object):
|
||||
player_mark = FIELD_USER_A if self.is_player_a else FIELD_USER_B
|
||||
|
||||
if not self.is_move_valid(sub_x, sub_y, x, y):
|
||||
print("invalid move")
|
||||
debug("invalid move")
|
||||
return False
|
||||
|
||||
# else: move!
|
||||
@ -169,7 +171,7 @@ class Match(object):
|
||||
|
||||
# check whether this indicates changes in the global field:
|
||||
if self.global_field[sub_y, sub_x] != FIELD_EMPTY:
|
||||
print("field not empty")
|
||||
debug("field not empty")
|
||||
return False
|
||||
|
||||
subgrid = self.complete_field[sub_y * self.n: (
|
||||
|
@ -5,14 +5,16 @@ import uuid
|
||||
import settings
|
||||
from match import Match
|
||||
|
||||
from tools import debug
|
||||
|
||||
|
||||
class MatchManager(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def get_match(self, id):
|
||||
query = f"SELECT * FROM matches WHERE id='{id}'"
|
||||
result = DatabaseConnection.global_single_query(query)
|
||||
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'],
|
||||
@ -20,45 +22,46 @@ class MatchManager(object):
|
||||
return match
|
||||
|
||||
def get_matches_for_user(self, user_name):
|
||||
query = f"SELECT * FROM matches WHERE user_a='{user_name}' OR user_b='{user_name}'"
|
||||
return DatabaseConnection.global_single_query(query)
|
||||
query = "SELECT * FROM matches WHERE user_a=%s OR user_b=%s"
|
||||
return DatabaseConnection.global_single_query(query, (user_name, user_name))
|
||||
|
||||
def create_new_match(self, user_a, user_b):
|
||||
match_id = uuid.uuid4().hex
|
||||
# check if already existent (but should not be the case)
|
||||
if len(DatabaseConnection.global_single_query(f"SELECT id FROM matches WHERE id='{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)
|
||||
|
||||
match = Match(n=settings.n, match_id=match_id, player_a_name=user_a, player_b_name=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()
|
||||
query = f"INSERT INTO matches (id, user_a, user_b, match_state, active_user, last_active) VALUES ('{match_id}', '{user_a}', '{user_b}', '{match.to_json_state()}', '{match.get_current_player()}','{get_sql_time(now)}')"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
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, match):
|
||||
now = get_sql_time(datetime.datetime.now())
|
||||
query = f"UPDATE matches SET match_state='{match.to_json_state()}', active_user='{match.get_current_player()}', last_active='{now}' WHERE id='{match_id}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
|
||||
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 apply_move(self, move_data):
|
||||
match = self.get_match(move_data['id'])
|
||||
if match is None:
|
||||
return None
|
||||
|
||||
if not match.move(move_data):
|
||||
print("error applying match move")
|
||||
return None
|
||||
|
||||
|
||||
self.update_match(move_data['id'], match)
|
||||
print("updated match")
|
||||
return match
|
||||
|
||||
def is_match(self, player_a, player_b):
|
||||
query = f"SELECT * FROM matches WHERE (user_a='{player_a}' AND user_b='{player_b}') OR (user_a='{player_b}' AND user_b='{player_a}')"
|
||||
return len(DatabaseConnection.global_single_query(query))
|
||||
|
||||
def delete_match(self, match_id):
|
||||
query = f"DELETE FROM matches WHERE id='{match_id}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
|
||||
if not match.move(move_data):
|
||||
debug("error applying match move")
|
||||
return None
|
||||
|
||||
self.update_match(move_data['id'], match)
|
||||
debug("updated match")
|
||||
return match
|
||||
|
||||
def is_match(self, player_a, player_b):
|
||||
query = "SELECT * FROM matches WHERE (user_a=%s AND user_b=%s) OR (user_a=%s AND user_b=%s)"
|
||||
return len(DatabaseConnection.global_single_query(query, (player_a, player_b, player_b, player_a)))
|
||||
|
||||
def delete_match(self, match_id):
|
||||
query = "DELETE FROM matches WHERE id=%s"
|
||||
DatabaseConnection.global_single_execution(query, (match_id))
|
||||
|
@ -9,59 +9,60 @@ class SessionManager(object):
|
||||
self.session_lifespan_timedelta = session_lifespan_timedelta
|
||||
|
||||
def get_session_by_id(self, session_id):
|
||||
query = f"SELECT * FROM sessions WHERE id='{session_id}'"
|
||||
return DatabaseConnection.global_single_query(query)
|
||||
query = "SELECT * FROM sessions WHERE id=%s"
|
||||
return DatabaseConnection.global_single_query(query, (session_id))
|
||||
|
||||
def touch_session(self, session_id):
|
||||
query = f"UPDATE sessions SET last_seen='{datetime.datetime.now()}' WHERE id='{session_id}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
query = "UPDATE sessions SET last_seen=%s WHERE id=%s"
|
||||
DatabaseConnection.global_single_execution(query, (datetime.datetime.now(), session_id))
|
||||
|
||||
def get_session_by_registered_user(self, user_name):
|
||||
query = f"SELECT * FROM sessions WHERE registered_user='{user_name}'"
|
||||
return DatabaseConnection.global_single_query(query)
|
||||
query = "SELECT * FROM sessions WHERE registered_user=%s"
|
||||
return DatabaseConnection.global_single_query(query, (user_name))
|
||||
|
||||
def get_session_by_temp_user(self, user_name):
|
||||
query = f"SELECT * FROM sessions WHERE temp_user='{user_name}'"
|
||||
return DatabaseConnection.global_single_query(query)
|
||||
query = "SELECT * FROM sessions WHERE temp_user=%s"
|
||||
return DatabaseConnection.global_single_query(query, (user_name))
|
||||
|
||||
def create_session_for_registered_user(self, user_name):
|
||||
new_id = uuid.uuid4().hex
|
||||
# check if already existent (but should not be the case)
|
||||
if len(DatabaseConnection.global_single_query(f"SELECT id FROM sessions WHERE id='{new_id}'")) > 0:
|
||||
if len(DatabaseConnection.global_single_query("SELECT id FROM sessions WHERE id=%s", (str(new_id)))) > 0:
|
||||
# okay, next try:
|
||||
return self.create_session_for_registered_user(user_name)
|
||||
|
||||
# delete other active sessions:
|
||||
query = f"DELETE FROM sessions WHERE registered_user='{user_name}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
|
||||
query = f"INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( '{new_id}', '{user_name}', NULL, '{datetime.datetime.now()}')"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
# delete other active sessions:
|
||||
query = "DELETE FROM sessions WHERE registered_user=%s"
|
||||
DatabaseConnection.global_single_execution(query, (user_name))
|
||||
|
||||
query = "INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( %s, %s, NULL, %s)"
|
||||
DatabaseConnection.global_single_execution(query, (new_id, user_name, datetime.datetime.now()))
|
||||
|
||||
return new_id
|
||||
|
||||
def create_session_for_temp_user(self, user_name):
|
||||
new_id = uuid.uuid4().hex
|
||||
# check if already existent (but should not be the case)
|
||||
if len(DatabaseConnection.global_single_query(f"SELECT id FROM sessions WHERE id='{new_id}'")) > 0:
|
||||
if len(DatabaseConnection.global_single_query("SELECT id FROM sessions WHERE id=%s", (new_id))) > 0:
|
||||
# okay, next try:
|
||||
return self.create_session_for_registered_user(user_name)
|
||||
|
||||
|
||||
# delete other active sessions:
|
||||
query = f"DELETE FROM sessions WHERE temp_user='{user_name}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
|
||||
query = f"INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( '{new_id}', NULL, '{user_name}', '{datetime.datetime.now()}')"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
query = "DELETE FROM sessions WHERE temp_user=%s"
|
||||
DatabaseConnection.global_single_execution(query, (user_name))
|
||||
|
||||
query = "INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( %s, NULL, %s, %s)"
|
||||
DatabaseConnection.global_single_execution(query, (new_id, user_name, datetime.datetime.now()))
|
||||
return new_id
|
||||
|
||||
def delete_session(self, session_id):
|
||||
query = f"DELETE FROM sessions WHERE id='{session_id}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
query = "DELETE FROM sessions WHERE id=%s"
|
||||
DatabaseConnection.global_single_execution(query, (session_id))
|
||||
|
||||
def revoke_inactive_sessions(self):
|
||||
revoke_time = datetime.datetime.now() - self.session_lifespan_timedelta
|
||||
query = f"SELECT * from sessions WHERE last_seen < '{get_sql_time(revoke_time)}'"
|
||||
revoked_sessions = DatabaseConnection.global_single_query(query)
|
||||
query = f"DELETE FROM sessions WHERE last_seen < '{get_sql_time(revoke_time)}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
query = "SELECT * from sessions WHERE last_seen < %s"
|
||||
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
|
||||
|
5
tools.py
Normal file
5
tools.py
Normal file
@ -0,0 +1,5 @@
|
||||
import datetime
|
||||
|
||||
|
||||
def debug(msg):
|
||||
print("[" + datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "]: " + msg)
|
@ -10,23 +10,23 @@ class UserManager(object):
|
||||
pass
|
||||
|
||||
def get_user(self, user_name):
|
||||
query = f"SELECT name, last_seen FROM users where name='{user_name}'"
|
||||
return DatabaseConnection.global_single_query(query)
|
||||
query = "SELECT name, last_seen FROM users where name=%s"
|
||||
return DatabaseConnection.global_single_query(query, (user_name))
|
||||
|
||||
def delete_user(self, user_name):
|
||||
query = f"DELETE FROM users where name='{user_name}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
query = "DELETE FROM users where name=%s"
|
||||
DatabaseConnection.global_single_execution(query, (user_name))
|
||||
|
||||
# remove from friends:
|
||||
query = f"DELETE FROM friends WHERE user='{user_name}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
query = "DELETE FROM friends WHERE user=%s"
|
||||
DatabaseConnection.global_single_execution(query, (user_name))
|
||||
|
||||
query = f"DELETE FROM friends WHERE friend='{user_name}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
query = "DELETE FROM friends WHERE friend=%s"
|
||||
DatabaseConnection.global_single_execution(query, (user_name))
|
||||
|
||||
def verify_user(self, user_name, pw):
|
||||
query = f"SELECT * FROM users where name='{user_name}'"
|
||||
users = DatabaseConnection.global_single_query(query)
|
||||
query = "SELECT * FROM users where name=%s"
|
||||
users = DatabaseConnection.global_single_query(query, (user_name))
|
||||
if len(self.get_user(user_name)) == 0:
|
||||
return False
|
||||
|
||||
@ -41,33 +41,33 @@ class UserManager(object):
|
||||
assert len(self.get_user(user_name)) == 0
|
||||
pw_salt = uuid.uuid4().hex
|
||||
pw_hash = hashlib.sha512(pw.encode() + pw_salt.encode()).hexdigest()
|
||||
query = f"INSERT INTO users (name, pw_hash, pw_salt, last_seen) VALUES ( '{user_name}', '{pw_hash}', '{pw_salt}', '{datetime.datetime.now()}')"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
query = "INSERT INTO users (name, pw_hash, pw_salt, last_seen) VALUES ( %s, %s, %s, %s)"
|
||||
DatabaseConnection.global_single_execution(query, (user_name, pw_hash, pw_salt, datetime.datetime.now()))
|
||||
|
||||
def touch_user(self, user_name):
|
||||
matches = self.get_user(user_name)
|
||||
assert len(matches) == 1
|
||||
query = f"UPDATE users SET last_seen='{datetime.datetime.now()}' WHERE name='{user_name}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
query = "UPDATE users SET last_seen=%s WHERE name=%s"
|
||||
DatabaseConnection.global_single_execution(query, (datetime.datetime.now(), user_name))
|
||||
|
||||
def add_friend_to_user(self, user_name, friend_name):
|
||||
if len(self.get_user(friend_name)) > 0:
|
||||
query = f"INSERT INTO friends (user, friend) VALUES ( '{user_name}', '{friend_name}')"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
query = "INSERT INTO friends (user, friend) VALUES ( %s, %s)"
|
||||
DatabaseConnection.global_single_execution(query, (user_name, friend_name))
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_friends_for_user(self, user_name):
|
||||
query = f"SELECT friend FROM friends WHERE user='{user_name}'"
|
||||
tmp = DatabaseConnection.global_single_query(query)
|
||||
query = "SELECT friend FROM friends WHERE user=%s"
|
||||
tmp = DatabaseConnection.global_single_query(query, (user_name))
|
||||
friends = set()
|
||||
for entry in tmp:
|
||||
friends.add(entry['friend'])
|
||||
return friends
|
||||
|
||||
def remove_friend_from_user(self, user_name, friend_name):
|
||||
query = f"DELETE FROM friends WHERE user='{user_name}' AND friend='{friend_name}'"
|
||||
DatabaseConnection.global_single_execution(query)
|
||||
query = "DELETE FROM friends WHERE user=%s AND friend=%s"
|
||||
DatabaseConnection.global_single_execution(query, (user_name, friend_name))
|
||||
|
||||
def get_all_users(self):
|
||||
query = "SELECT name, last_seen FROM users"
|
||||
|
Loading…
Reference in New Issue
Block a user