1
0

erase_user_data.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. #!/usr/bin/env python3
  2. # Script for erasing all data about a user from the database.
  3. # Intended for GDPR erasure requests.
  4. #
  5. # NOTE: We recommend implementing a "GDPR Erasure Ban" on the user's last IP/HWID before erasing their data, to prevent abuse.
  6. # This is acceptable under the GDPR as a "legitimate interest" to prevent GDPR erasure being used to avoid moderation/bans.
  7. # You would need to do this *before* running this script, to avoid losing the IP/HWID of the user entirely.
  8. import argparse
  9. import os
  10. import psycopg2
  11. from uuid import UUID
  12. LATEST_DB_MIGRATION = "20230725193102_AdminNotesImprovementsForeignKeys"
  13. def main():
  14. parser = argparse.ArgumentParser()
  15. # Yes we need both to reliably pseudonymize the admin_log table.
  16. parser.add_argument("user_id", help="User ID to erase data for")
  17. parser.add_argument("user_name", help="User name to erase data for")
  18. parser.add_argument("--ignore-schema-mismatch", action="store_true")
  19. parser.add_argument("--connection-string", required=True, help="Database connection string to use. See https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING")
  20. args = parser.parse_args()
  21. conn = psycopg2.connect(args.connection_string)
  22. cur = conn.cursor()
  23. check_schema_version(cur, args.ignore_schema_mismatch)
  24. user_id = args.user_id
  25. user_name = args.user_name
  26. clear_admin(cur, user_id)
  27. pseudonymize_admin_log(cur, user_name, user_id)
  28. clear_assigned_user_id(cur, user_id)
  29. clear_connection_log(cur, user_id)
  30. clear_play_time(cur, user_id)
  31. clear_player(cur, user_id)
  32. clear_preference(cur, user_id)
  33. clear_server_ban(cur, user_id)
  34. clear_server_ban_exemption(cur, user_id)
  35. clear_server_role_ban(cur, user_id)
  36. clear_uploaded_resource_log(cur, user_id)
  37. clear_whitelist(cur, user_id)
  38. print("Committing...")
  39. conn.commit()
  40. def check_schema_version(cur: "psycopg2.cursor", ignore_mismatch: bool):
  41. cur.execute('SELECT "MigrationId" FROM "__EFMigrationsHistory" ORDER BY "__EFMigrationsHistory" DESC LIMIT 1')
  42. schema_version = cur.fetchone()
  43. if schema_version == None:
  44. print("Unable to read database schema version.")
  45. exit(1)
  46. if schema_version[0] != LATEST_DB_MIGRATION:
  47. print(f"Unsupport schema version of DB: '{schema_version[0]}'. Supported: {LATEST_DB_MIGRATION}")
  48. if ignore_mismatch:
  49. return
  50. exit(1)
  51. def clear_admin(cur: "psycopg2.cursor", user_id: str):
  52. print("Clearing admin...")
  53. cur.execute("""
  54. DELETE FROM
  55. admin
  56. WHERE
  57. user_id = %s
  58. """, (user_id,))
  59. def pseudonymize_admin_log(cur: "psycopg2.cursor", user_name: str, user_id: str):
  60. print("Pseudonymizing admin_log...")
  61. cur.execute("""
  62. UPDATE
  63. admin_log l
  64. SET
  65. message = replace(message, %s, %s)
  66. FROM
  67. admin_log_player lp
  68. WHERE
  69. lp.round_id = l.round_id AND lp.log_id = l.admin_log_id AND player_user_id = %s;
  70. """, (user_name, user_id, user_id,))
  71. def clear_assigned_user_id(cur: "psycopg2.cursor", user_id: str):
  72. print("Clearing assigned_user_id...")
  73. cur.execute("""
  74. DELETE FROM
  75. assigned_user_id
  76. WHERE
  77. user_id = %s
  78. """, (user_id,))
  79. def clear_connection_log(cur: "psycopg2.cursor", user_id: str):
  80. print("Clearing connection_log...")
  81. cur.execute("""
  82. DELETE FROM
  83. connection_log
  84. WHERE
  85. user_id = %s
  86. """, (user_id,))
  87. def clear_play_time(cur: "psycopg2.cursor", user_id: str):
  88. print("Clearing play_time...")
  89. cur.execute("""
  90. DELETE FROM
  91. play_time
  92. WHERE
  93. player_id = %s
  94. """, (user_id,))
  95. def clear_player(cur: "psycopg2.cursor", user_id: str):
  96. print("Clearing player...")
  97. cur.execute("""
  98. DELETE FROM
  99. player
  100. WHERE
  101. user_id = %s
  102. """, (user_id,))
  103. def clear_preference(cur: "psycopg2.cursor", user_id: str):
  104. print("Clearing preference...")
  105. cur.execute("""
  106. DELETE FROM
  107. preference
  108. WHERE
  109. user_id = %s
  110. """, (user_id,))
  111. def clear_server_ban(cur: "psycopg2.cursor", user_id: str):
  112. print("Clearing server_ban...")
  113. cur.execute("""
  114. DELETE FROM
  115. server_ban
  116. WHERE
  117. player_user_id = %s
  118. """, (user_id,))
  119. def clear_server_ban_exemption(cur: "psycopg2.cursor", user_id: str):
  120. print("Clearing server_ban_exemption...")
  121. cur.execute("""
  122. DELETE FROM
  123. server_ban_exemption
  124. WHERE
  125. user_id = %s
  126. """, (user_id,))
  127. def clear_server_role_ban(cur: "psycopg2.cursor", user_id: str):
  128. print("Clearing server_role_ban...")
  129. cur.execute("""
  130. DELETE FROM
  131. server_role_ban
  132. WHERE
  133. player_user_id = %s
  134. """, (user_id,))
  135. def clear_uploaded_resource_log(cur: "psycopg2.cursor", user_id: str):
  136. print("Clearing uploaded_resource_log...")
  137. cur.execute("""
  138. DELETE FROM
  139. uploaded_resource_log
  140. WHERE
  141. user_id = %s
  142. """, (user_id,))
  143. def clear_whitelist(cur: "psycopg2.cursor", user_id: str):
  144. print("Clearing whitelist...")
  145. cur.execute("""
  146. DELETE FROM
  147. whitelist
  148. WHERE
  149. user_id = %s
  150. """, (user_id,))
  151. main()
  152. # "I'm surprised you managed to write this entire Python file without spamming the word 'sus' everywhere." - Remie