diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm
index d8cff863625..3ea75be832a 100644
--- a/code/__HELPERS/roundend.dm
+++ b/code/__HELPERS/roundend.dm
@@ -742,86 +742,6 @@ GLOBAL_LIST_INIT(achievements_unlocked, list())
count++
return objective_parts.Join("
")
-/datum/controller/subsystem/ticker/proc/save_admin_data()
- if(IsAdminAdvancedProcCall())
- to_chat(usr, "Admin rank DB Sync blocked: Advanced ProcCall detected.")
- return
- if(CONFIG_GET(flag/admin_legacy_system)) //we're already using legacy system so there's nothing to save
- return
- else if(load_admins(TRUE)) //returns true if there was a database failure and the backup was loaded from
- return
- sync_ranks_with_db()
- var/list/sql_admins = list()
- for(var/i in GLOB.protected_admins)
- var/datum/admins/A = GLOB.protected_admins[i]
- sql_admins += list(list("ckey" = A.target, "rank" = A.rank_names()))
- SSdbcore.MassInsert(format_table_name("admin"), sql_admins, duplicate_key = TRUE)
- var/datum/db_query/query_admin_rank_update = SSdbcore.NewQuery("UPDATE [format_table_name("player")] p INNER JOIN [format_table_name("admin")] a ON p.ckey = a.ckey SET p.lastadminrank = a.rank")
- query_admin_rank_update.Execute()
- qdel(query_admin_rank_update)
-
- //json format backup file generation stored per server
- var/json_file = file("data/admins_backup.json")
- var/list/file_data = list(
- "ranks" = list(),
- "admins" = list(),
- "connections" = list(),
- )
- for(var/datum/admin_rank/R in GLOB.admin_ranks)
- file_data["ranks"]["[R.name]"] = list()
- file_data["ranks"]["[R.name]"]["include rights"] = R.include_rights
- file_data["ranks"]["[R.name]"]["exclude rights"] = R.exclude_rights
- file_data["ranks"]["[R.name]"]["can edit rights"] = R.can_edit_rights
-
- for(var/admin_ckey in GLOB.admin_datums + GLOB.deadmins)
- var/datum/admins/admin = GLOB.admin_datums[admin_ckey]
-
- if(!admin)
- admin = GLOB.deadmins[admin_ckey]
- if (!admin)
- continue
-
- file_data["admins"][admin_ckey] = admin.rank_names()
-
- if (admin.owner)
- file_data["connections"][admin_ckey] = list(
- "cid" = admin.owner.computer_id,
- "ip" = admin.owner.address,
- )
-
- fdel(json_file)
- WRITE_FILE(json_file, json_encode(file_data))
-
-/datum/controller/subsystem/ticker/proc/update_everything_flag_in_db()
- for(var/datum/admin_rank/R in GLOB.admin_ranks)
- var/list/flags = list()
- if(R.include_rights == R_EVERYTHING)
- flags += "flags"
- if(R.exclude_rights == R_EVERYTHING)
- flags += "exclude_flags"
- if(R.can_edit_rights == R_EVERYTHING)
- flags += "can_edit_flags"
- if(!flags.len)
- continue
- var/flags_to_check = flags.Join(" != [R_EVERYTHING] AND ") + " != [R_EVERYTHING]"
- var/datum/db_query/query_check_everything_ranks = SSdbcore.NewQuery(
- "SELECT flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] WHERE rank = :rank AND ([flags_to_check])",
- list("rank" = R.name)
- )
- if(!query_check_everything_ranks.Execute())
- qdel(query_check_everything_ranks)
- return
- if(query_check_everything_ranks.NextRow()) //no row is returned if the rank already has the correct flag value
- var/flags_to_update = flags.Join(" = [R_EVERYTHING], ") + " = [R_EVERYTHING]"
- var/datum/db_query/query_update_everything_ranks = SSdbcore.NewQuery(
- "UPDATE [format_table_name("admin_ranks")] SET [flags_to_update] WHERE rank = :rank",
- list("rank" = R.name)
- )
- if(!query_update_everything_ranks.Execute())
- qdel(query_update_everything_ranks)
- return
- qdel(query_update_everything_ranks)
- qdel(query_check_everything_ranks)
/datum/controller/subsystem/ticker/proc/cheevo_report()
var/list/parts = list()
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index f1ca584bfe5..22fc7ba29cd 100644
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -765,8 +765,6 @@ SUBSYSTEM_DEF(ticker)
/datum/controller/subsystem/ticker/Shutdown()
gather_newscaster() //called here so we ensure the log is created even upon admin reboot
- save_admin_data()
- update_everything_flag_in_db()
if(!round_end_sound)
round_end_sound = choose_round_end_song()
///The reference to the end of round sound that we have chosen.
diff --git a/code/modules/admin/admin_ranks.dm b/code/modules/admin/admin_ranks.dm
index c39464e3ef0..3b959d449cd 100644
--- a/code/modules/admin/admin_ranks.dm
+++ b/code/modules/admin/admin_ranks.dm
@@ -102,19 +102,8 @@ GLOBAL_PROTECT(protected_ranks)
if(3)
can_edit_rights |= flag
-/proc/sync_ranks_with_db()
- set waitfor = FALSE
-
- if(IsAdminAdvancedProcCall())
- to_chat(usr, "Admin rank DB Sync blocked: Advanced ProcCall detected.", confidential = TRUE)
- return
-
- var/list/sql_ranks = list()
- for(var/datum/admin_rank/R in GLOB.protected_ranks)
- sql_ranks += list(list("rank" = R.name, "flags" = R.include_rights, "exclude_flags" = R.exclude_rights, "can_edit_flags" = R.can_edit_rights))
- SSdbcore.MassInsert(format_table_name("admin_ranks"), sql_ranks, duplicate_key = TRUE)
-
-//load our rank - > rights associations
+/// Loads admin ranks.
+/// Return a list containing the backup data if they were loaded from the database backup json
/proc/load_admin_ranks(dbfail, no_update)
if(IsAdminAdvancedProcCall())
to_chat(usr, "Admin Reload blocked: Advanced ProcCall detected.", confidential = TRUE)
@@ -137,7 +126,7 @@ GLOBAL_PROTECT(protected_ranks)
GLOB.admin_ranks += R
GLOB.protected_ranks += R
previous_rank = R
- if(!CONFIG_GET(flag/admin_legacy_system) || dbfail)
+ if(!CONFIG_GET(flag/admin_legacy_system) && !dbfail)
if(CONFIG_GET(flag/load_legacy_ranks_only))
if(!no_update)
sync_ranks_with_db()
@@ -146,7 +135,7 @@ GLOBAL_PROTECT(protected_ranks)
if(!query_load_admin_ranks.Execute())
message_admins("Error loading admin ranks from database. Loading from backup.")
log_sql("Error loading admin ranks from database. Loading from backup.")
- dbfail = 1
+ dbfail = TRUE
else
while(query_load_admin_ranks.NextRow())
var/skip
@@ -220,12 +209,14 @@ GLOBAL_PROTECT(protected_ranks)
return jointext(names, "+")
+/// (Re)Loads the admin list.
+/// returns TRUE if database admins had to be loaded from the backup json
/proc/load_admins(no_update)
var/dbfail
if(!CONFIG_GET(flag/admin_legacy_system) && !SSdbcore.Connect())
message_admins("Failed to connect to database while loading admins. Loading from backup.")
log_sql("Failed to connect to database while loading admins. Loading from backup.")
- dbfail = 1
+ dbfail = TRUE
//clear the datums references
GLOB.admin_datums.Cut()
for(var/client/C in GLOB.admins)
@@ -251,7 +242,7 @@ GLOBAL_PROTECT(protected_ranks)
var/admin_rank = admins_regex.group[2]
new /datum/admins(ranks_from_rank_name(admin_rank), ckey(admin_key), force_active = FALSE, protected = TRUE)
- if(!CONFIG_GET(flag/admin_legacy_system) || dbfail)
+ if(!CONFIG_GET(flag/admin_legacy_system) && !dbfail)
var/datum/db_query/query_load_admins = SSdbcore.NewQuery("SELECT ckey, `rank`, feedback FROM [format_table_name("admin")] ORDER BY `rank`")
if(!query_load_admins.Execute())
message_admins("Error loading admins from database. Loading from backup.")
@@ -275,6 +266,9 @@ GLOBAL_PROTECT(protected_ranks)
var/datum/admins/admin_holder = new(admin_ranks, admin_ckey)
admin_holder.cached_feedback_link = admin_feedback || NO_FEEDBACK_LINK
qdel(query_load_admins)
+ if (!no_update)
+ save_admin_backup()
+ sync_admins_with_db()
//load admins from backup file
if(dbfail)
if(!backup_file_json)
@@ -286,14 +280,15 @@ GLOBAL_PROTECT(protected_ranks)
log_world("Unable to locate admins backup file.")
return
backup_file_json = json_decode(backup_file)
- for(var/J in backup_file_json["admins"])
+ for(var/backup_admin_ckey in backup_file_json["admins"])
var/skip
- for(var/A in GLOB.admin_datums + GLOB.deadmins)
- if(A == "[J]") //this admin was already loaded from txt override
+ for(var/admin_ckey in GLOB.admin_datums + GLOB.deadmins)
+ if(ckey(admin_ckey) == ckey("[backup_admin_ckey]")) //this admin was already loaded from txt override
skip = TRUE
+ break
if(skip)
continue
- new /datum/admins(ranks_from_rank_name(backup_file_json["admins"]["[J]"]), ckey("[J]"))
+ new /datum/admins(ranks_from_rank_name(backup_file_json["admins"]["[backup_admin_ckey]"]), ckey("[backup_admin_ckey]"))
#ifdef TESTING
var/msg = "Admins Built:\n"
for(var/ckey in GLOB.admin_datums)
@@ -302,3 +297,103 @@ GLOBAL_PROTECT(protected_ranks)
testing(msg)
#endif
return dbfail
+
+
+/proc/sync_ranks_with_db()
+ set waitfor = FALSE
+
+ if(IsAdminAdvancedProcCall())
+ to_chat(usr, "Admin rank DB Sync blocked: Advanced ProcCall detected.", confidential = TRUE)
+ return
+
+ var/list/sql_ranks = list()
+ for(var/datum/admin_rank/R as anything in GLOB.protected_ranks)
+ sql_ranks += list(list("rank" = R.name, "flags" = R.include_rights, "exclude_flags" = R.exclude_rights, "can_edit_flags" = R.can_edit_rights))
+ SSdbcore.MassInsert(format_table_name("admin_ranks"), sql_ranks, duplicate_key = TRUE)
+ update_everything_flag_in_db()
+
+
+/proc/update_everything_flag_in_db()
+ for(var/datum/admin_rank/R as anything in GLOB.admin_ranks)
+ var/list/flags = list()
+ if(R.include_rights == R_EVERYTHING)
+ flags += "flags"
+ if(R.exclude_rights == R_EVERYTHING)
+ flags += "exclude_flags"
+ if(R.can_edit_rights == R_EVERYTHING)
+ flags += "can_edit_flags"
+ if(!flags.len)
+ continue
+ var/flags_to_check = flags.Join(" != [R_EVERYTHING] AND ") + " != [R_EVERYTHING]"
+ var/datum/db_query/query_check_everything_ranks = SSdbcore.NewQuery(
+ "SELECT flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] WHERE rank = :rank AND ([flags_to_check])",
+ list("rank" = R.name)
+ )
+ if(!query_check_everything_ranks.Execute())
+ qdel(query_check_everything_ranks)
+ return
+ if(query_check_everything_ranks.NextRow()) //no row is returned if the rank already has the correct flag value
+ var/flags_to_update = flags.Join(" = [R_EVERYTHING], ") + " = [R_EVERYTHING]"
+ var/datum/db_query/query_update_everything_ranks = SSdbcore.NewQuery(
+ "UPDATE [format_table_name("admin_ranks")] SET [flags_to_update] WHERE rank = :rank",
+ list("rank" = R.name)
+ )
+ if(!query_update_everything_ranks.Execute())
+ qdel(query_update_everything_ranks)
+ return
+ qdel(query_update_everything_ranks)
+ qdel(query_check_everything_ranks)
+
+
+/proc/sync_admins_with_db()
+ if(IsAdminAdvancedProcCall())
+ to_chat(usr, "Admin rank DB Sync blocked: Advanced ProcCall detected.")
+ return
+
+ if(CONFIG_GET(flag/admin_legacy_system) || !SSdbcore.IsConnected()) //we're already using legacy system so there's nothing to save
+ return
+ sync_ranks_with_db()
+ var/list/sql_admins = list()
+ for(var/holder_ckey in GLOB.protected_admins)
+ var/datum/admins/holder = GLOB.protected_admins[holder_ckey]
+ sql_admins += list(list("ckey" = holder.target, "rank" = holder.rank_names()))
+ SSdbcore.MassInsert(format_table_name("admin"), sql_admins, duplicate_key = TRUE)
+ var/datum/db_query/query_admin_rank_update = SSdbcore.NewQuery("UPDATE [format_table_name("player")] AS p INNER JOIN [format_table_name("admin")] AS a ON p.ckey = a.ckey SET p.lastadminrank = a.rank")
+ query_admin_rank_update.Execute()
+ qdel(query_admin_rank_update)
+
+
+/proc/save_admin_backup()
+ if(IsAdminAdvancedProcCall())
+ to_chat(usr, "Admin rank DB Sync blocked: Advanced ProcCall detected.")
+ return
+
+ if(CONFIG_GET(flag/admin_legacy_system)) //we're already using legacy system so there's nothing to save
+ return
+
+ //json format backup file generation stored per server
+ var/json_file = file("data/admins_backup.json")
+ var/list/file_data = list(
+ "ranks" = list(),
+ "admins" = list()
+ )
+ for(var/datum/admin_rank/R as anything in GLOB.admin_ranks)
+ file_data["ranks"]["[R.name]"] = list()
+ file_data["ranks"]["[R.name]"]["include rights"] = R.include_rights
+ file_data["ranks"]["[R.name]"]["exclude rights"] = R.exclude_rights
+ file_data["ranks"]["[R.name]"]["can edit rights"] = R.can_edit_rights
+
+ for(var/admin_ckey in GLOB.admin_datums + GLOB.deadmins)
+ var/datum/admins/admin = GLOB.admin_datums[admin_ckey]
+
+ if(!admin)
+ admin = GLOB.deadmins[admin_ckey]
+ if (!admin)
+ continue
+
+ file_data["admins"][admin_ckey] = admin.rank_names()
+
+ admin.backup_connections()
+
+ fdel(json_file)
+ WRITE_FILE(json_file, json_encode(file_data, JSON_PRETTY_PRINT))
diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm
index 9d2525ed8fa..df08496ec2a 100644
--- a/code/modules/admin/holder2.dm
+++ b/code/modules/admin/holder2.dm
@@ -232,7 +232,7 @@ GLOBAL_PROTECT(href_token)
return VALID_2FA_CONNECTION
if (!SSdbcore.Connect())
- if (verify_backup_data(client) || (client.ckey in GLOB.protected_admins))
+ if (verify_admin_from_local_cache(client) || (client.ckey in GLOB.protected_admins))
return VALID_2FA_CONNECTION
else
return list(FALSE, null)
@@ -249,7 +249,8 @@ GLOBAL_PROTECT(href_token)
))
if (!query.Execute())
- qdel(query)
+ if (verify_admin_from_local_cache(client) || (client.ckey in GLOB.protected_admins))
+ return VALID_2FA_CONNECTION
return list(FALSE, null)
var/is_valid = FALSE
@@ -319,25 +320,30 @@ GLOBAL_PROTECT(href_token)
#undef ERROR_2FA_REQUEST_PERMISSIONS
-/datum/admins/proc/verify_backup_data(client/client)
- var/backup_file = file2text("data/admins_backup.json")
+/// Returns true if the admin's cid/ip is verified in the local cache
+/datum/admins/proc/verify_admin_from_local_cache(client/client)
+ var/backup_filename = "data/admin_connections/[ckey(client?.ckey)].json"
+ if (!fexists(backup_filename))
+ return FALSE
+ var/backup_file = file2text(backup_filename)
if (isnull(backup_file))
- log_world("Unable to locate admins backup file.")
+ log_world("Unable to load admin connection's last_connections.json backup file.")
return FALSE
- var/list/backup_file_json = json_decode(backup_file)
- var/connections = backup_file_json["connections"]
+ var/list/connections = json_decode(backup_file)
- // This can happen for older admins_backup.json files
if (isnull(connections))
return FALSE
- var/most_recent_valid_connection = connections[client?.ckey]
- if (isnull(most_recent_valid_connection))
- return FALSE
+ for (var/list/connection as anything in connections)
+ if (!islist(connection) || length(connection) < 2)
+ stack_trace("Invalid connection in admin connections backup file for `[client]`.")
+ continue
+ if (connection["cid"] == client?.computer_id && connection["ip"] == client?.address)
+ return TRUE
+
+ return FALSE
- return most_recent_valid_connection["cid"] == client?.computer_id \
- && most_recent_valid_connection["ip"] == client?.address
/datum/admins/proc/alert_2fa_necessary(client/client)
var/msg = " is trying to join, but needs to verify their ckey."
@@ -358,6 +364,46 @@ GLOBAL_PROTECT(href_token)
confidential = TRUE,
)
+/datum/admins/proc/backup_connections()
+ set waitfor = FALSE
+ if (!length(CONFIG_GET(string/admin_2fa_url)))
+ return
+ var/ckey = ckey(target)
+ if (!ckey)
+ CRASH("can't backup an admin datum assigned to a blank ckey")
+
+ if (!SSdbcore.Connect())
+ return
+
+ var/datum/db_query/query = SSdbcore.NewQuery({"
+ SELECT cid, INET_NTOA(ip) as ip FROM [format_table_name("admin_connections")]
+ WHERE
+ ckey = :ckey AND verification_time IS NOT NULL
+ "}, list(
+ "ckey" = ckey,
+ ))
+
+ if (!query.Execute())
+ qdel(query)
+ return
+ var/list/admin_connections = list()
+ while (query.NextRow())
+ admin_connections += LIST_VALUE_WRAP_LISTS(list(
+ "cid" = query.item[1],
+ "ip" = query.item[2],
+ ))
+
+ qdel(query)
+
+ if (length(admin_connections) < 1)
+ return
+
+
+ var/backup_file = "data/admin_connections/[ckey].json"
+ if (fexists(backup_file))
+ fdel(backup_file)
+ WRITE_FILE(file(backup_file), json_encode(admin_connections, JSON_PRETTY_PRINT))
+
/// Get the rank name of the admin
/datum/admins/proc/rank_names()
return join_admin_ranks(ranks)