From 9f4f29b740816afb0f0189fa37a922058313a103 Mon Sep 17 00:00:00 2001 From: cmyui Date: Sun, 19 May 2024 23:55:01 -0400 Subject: [PATCH 1/3] Some refactoring of hwid handling --- common/ripple/user_utils.py | 129 ++++++++++++++++++++++++------------ events/loginEvent.py | 8 +++ objects/osuToken.py | 1 - 3 files changed, 95 insertions(+), 43 deletions(-) diff --git a/common/ripple/user_utils.py b/common/ripple/user_utils.py index 680ec013..7fbbb67e 100644 --- a/common/ripple/user_utils.py +++ b/common/ripple/user_utils.py @@ -253,6 +253,7 @@ async def ban(user_id: int) -> None: ~( privileges.USER_NORMAL | privileges.USER_PUBLIC + # Seems appropriate to remove pending verification here | privileges.USER_PENDING_VERIFICATION ), user_id, @@ -580,11 +581,14 @@ async def associate_user_with_hwids_and_restrict_if_multiaccounting( # Running under wine, check by unique id logger.debug("Logging Linux/Mac hardware") banned = await glob.db.fetchAll( - """SELECT users.id as userid, hw_user.occurencies, users.username FROM hw_user + """ + SELECT users.id as userid, hw_user.occurencies, users.username + FROM hw_user LEFT JOIN users ON users.id = hw_user.userid WHERE hw_user.userid != %(userid)s AND hw_user.unique_id = %(uid)s - AND (users.privileges & 3 != 3)""", + AND (users.privileges & 3 != 3) + """, { "userid": user_id, "uid": hwid_set[3], @@ -594,13 +598,16 @@ async def associate_user_with_hwids_and_restrict_if_multiaccounting( # Running under windows, do all checks logger.debug("Logging Windows hardware") banned = await glob.db.fetchAll( - """SELECT users.id as userid, hw_user.occurencies, users.username FROM hw_user + """ + SELECT users.id as userid, hw_user.occurencies, users.username + FROM hw_user LEFT JOIN users ON users.id = hw_user.userid WHERE hw_user.userid != %(userid)s AND hw_user.mac = %(mac)s AND hw_user.unique_id = %(uid)s AND hw_user.disk_id = %(diskid)s - AND (users.privileges & 3 != 3)""", + AND (users.privileges & 3 != 3) + """, { "userid": user_id, "mac": hwid_set[2], @@ -616,7 +623,11 @@ async def associate_user_with_hwids_and_restrict_if_multiaccounting( # Get the total numbers of logins user_hwids_count_rec = await glob.db.fetch( - "SELECT COUNT(*) AS count FROM hw_user WHERE userid = %s", + """ + SELECT COUNT(*) AS count + FROM hw_user + WHERE userid = %s + """, [user_id], ) # and make sure it is valid @@ -641,9 +652,10 @@ async def associate_user_with_hwids_and_restrict_if_multiaccounting( # Update hash set occurencies await glob.db.execute( """ - INSERT INTO hw_user (id, userid, mac, unique_id, disk_id, occurencies) VALUES (NULL, %s, %s, %s, %s, 1) - ON DUPLICATE KEY UPDATE occurencies = occurencies + 1 - """, + INSERT INTO hw_user (id, userid, mac, unique_id, disk_id, occurencies) + VALUES (NULL, %s, %s, %s, %s, 1) + ON DUPLICATE KEY UPDATE occurencies = occurencies + 1 + """, [user_id, hwid_set[2], hwid_set[3], hwid_set[4]], ) @@ -674,37 +686,51 @@ async def grant_user_default_privileges(user_id: int) -> None: ) -async def authorize_login_and_activate_new_account( +WINE_STATIC_MAC_ADDRESS = "b4ec3c4334a0249dae95c284ec5983df" +WINE_STATIC_DISK_ID = "ffae06fb022871fe9beb58b005c5e21d" + + +def _hwid_set_running_under_wine(hwid_set: list[str]) -> bool: + return hwid_set[2] == WINE_STATIC_MAC_ADDRESS or hwid_set[4] == WINE_STATIC_DISK_ID + + +async def find_user_ids_with_hwid_matches( user_id: int, - # TODO: refactor hwid sets into an object across the codebase hwid_set: list[str], -) -> bool: - """ - Check for multi-accounts, authorize the login, activate the account (if new), - and grant them default user privileges (publicity & regular access). - """ - username = await get_username_from_id(user_id) +) -> list[int]: + """\ + Find any users using the same set of hardware identifiers. - # Make sure there are no other accounts activated with this exact mac/unique id/hwid - if ( - hwid_set[2] == "b4ec3c4334a0249dae95c284ec5983df" - or hwid_set[4] == "ffae06fb022871fe9beb58b005c5e21d" - ): + In practice, we use this to determine users re-using the same hardware. + + This often a common sign of multi-accounting, which is against our ToS. + """ + if _hwid_set_running_under_wine(hwid_set): # Running under wine, check only by uniqueid - await audit_logs.send_log_as_discord_webhook( - message=f"[{username}](https://akatsuki.gg/u/{user_id}) running under wine:\n**Full data:** {hwid_set}\n**Usual wine mac address hash:** b4ec3c4334a0249dae95c284ec5983df\n**Usual wine disk id:** ffae06fb022871fe9beb58b005c5e21d", - discord_channel="ac_confidential", - ) - logger.debug("Veryfing with Linux/Mac hardware") - match = await glob.db.fetchAll( - "SELECT userid FROM hw_user WHERE unique_id = %(uid)s AND userid != %(userid)s AND activated = 1 LIMIT 1", + matching_records = await glob.db.fetchAll( + """ + SELECT userid + FROM hw_user + WHERE unique_id = %(uid)s + AND userid != %(userid)s + AND activated = 1 + LIMIT 1 + """, {"uid": hwid_set[3], "userid": user_id}, ) else: # Running under windows, full check - logger.debug("Veryfing with Windows hardware") - match = await glob.db.fetchAll( - "SELECT userid FROM hw_user WHERE mac = %(mac)s AND unique_id = %(uid)s AND disk_id = %(diskid)s AND userid != %(userid)s AND activated = 1 LIMIT 1", + matching_records = await glob.db.fetchAll( + """ + SELECT userid + FROM hw_user + WHERE mac = %(mac)s + AND unique_id = %(uid)s + AND disk_id = %(diskid)s + AND userid != %(userid)s + AND activated = 1 + LIMIT 1 + """, { "mac": hwid_set[2], "uid": hwid_set[3], @@ -713,30 +739,49 @@ async def authorize_login_and_activate_new_account( }, ) - if match: - # This is a multiaccount, restrict other account and ban this account + return [rec["userid"] for rec in matching_records] + + +async def authorize_login_and_activate_new_account( + user_id: int, + # TODO: refactor hwid sets into an object across the codebase + hwid_set: list[str], +) -> bool: + """ + Check for multi-accounts, authorize the login, activate the account (if new), + and grant them default user privileges (publicity & regular access). + """ + username = await get_username_from_id(user_id) + + user_ids_associated_with_device = await find_user_ids_with_hwid_matches( + user_id, hwid_set + ) + + if user_ids_associated_with_device: + # There are other users associated with this set of hardware identifiers. + # We'll consider this a multi-account; restrict original account; ban this account. - # Get original user_id and username (lowest ID) - originalUserID = match[0]["userid"] - originalUsername: str | None = await get_username_from_id(originalUserID) + # Fetch their original account's information + original_user_id = user_ids_associated_with_device[0] + original_username = await get_username_from_id(original_user_id) # Ban this user and append notes await ban(user_id) # this removes the USER_PENDING_VERIFICATION flag too await append_cm_notes( user_id, - f"{originalUsername}'s multiaccount ({originalUserID}), found HWID match while verifying account.", + f"{original_username}'s multiaccount ({original_user_id}), found HWID match while verifying account.", ) await append_cm_notes( - originalUserID, + original_user_id, f"Has created multiaccount {username} ({user_id}).", ) # Restrict the original - await restrict(originalUserID) + await restrict(original_user_id) # Discord message await audit_logs.send_log_as_discord_webhook( - message=f"[{originalUsername}](https://akatsuki.gg/u/{originalUserID}) has been restricted because they have created the multiaccount [{username}](https://akatsuki.gg/u/{user_id}). The multiaccount has been banned.", + message=f"[{original_username}](https://akatsuki.gg/u/{original_user_id}) has been restricted because they have created the multiaccount [{username}](https://akatsuki.gg/u/{user_id}). The multiaccount has been banned.", discord_channel="ac_general", ) @@ -845,7 +890,7 @@ async def remove_from_leaderboard(user_id: int) -> None: for board in ("leaderboard", "relaxboard"): for mode in ("std", "taiko", "ctb", "mania"): await pipe.zrem(f"ripple:{board}:{mode}", str(user_id)) - if country and country != "xx": + if country != "xx": await pipe.zrem(f"ripple:{board}:{mode}:{country}", str(user_id)) await pipe.execute() @@ -874,7 +919,7 @@ async def remove_from_specified_leaderboard( async with glob.redis.pipeline() as pipe: await pipe.zrem(redis_board, str(user_id)) - if country and country != "xx": + if country != "xx": await pipe.zrem(f"{redis_board}:{country}", str(user_id)) await pipe.execute() diff --git a/events/loginEvent.py b/events/loginEvent.py index 451aef30..49a516f8 100644 --- a/events/loginEvent.py +++ b/events/loginEvent.py @@ -248,6 +248,14 @@ async def handle(web_handler: AsyncRequestHandler) -> tuple[str, bytes]: # toke else: # The user's freeze has expired; restrict them. await user_utils.restrict(userID) + + maybe_token = await osuToken.update_token( + userToken["token_id"], + privileges=userToken["privileges"] & ~privileges.USER_PUBLIC, + ) + assert maybe_token is not None + userToken = maybe_token + await user_utils.unfreeze( userID, should_log_to_cm_notes_and_discord=False, diff --git a/objects/osuToken.py b/objects/osuToken.py index 905f703b..a1e35936 100644 --- a/objects/osuToken.py +++ b/objects/osuToken.py @@ -255,7 +255,6 @@ async def get_all_tokens_by_username(username: str) -> list[Token]: return [token for token in tokens if token["username"] == username] -# TODO: the things that can actually be Optional need to have different defaults async def update_token( token_id: str, *, From 249da24f19f34b2317a6d90550408958530db29c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 03:55:19 +0000 Subject: [PATCH 2/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- common/ripple/user_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/ripple/user_utils.py b/common/ripple/user_utils.py index 7fbbb67e..fee04e4c 100644 --- a/common/ripple/user_utils.py +++ b/common/ripple/user_utils.py @@ -754,7 +754,7 @@ async def authorize_login_and_activate_new_account( username = await get_username_from_id(user_id) user_ids_associated_with_device = await find_user_ids_with_hwid_matches( - user_id, hwid_set + user_id, hwid_set, ) if user_ids_associated_with_device: From b976789cce782aeb81962106cd3eef8caecb3eeb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 05:41:21 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- common/ripple/user_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/ripple/user_utils.py b/common/ripple/user_utils.py index fee04e4c..74511157 100644 --- a/common/ripple/user_utils.py +++ b/common/ripple/user_utils.py @@ -754,7 +754,8 @@ async def authorize_login_and_activate_new_account( username = await get_username_from_id(user_id) user_ids_associated_with_device = await find_user_ids_with_hwid_matches( - user_id, hwid_set, + user_id, + hwid_set, ) if user_ids_associated_with_device: