From 5bdf2c922a7a11919fd27aae556b6d783147fb8d Mon Sep 17 00:00:00 2001 From: Kensuke Matsuzaki Date: Mon, 18 Sep 2023 17:38:50 +0900 Subject: [PATCH] Multiple admin login --- server-python/cgos/app/cgos.py | 80 ++++++++++++++++------------ server-python/cgos/reset_password.py | 72 +++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 34 deletions(-) create mode 100644 server-python/cgos/reset_password.py diff --git a/server-python/cgos/app/cgos.py b/server-python/cgos/app/cgos.py index a0d3200..59978be 100644 --- a/server-python/cgos/app/cgos.py +++ b/server-python/cgos/app/cgos.py @@ -240,7 +240,7 @@ def __init__( games: Dict[int, Game] = dict() # currently active games ratingOf: Dict[str, str] = dict() # ratings of any player who logs on viewers = ViewerList() -admin: Optional[ActiveUser] = None # users currently logged on +admin: Dict[str, ActiveUser] = dict() # users currently logged on # a unique and temporary name for each login until a name is established @@ -259,13 +259,14 @@ def nsend(name: str, msg: str) -> None: logger.info(f"alert: Cannot find active record for {name}") +def is_admin(who: str) -> bool: + return who.startswith(ADMIN_USER) + + # ------------------------------------------------- # send an informational message out to all clients # ------------------------------------------------- def infoMsg(msg: str) -> None: - global act - global admin - for (who, v) in list(act.items()): if v.msg_state != "protocol": soc = v.sock @@ -279,12 +280,12 @@ def infoMsg(msg: str) -> None: viewers.sendAll(f"info {msg}") # send to admin - if admin: - soc = admin.sock + for (who, v) in list(admin.items()): + soc = v.sock if not soc.send(f"info {msg}"): - logger.error("admin disconnected") + logger.error(f"admin[{who}] disconnected") soc.close() - admin = None + del admin[who] # write an SGF game record @@ -813,6 +814,14 @@ def _handle_player_password(sock: Client, data: str) -> None: res = cur.fetchone() if res is None: + if is_admin(who): + if cfg.hashPassword: + passctx.dummy_verify() + logger.warn(f"user {who} password hash doesn't match") + sock.send("Error: Sorry, password doesn't match") + sock.close() + del act[uid] + return logger.info(f"[{who}] new user") if cfg.hashPassword: pw_store = passctx.hash(pw) @@ -885,23 +894,27 @@ def _handle_player_password(sock: Client, data: str) -> None: ) db.commit() + def cleanup_old_connection(who: str, users: Dict[str, ActiveUser]) -> None: + if who in users: + # cleanup old connection + xsoc = users[who].sock + xsoc.send("info another login is being attempted using this user name") + xsoc.close() + logger.error(f"Error: user {who} apparently lost old connection") + del users[who] + # Handle user who already logged on - if who in act: - # cleanup old connection - xsoc = act[who].sock - xsoc.send("info another login is being attempted using this user name") - xsoc.close() - logger.error(f"Error: user {who} apparently lost old connection") - del act[who] + cleanup_old_connection(who, act) + cleanup_old_connection(who, admin) sock.id = who client = act[uid] del act[uid] - if who == ADMIN_USER: + if is_admin(who): sock.send("ok") - global admin - admin = ActiveUser(sock, msg_state="waiting") + + admin[who] = ActiveUser(sock, msg_state="waiting") logger.info(f"[{who}] logged on as admin") return @@ -1121,11 +1134,8 @@ def _handle_player_genmove(sock: Client, data: str) -> None: # Handle admin client # def admin_respond(sock: Client, data: str) -> None: - global act - global admin - who = sock.id - user = admin + user = admin[who] # If we can no longer read from sock, close it. # --------------------------------------------- @@ -1133,7 +1143,7 @@ def admin_respond(sock: Client, data: str) -> None: logger.error(f"[{who}] disconnected") sock.close() - admin = None + del admin[who] return logger.debug(f"handle '{user.msg_state}' '{data}'") @@ -1178,11 +1188,12 @@ def _handle_admin_waiting(sock: Client, data: str) -> None: def _admin_command_quit(sock: Client, tokens: List[str]) -> None: - global admin + who = sock.id + logger.info("admin quits") sock.send("Quit") sock.close() - admin = None + del admin[who] def _admin_command_who(sock: Client, tokens: List[str]) -> None: @@ -1326,9 +1337,7 @@ def _admin_command_match(sock: Client, tokens: List[str]) -> None: def _admin_command_abort(sock: Client, tokens: List[str]) -> None: if len(tokens) < 2: - sock.send( - "abort []" - ) + sock.send("abort []") return gid = int(tokens[1]) @@ -1339,9 +1348,7 @@ def _admin_command_abort(sock: Client, tokens: List[str]) -> None: logger.info(f"Abort {gid}") if gid not in games: - sock.send( - "No game" - ) + sock.send("No game") return game = games[gid] @@ -1430,7 +1437,7 @@ async def handle_client(client: Client) -> None: if who in viewers.vact: logger.debug(f"handle viewer {who}: {line}") viewer_respond(client, line) - elif client.id == ADMIN_USER: + elif is_admin(client.id): logger.debug(f"handle admin {who}: {line}") admin_respond(client, line) else: @@ -1535,7 +1542,9 @@ def schedule_games() -> None: if ct - rec.last_move_start_time > schedule_games_interval * 2: sw = f"{rec.w}({rec.white_rate})" sb = f"{rec.b}({rec.black_rate})" - logger.warn(f"Match {gid} {sw} {sb} last move is {(ct - rec.last_move_start_time) // 1000} seconds ago") + logger.warn( + f"Match {gid} {sw} {sb} last move is {(ct - rec.last_move_start_time) // 1000} seconds ago" + ) count += 1 @@ -1556,7 +1565,10 @@ def schedule_games() -> None: infoMsg("Maximum time until next round: %02d:%02d" % (estMin, estSec)) else: dt_now = datetime.datetime.now() - infoMsg("Next round will start soon: %02d:%02d" % (dt_now.hour, dt_now.minute)) + infoMsg( + "Next round will start soon: %02d:%02d" + % (dt_now.hour, dt_now.minute) + ) last_est = curTime # should we begin another round of scheduling? diff --git a/server-python/cgos/reset_password.py b/server-python/cgos/reset_password.py new file mode 100644 index 0000000..4a5c28e --- /dev/null +++ b/server-python/cgos/reset_password.py @@ -0,0 +1,72 @@ +# The MIT License +# +# Copyright (c) 2023 Kensuke Matsuzaki +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import sys + +import sqlite3 +from passlib.context import CryptContext + +from app.config import Configs + +if __name__ == "__main__": + + cfg = Configs() + cfg.load(sys.argv[1]) + + passctx = CryptContext() + passctx.load_path(sys.argv[1]) + + who = sys.argv[2] + pw = sys.argv[3] + + db = sqlite3.connect(cfg.database_state_file) + + if cfg.hashPassword: + pw_store = passctx.hash(pw) + else: + pw_store = pw + + cur = db.execute("SELECT pass, rating, K FROM password WHERE name = ?", (who,)) + res = cur.fetchone() + + if res is None: + print(f"insert user:{who} hash:{pw_store}") + db.execute( + """INSERT INTO password VALUES(?, ?, 0, ?, ?, "2000-01-01 00:00")""", + ( + who, + pw_store, + cfg.defaultRating, + cfg.maxK, + ), + ) + else: + print(f"update user:{who} hash:{pw_store}") + db.execute( + "UPDATE password set pass=? WHERE name=?", + ( + pw_store, + who, + ), + ) + + db.commit()