ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit admin permissions.", ADMIN_CATEGORY_MAIN) user.holder.edit_admin_permissions(PERMISSIONS_PAGE_PERMISSIONS) #define PERMISSIONS_LOGS_PER_PAGE 20 /// List of all the actions you have ever been able to take in admin logs, keep in parity with the /// operation enum in the admin_log table GLOBAL_LIST_INIT(permission_action_types, list( PERMISSIONS_ACTION_ADMIN_ADDED, PERMISSIONS_ACTION_ADMIN_REMOVED, PERMISSIONS_ACTION_ADMIN_RANK_CHANGED, PERMISSIONS_ACTION_RANK_ADDED, PERMISSIONS_ACTION_RANK_REMOVED, PERMISSIONS_ACTION_RANK_CHANGED, PERMISSIONS_ACTION_NONE )) /datum/admins/proc/edit_admin_permissions(action, log_target, log_actor, log_operation, log_page) if(!check_rights(R_PERMISSIONS)) return var/datum/asset/asset_cache_datum = get_asset_datum(/datum/asset/group/permissions) asset_cache_datum.send(usr) var/list/output = list("") var/new_line = " | " if(action == PERMISSIONS_PAGE_PERMISSIONS) new_line = "
" output += {" \ [action == PERMISSIONS_PAGE_PERMISSIONS ? "\[Permissions\] [new_line] [action == PERMISSIONS_PAGE_RANKS ? "\[Ranks\] [new_line] [action == PERMISSIONS_PAGE_LOGGING ? "\[Logging\] [new_line] [action == PERMISSIONS_PAGE_HOUSEKEEPING ? "\[Housekeeping\]": "\[Housekeeping\]"] "} if(action != PERMISSIONS_PAGE_PERMISSIONS) output += "
" if(action == PERMISSIONS_PAGE_PERMISSIONS) output += {" Permissions Panel
"} for(var/admin_ckey in GLOB.admin_datums + GLOB.deadmins) var/datum/admins/admin_datum = GLOB.admin_datums[admin_ckey] if(!admin_datum) admin_datum = GLOB.deadmins[admin_ckey] if (!admin_datum) continue if(admin_datum.owner) admin_ckey = admin_datum.owner.key var/deadmin_link = "" if (admin_datum.deadmined) deadmin_link = "\[RA\]" else deadmin_link = "\[DA\]" var/verify_link = "" if (admin_datum.blocked_by_2fa) verify_link = "\[2FA VERIFY\]" output += {" "} output += {"
CKEY \[+\] RANK PERMISSIONS
[admin_ckey]
[deadmin_link] \[-\] \[SYNC TGDB\] [verify_link]
[admin_datum.rank_names()] [rights2text(admin_datum.rank_flags(), " ")]
Search:
"} else if(action == PERMISSIONS_PAGE_RANKS) var/creation_meaningful = check_rights(R_PERMISSIONS) && usr.client.holder.can_edit_rights_flags() != NONE output += {" Permissions Panel [creation_meaningful ? "\[Create Rank\]" : ""]
"} // First we're gonna collect admins by rank, for sorting purposes var/datum/db_query/query_extract_admins = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("admin")].ckey), ckey), [format_table_name("admin")].`rank` FROM [format_table_name("admin")]") if(!query_extract_admins.warn_execute()) qdel(query_extract_admins) return var/list/admins_by_rank = list() while(query_extract_admins.NextRow()) var/admin_key = query_extract_admins.item[1] var/admin_rank = query_extract_admins.item[2] for(var/datum/admin_rank/composed_rank as anything in ranks_from_rank_name(admin_rank)) admins_by_rank[composed_rank.name] ||= list() admins_by_rank[composed_rank.name] |= list(admin_key) QDEL_NULL(query_extract_admins) for(var/stored_key in GLOB.admin_datums) var/datum/admins/live_holder = GLOB.admin_datums[stored_key] for(var/datum/admin_rank/composed_rank as anything in live_holder.ranks) admins_by_rank[composed_rank.name] ||= list() admins_by_rank[composed_rank.name] |= list(stored_key) // Then, pull the full list of DB ranks var/datum/db_query/query_extract_ranks = SSdbcore.NewQuery("SELECT rank, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")]") if(!query_extract_ranks.warn_execute()) qdel(query_extract_ranks) return var/list/all_ranks = list() while(query_extract_ranks.NextRow()) var/loaded_rank = query_extract_ranks.item[1] var/rank_flags = text2num(query_extract_ranks.item[2]) var/rank_exclude_flags = text2num(query_extract_ranks.item[3]) var/rank_can_edit_flags = text2num(query_extract_ranks.item[4]) all_ranks[loaded_rank] = list("rank" = loaded_rank, "flags" = rank_flags, "exclude_flags" = rank_exclude_flags, "can_edit_flags" = rank_can_edit_flags) QDEL_NULL(query_extract_ranks) for(var/datum/admin_rank/rank as anything in GLOB.admin_ranks) // This does technically potentially mask local edits? to db ranks if that is even possible (should not be), but I prefer to be honest about longterm values. if(all_ranks[rank.name]) continue all_ranks[rank.name] = list("rank" = rank.name, "flags" = rank.include_rights, "exclude_flags" = rank.exclude_rights, "can_edit_flags" = rank.can_edit_rights) sortTim(all_ranks, GLOBAL_PROC_REF(cmp_text_asc)) for(var/rank_name in all_ranks) var/list/datum/admin_rank/rank_datums = ranks_from_rank_name(rank_name) if(!length(rank_datums)) continue var/datum/admin_rank/rank_datum = rank_datums[1] var/list/rank_info = all_ranks[rank_name] var/permissions = rights2text(rank_info["flags"], seperator = " ", prefix = "+") var/denied_permissions = rights2text(rank_info["exclude_flags"], seperator = " ", prefix = "-") var/editing_permissions = rights2text(rank_info["can_edit_flags"], seperator = " ", prefix = "*") var/modify_might_be_allowed = FALSE var/delete_might_be_allowed = FALSE if((rank_datum.source == RANK_SOURCE_DB && check_rights(R_DBRANKS)) || rank_datum.source == RANK_SOURCE_TEMPORARY) modify_might_be_allowed = TRUE delete_might_be_allowed = TRUE if(length(admins_by_rank[rank_name]) != 0) delete_might_be_allowed = FALSE if((usr.client.holder.can_edit_rights_flags() & rank_datum.rights) != rank_datum.rights) modify_might_be_allowed = FALSE delete_might_be_allowed = FALSE output += {" [rank_name] [modify_might_be_allowed ? " | \[Edit\]" : ""] [delete_might_be_allowed ? " | \[Delete\]" : ""]
Source: [rank_datum.pretty_print_source()]
Held By: [length(admins_by_rank[rank_name])]
Permissions: [permissions]
Denied: [denied_permissions]
Allowed to edit: [editing_permissions]
"} else if(action == PERMISSIONS_PAGE_LOGGING) output += {" Permissions Panel "} var/log_count = 0 var/page_count = 0 var/selected_page = 0 log_target ||= "" log_actor ||= "" log_operation ||= null if(log_operation == PERMISSIONS_ACTION_NONE) log_operation = null if(log_page) selected_page = text2num(log_page) var/list/action_options = list() for(var/action_type in GLOB.permission_action_types) if(action_type == log_operation || (isnull(log_operation) && action_type == PERMISSIONS_ACTION_NONE)) action_options += "" else action_options += "" // We intentionally do not carry page values through searches since they'd get messed with output += {"
[HrefTokenFormField()] Ckey Modified: Acting Admin: Action:
"} var/datum/db_query/query_count_admin_logs = SSdbcore.NewQuery({" SELECT COUNT(id) FROM [format_table_name("admin_log")] WHERE target LIKE CONCAT('%',:target,'%') AND adminckey LIKE CONCAT('%',:adminckey,'%') AND (:operation IS NULL OR operation = :operation) "}, list("target" = log_target, "adminckey" = log_actor, "operation" = log_operation) ) if(!query_count_admin_logs.warn_execute()) qdel(query_count_admin_logs) return if(query_count_admin_logs.NextRow()) log_count = text2num(query_count_admin_logs.item[1]) QDEL_NULL(query_count_admin_logs) if(log_count > PERMISSIONS_LOGS_PER_PAGE) output += "Page: " while(log_count > 0) output += {" | [page_count == selected_page ? "\[[page_count]\]" : "\[[page_count]\]"] "} log_count -= PERMISSIONS_LOGS_PER_PAGE page_count++ output += "|" var/datum/db_query/query_search_admin_logs = SSdbcore.NewQuery({" SELECT datetime, round_id, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), operation, IF(ckey IS NULL, target, byond_key), log FROM [format_table_name("admin_log")] LEFT JOIN [format_table_name("player")] ON target = ckey WHERE target LIKE CONCAT('%',:target,'%') AND adminckey LIKE CONCAT('%',:adminckey,'%') AND (:operation IS NULL OR operation = :operation) ORDER BY datetime DESC LIMIT :skip, :take "}, list("target" = log_target, "adminckey" = log_actor, "operation" = log_operation, "skip" = PERMISSIONS_LOGS_PER_PAGE * selected_page, "take" = PERMISSIONS_LOGS_PER_PAGE)) if(!query_search_admin_logs.warn_execute()) qdel(query_search_admin_logs) return var/action_found = FALSE while(query_search_admin_logs.NextRow()) var/datetime = query_search_admin_logs.item[1] var/round_id = query_search_admin_logs.item[2] var/admin_key = query_search_admin_logs.item[3] var/operation_applied = query_search_admin_logs.item[4] var/ckey_actioned = query_search_admin_logs.item[5] var/log = query_search_admin_logs.item[6] if(!action_found) action_found = TRUE output += "
" output += {"

[datetime] | Round ID [round_id] | Admin [admin_key] | Operation [operation_applied] on [ckey_actioned]
[log]


"} QDEL_NULL(query_search_admin_logs) else if(action == PERMISSIONS_PAGE_HOUSEKEEPING) output += {" Permissions Panel "} // We're gonna sanity check admin rank info, yea? // We're pulling from the db as a source of truth here, instead of trusting the local lists // First, pull all admin ranks which are used var/datum/db_query/query_extract_admins = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("admin")].ckey), ckey), [format_table_name("admin")].`rank` FROM [format_table_name("admin")]") if(!query_extract_admins.warn_execute()) qdel(query_extract_admins) return var/list/admins_by_rank = list() while(query_extract_admins.NextRow()) var/admin_key = query_extract_admins.item[1] var/admin_rank = query_extract_admins.item[2] for(var/datum/admin_rank/composed_rank as anything in ranks_from_rank_name(admin_rank)) admins_by_rank[composed_rank.name] += list(admin_key) QDEL_NULL(query_extract_admins) // Then, pull the full list of DB ranks to purity check against var/datum/db_query/query_extract_ranks = SSdbcore.NewQuery("SELECT rank, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")]") if(!query_extract_ranks.warn_execute()) qdel(query_extract_ranks) return var/list/all_db_ranks = list() while(query_extract_ranks.NextRow()) var/loaded_rank = query_extract_ranks.item[1] var/rank_flags = query_extract_ranks.item[2] var/rank_exclude_flags = query_extract_ranks.item[3] var/rank_can_edit_flags = query_extract_ranks.item[4] all_db_ranks[loaded_rank] = list("rank" = loaded_rank, "flags" = rank_flags, "exclude_flags" = rank_exclude_flags, "can_edit_flags" = rank_can_edit_flags) QDEL_NULL(query_extract_ranks) output += "

Admin ckeys with invalid ranks

" var/list/invalid_admin_ranks = admins_by_rank - all_db_ranks if(length(invalid_admin_ranks)) // Add a nice leader to the output output += "
" else output += "No invalid ranks found." for(var/illegal_rank in invalid_admin_ranks) for(var/misapplied_admin in admins_by_rank[illegal_rank]) output += {" [misapplied_admin] has the non-existent rank [illegal_rank] | \[Change Rank\] | \[Remove\]
"} output += "

Unused DB Ranks

" var/list/unused_ranks = all_db_ranks - admins_by_rank if(length(unused_ranks)) output += "
" else output += "No unused ranks found." sortTim(unused_ranks, GLOBAL_PROC_REF(cmp_text_asc)) for(var/unused_rank in unused_ranks) var/list/datum/admin_rank/rank_datums = ranks_from_rank_name(unused_rank) var/datum/admin_rank/rank_datum = rank_datums[1] var/list/rank_info = all_db_ranks[unused_rank] var/permissions = rights2text(text2num(rank_info["flags"]), seperator = " ", prefix = "+") var/denied_permissions = rights2text(text2num(rank_info["exclude_flags"]), seperator = " ", prefix = "-") var/editing_permissions = rights2text(text2num(rank_info["can_edit_flags"]), seperator = " ", prefix = "*") var/delete_might_be_allowed = FALSE // First case should never fail, but uh, just in case if(rank_datum.source == RANK_SOURCE_DB && check_rights(R_DBRANKS)) delete_might_be_allowed = TRUE if((usr.client.holder.can_edit_rights_flags() & rank_datum.rights) == rank_datum.rights) delete_might_be_allowed = FALSE output += {" Rank [unused_rank] is not held by any admin [delete_might_be_allowed ? " | \[Delete\]" : ""]
Source: [rank_datum.pretty_print_source()]
Permissions: [permissions]
Denied: [denied_permissions]
Allowed to edit: [editing_permissions]
"} if(QDELETED(usr)) return usr << browse("[jointext(output, "")]","window=editrights;size=1000x650") /datum/admins/proc/edit_rights_topic(list/href_list) if(!check_rights(R_PERMISSIONS)) message_admins("[key_name_admin(usr)] attempted to edit admin permissions without sufficient rights.") log_admin("[key_name(usr)] attempted to edit admin permissions without sufficient rights.") return if(IsAdminAdvancedProcCall()) to_chat(usr, span_adminprefix("Admin Edit blocked: Advanced ProcCall detected."), confidential = TRUE) return var/datum/asset/permissions_assets = get_asset_datum(/datum/asset/simple/namespaced/common) permissions_assets.send(usr.client) var/admin_key = href_list["key"] var/admin_ckey = ckey(admin_key) var/task = href_list["editrights"] var/datum/admins/target_admin_datum = GLOB.admin_datums[admin_ckey] if(!target_admin_datum) target_admin_datum = GLOB.deadmins[admin_ckey] if (!target_admin_datum && task != "add") return var/use_db var/skip var/legacy_only if(task == "activate" || task == "deactivate" || task == "sync" || task == "verify" || task == "permissions") skip = TRUE if(!CONFIG_GET(flag/admin_legacy_system) && CONFIG_GET(flag/protect_legacy_admins) && task == "rank") if(admin_ckey in GLOB.protected_admins) to_chat(usr, span_adminprefix("Editing the rank of this admin is blocked by server configuration."), confidential = TRUE) return if(!CONFIG_GET(flag/admin_legacy_system) && CONFIG_GET(flag/protect_legacy_ranks) && task == "permissions") if((target_admin_datum.ranks & GLOB.protected_ranks).len > 0) to_chat(usr, span_adminprefix("Editing the flags of this rank is blocked by server configuration."), confidential = TRUE) return if(CONFIG_GET(flag/load_legacy_ranks_only) && (task == "add" || task == "rank" || task == "permissions")) to_chat(usr, span_adminprefix("Database rank loading is disabled, only temporary changes can be made to a rank's permissions and permanently creating a new rank is blocked."), confidential = TRUE) legacy_only = TRUE if(check_rights(R_DBRANKS, FALSE) && !skip) if(!SSdbcore.Connect()) to_chat(usr, span_danger("Unable to connect to database, changes are temporary only."), confidential = TRUE) use_db = FALSE else use_db = tgui_alert(usr,"Permanent changes are saved to the database for future rounds, temporary changes will affect only the current round", "Permanent or Temporary?", list("Permanent", "Temporary", "Cancel")) if(isnull(use_db) || use_db == "Cancel") return if(use_db == "Permanent") use_db = TRUE else use_db = FALSE if(QDELETED(usr)) return if(target_admin_datum && (task != "sync" && task != "verify") && !check_if_greater_rights_than_holder(target_admin_datum)) message_admins("[key_name_admin(usr)] attempted to change the rank of [admin_key] without sufficient rights.") log_admin("[key_name(usr)] attempted to change the rank of [admin_key] without sufficient rights.") return switch(task) if("add") admin_ckey = add_admin(admin_ckey, admin_key, use_db) if(!admin_ckey) return if(!admin_key) // Prevents failures in logging admin rank changes. admin_key = admin_ckey change_admin_rank(admin_ckey, admin_key, use_db, null, legacy_only) if("remove") remove_admin(admin_ckey, admin_key, use_db, target_admin_datum) if("rank") change_admin_rank(admin_ckey, admin_key, use_db, target_admin_datum, legacy_only) if("permissions") change_admin_flags(admin_ckey, admin_key, target_admin_datum) if("activate") force_readmin(admin_key, target_admin_datum) if("deactivate") force_deadmin(admin_key, target_admin_datum) if("sync") sync_lastadminrank(admin_ckey, admin_key, target_admin_datum) if("verify") var/msg = "has authenticated [admin_ckey]" message_admins("[key_name_admin(usr)] [msg]") log_admin("[key_name(usr)] [msg]") target_admin_datum.bypass_2fa = TRUE target_admin_datum.associate(GLOB.directory[admin_ckey]) edit_admin_permissions(PERMISSIONS_PAGE_PERMISSIONS) /datum/admins/proc/add_admin(admin_ckey, admin_key, use_db) if(!check_rights(R_PERMISSIONS) || (use_db && !check_rights(R_DBRANKS))) return if(IsAdminAdvancedProcCall()) to_chat(usr, span_adminprefix("Admin Addition blocked: Advanced ProcCall detected."), confidential = TRUE) return if(admin_ckey) . = admin_ckey else admin_key = input("New admin's key","Admin key") as text|null . = ckey(admin_key) if(!.) return FALSE if(!admin_ckey && (. in (GLOB.admin_datums+GLOB.deadmins))) to_chat(usr, span_danger("[admin_key] is already an admin."), confidential = TRUE) return FALSE if(!use_db) return //if an admin exists without a datum they won't be caught by the above var/datum/db_query/query_admin_in_db = SSdbcore.NewQuery( "SELECT 1 FROM [format_table_name("admin")] WHERE ckey = :ckey", list("ckey" = .) ) if(!query_admin_in_db.warn_execute()) qdel(query_admin_in_db) return FALSE if(query_admin_in_db.NextRow()) qdel(query_admin_in_db) to_chat(usr, span_danger("[admin_key] already listed in admin database. Check the Housekeeping tab if they don't appear in the list of admins."), confidential = TRUE) return FALSE QDEL_NULL(query_admin_in_db) var/datum/db_query/query_add_admin = SSdbcore.NewQuery( "INSERT INTO [format_table_name("admin")] (ckey, `rank`) VALUES (:ckey, 'NEW ADMIN')", list("ckey" = .) ) if(!query_add_admin.warn_execute()) qdel(query_add_admin) return FALSE QDEL_NULL(query_add_admin) var/datum/db_query/query_add_admin_log = SSdbcore.NewQuery({" INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES (NOW(), :round_id, :adminckey, INET_ATON(:adminip), '[PERMISSIONS_ACTION_ADMIN_ADDED]', :target, CONCAT('New admin added: ', :target)) "}, list("round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "target" = .)) if(!query_add_admin_log.warn_execute()) qdel(query_add_admin_log) return QDEL_NULL(query_add_admin_log) /datum/admins/proc/remove_admin(admin_ckey, admin_key, use_db, datum/admins/target_holder) if(!check_rights(R_PERMISSIONS) || (use_db && !check_rights(R_DBRANKS))) return if(IsAdminAdvancedProcCall()) to_chat(usr, span_adminprefix("Admin Removal blocked: Advanced ProcCall detected."), confidential = TRUE) return if(tgui_alert(usr,"Are you sure you want to remove [admin_ckey]?", "Confirm Removal", list("Do it", "Cancel")) != "Do it") return GLOB.admin_datums -= admin_ckey GLOB.deadmins -= admin_ckey if(target_holder) target_holder.disassociate() var/m1 = "[key_name_admin(usr)] removed [admin_key] from the admins list [use_db ? "permanently" : "temporarily"]" var/m2 = "[key_name(usr)] removed [admin_key] from the admins list [use_db ? "permanently" : "temporarily"]" if(!use_db) message_admins(m1) log_admin(m2) return var/datum/db_query/query_remove_admin = SSdbcore.NewQuery( "DELETE FROM [format_table_name("admin")] WHERE ckey = :ckey", list("ckey" = admin_ckey) ) if(!query_remove_admin.warn_execute()) qdel(query_remove_admin) return QDEL_NULL(query_remove_admin) message_admins(m1) log_admin(m2) var/datum/db_query/query_remove_admin_log = SSdbcore.NewQuery({" INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES (NOW(), :round_id, :adminckey, INET_ATON(:adminip), '[PERMISSIONS_ACTION_ADMIN_REMOVED]', :admin_ckey, CONCAT('Admin removed: ', :admin_ckey)) "}, list("round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "admin_ckey" = admin_ckey)) if(!query_remove_admin_log.warn_execute()) qdel(query_remove_admin_log) return QDEL_NULL(query_remove_admin_log) sync_lastadminrank(admin_ckey, admin_key) /datum/admins/proc/force_readmin(admin_key, datum/admins/target_holder) if(!target_holder || !target_holder.deadmined) return target_holder.activate() message_admins("[key_name_admin(usr)] forcefully readmined [admin_key]") log_admin("[key_name(usr)] forcefully readmined [admin_key]") /datum/admins/proc/force_deadmin(admin_key, datum/admins/target_holder) if(!target_holder || target_holder.deadmined) return message_admins("[key_name_admin(usr)] forcefully deadmined [admin_key]") log_admin("[key_name(usr)] forcefully deadmined [admin_key]") target_holder.deactivate() //after logs so the deadmined admin can see the message. /datum/admins/proc/auto_deadmin() if(owner.is_localhost()) return FALSE if(owner.prefs.read_preference(/datum/preference/toggle/bypass_deadmin_in_centcom) && is_centcom_level(owner.mob.z) && !istype(owner.mob, /mob/dead/new_player)) return FALSE to_chat(owner, span_interface("You are now a normal player."), confidential = TRUE) var/old_owner = owner deactivate() message_admins("[old_owner] deadmined via auto-deadmin config.") log_admin("[old_owner] deadmined via auto-deadmin config.") return TRUE #define RANK_DONE ":) I'm Done" /datum/admins/proc/change_admin_rank(admin_ckey, admin_key, use_db, datum/admins/target_holder, legacy_only) if(!check_rights(R_PERMISSIONS) || (use_db && !check_rights(R_DBRANKS))) return if(IsAdminAdvancedProcCall()) to_chat(usr, span_adminprefix("Rank Modification blocked: Advanced ProcCall detected."), confidential = TRUE) return var/rank_type = RANK_SOURCE_TEMPORARY if(use_db) rank_type = RANK_SOURCE_DB var/list/rank_names = list() if(!use_db || (use_db && !legacy_only)) rank_names += "*New Rank*" for(var/datum/admin_rank/admin_rank as anything in GLOB.admin_ranks) if((admin_rank.rights & usr.client.holder.can_edit_rights_flags()) != admin_rank.rights) continue if(use_db && admin_rank.source != RANK_SOURCE_DB && admin_rank.source != RANK_SOURCE_TXT) continue rank_names[admin_rank.name] = admin_rank var/list/new_rank_names = list() var/list/custom_ranks = list() while (TRUE) var/list/display_rank_names = list(RANK_DONE) if (new_rank_names.len > 0) display_rank_names += "** SELECTED **" for (var/rank_name in new_rank_names) display_rank_names += rank_name display_rank_names += "---------" for (var/rank_name in rank_names) if (!(rank_name in display_rank_names)) display_rank_names += rank_name var/next_rank = input("Please select a rank, or select [RANK_DONE] if you are finished.") as null|anything in display_rank_names if (isnull(next_rank)) return if (next_rank == RANK_DONE) break // They clicked "** SELECTED **" or something silly. if (!(next_rank in rank_names)) continue if (next_rank in new_rank_names) new_rank_names -= next_rank continue if (next_rank == "*New Rank*") var/new_rank_name = input("Please input a new rank", "New custom rank") as text|null if (!new_rank_name) return var/datum/admin_rank/custom_rank = rank_names[new_rank_name] if(!isnull(custom_rank)) continue if (target_holder) custom_rank = new(new_rank_name, rank_type, target_holder.rank_flags()) else custom_rank = new(new_rank_name, rank_type) if(QDELETED(custom_rank)) continue GLOB.admin_ranks += custom_rank custom_ranks += custom_rank new_rank_names += new_rank_name new_rank_names += next_rank var/list/new_ranks = list() for (var/datum/admin_rank/admin_rank as anything in GLOB.admin_ranks) if (admin_rank.name in new_rank_names) new_ranks += admin_rank new_rank_names -= admin_rank.name if (new_rank_names.len == 0) break var/joined_rank = join_admin_ranks(new_ranks) var/m1 = "[key_name_admin(usr)] edited the admin rank of [admin_key] to [joined_rank] [use_db ? "permanently" : "temporarily"]" var/m2 = "[key_name(usr)] edited the admin rank of [admin_key] to [joined_rank] [use_db ? "permanently" : "temporarily"]" if(use_db) //if a player was tempminned before having a permanent change made to their rank they won't yet be in the db var/old_rank var/datum/db_query/query_admin_in_db = SSdbcore.NewQuery( "SELECT `rank` FROM [format_table_name("admin")] WHERE ckey = :admin_ckey", list("admin_ckey" = admin_ckey) ) if(!query_admin_in_db.warn_execute()) qdel(query_admin_in_db) return if(!query_admin_in_db.NextRow()) add_admin(admin_ckey, admin_key, TRUE) old_rank = "NEW ADMIN" else old_rank = query_admin_in_db.item[1] QDEL_NULL(query_admin_in_db) for (var/datum/admin_rank/custom_rank in custom_ranks) //similarly if a temp rank is created it won't be in the db if someone is permanently changed to it var/datum/db_query/query_rank_in_db = SSdbcore.NewQuery( "SELECT 1 FROM [format_table_name("admin_ranks")] WHERE `rank` = :new_rank", list("new_rank" = custom_rank.name) ) if(!query_rank_in_db.warn_execute()) qdel(query_rank_in_db) return if(query_rank_in_db.NextRow()) QDEL_NULL(query_rank_in_db) continue QDEL_NULL(query_rank_in_db) var/datum/db_query/query_add_rank = SSdbcore.NewQuery({" INSERT INTO [format_table_name("admin_ranks")] (`rank`, flags, exclude_flags, can_edit_flags) VALUES (:new_rank, '0', '0', '0') "}, list("new_rank" = custom_rank.name)) if(!query_add_rank.warn_execute()) qdel(query_add_rank) return QDEL_NULL(query_add_rank) var/datum/db_query/query_add_rank_log = SSdbcore.NewQuery({" INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES (NOW(), :round_id, :adminckey, INET_ATON(:adminip), '[PERMISSIONS_ACTION_RANK_ADDED]', :new_rank, CONCAT('New rank added: ', :new_rank)) "}, list("round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "new_rank" = custom_rank.name)) if(!query_add_rank_log.warn_execute()) qdel(query_add_rank_log) return QDEL_NULL(query_add_rank_log) var/datum/db_query/query_change_rank = SSdbcore.NewQuery( "UPDATE [format_table_name("admin")] SET `rank` = :new_rank WHERE ckey = :admin_ckey", list("new_rank" = joined_rank, "admin_ckey" = admin_ckey) ) if(!query_change_rank.warn_execute()) qdel(query_change_rank) return QDEL_NULL(query_change_rank) message_admins(m1) log_admin(m2) var/datum/db_query/query_change_rank_log = SSdbcore.NewQuery({" INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES (NOW(), :round_id, :adminckey, INET_ATON(:adminip), '[PERMISSIONS_ACTION_ADMIN_RANK_CHANGED]', :target, CONCAT('Rank of ', :target, ' changed from ', :old_rank, ' to ', :new_rank)) "}, list("round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "target" = admin_ckey, "old_rank" = old_rank, "new_rank" = joined_rank)) if(!query_change_rank_log.warn_execute()) qdel(query_change_rank_log) return QDEL_NULL(query_change_rank_log) else message_admins(m1) log_admin(m2) if(target_holder) //they were previously an admin target_holder.disassociate() //existing admin needs to be disassociated target_holder.ranks = new_ranks //set the admin_rank as our rank target_holder.bypass_2fa = TRUE // Another admin has cleared us var/client/target_client = GLOB.directory[admin_ckey] target_holder.associate(target_client) else target_holder = new(new_ranks, admin_ckey) //new admin target_holder.bypass_2fa = TRUE // Another admin has cleared us target_holder.activate() #undef RANK_DONE /// Changes, for this round only, the flags a particular admin gets to use /datum/admins/proc/change_admin_flags(admin_ckey, admin_key, datum/admins/admin_holder) if(!check_rights(R_PERMISSIONS)) return if(IsAdminAdvancedProcCall()) to_chat(usr, span_adminprefix("Rank Modification blocked: Advanced ProcCall detected."), confidential = TRUE) return var/new_flags = input_bitfield( usr, "Admin rights
This will affect only the current admin ([admin_ckey]), temporarily", "admin_flags", admin_holder.rank_flags(), 350, 590, allowed_edit_field = usr.client.holder.can_edit_rights_flags(), ) if(isnull(new_flags)) return admin_holder.disassociate() if (findtext(admin_holder.rank_names(), "([admin_ckey])")) var/datum/admin_rank/rank = admin_holder.ranks[1] rank.rights = new_flags rank.include_rights = new_flags rank.exclude_rights = NONE rank.can_edit_rights = rank.can_edit_rights else // Not a modified subrank, need to duplicate the admin_rank datum to prevent modifying others too. var/datum/admin_rank/new_admin_rank = new( /* init_name = */ "[admin_holder.rank_names()]([admin_ckey])", /* init_source = */ RANK_SOURCE_TEMPORARY, /* init_rights = */ new_flags, // rank_flags() includes the exclude rights, so we no longer need to handle them separately. /* init_exclude_rights = */ NONE, /* init_edit_rights = */ admin_holder.can_edit_rights_flags(), ) admin_holder.ranks = list(new_admin_rank) var/log = "[key_name(usr)] has updated the admin rights of [admin_ckey] into [rights2text(new_flags)]" message_admins(log) log_admin(log) var/client/admin_client = GLOB.directory[admin_ckey] admin_holder.associate(admin_client) /// Polls usr for a new rank to add to either JUST this round, or the DB /datum/admins/proc/add_rank() if(!check_rights(R_PERMISSIONS)) to_chat(usr, span_adminprefix("You don't have the permissions for this."), confidential = TRUE) return if(IsAdminAdvancedProcCall()) to_chat(usr, span_adminprefix("Rank Addition blocked: Advanced ProcCall detected."), confidential = TRUE) return if(usr.client.holder.can_edit_rights_flags() == NONE) to_chat(usr, span_adminprefix("You are not allowed to add any rights."), confidential = TRUE) return var/new_rank_name = input("Please input a new rank", "New custom rank") as text|null if (!new_rank_name) return var/list/datum/admin_rank/existing_ranks = ranks_from_rank_name(new_rank_name) if (length(existing_ranks)) to_chat(usr, span_adminprefix("A rank by this name already exists, sorry!."), confidential = TRUE) return var/rights = input_bitfield( usr, "New rights for [new_rank_name]", "admin_flags", NONE, 350, 590, allowed_edit_field = usr.client.holder.can_edit_rights_flags(), ) var/excluded_rights = input_bitfield( usr, "New excluded rights for [new_rank_name]", "admin_flags", NONE, 350, 590, allowed_edit_field = usr.client.holder.can_edit_rights_flags(), ) var/edit_rights = input_bitfield( usr, "New editing rights for [new_rank_name]", "admin_flags", NONE, 350, 590, allowed_edit_field = usr.client.holder.can_edit_rights_flags(), ) var/use_db = FALSE if(check_rights(R_DBRANKS, FALSE)) if(!SSdbcore.Connect()) to_chat(usr, span_danger("Unable to connect to database, changes are temporary only."), confidential = TRUE) use_db = FALSE else var/use_db_response = tgui_alert(usr,"Permanent changes are saved to the database for future rounds, temporary changes will affect only the current round", "Permanent or Temporary?", list("Permanent", "Temporary", "Cancel")) if(isnull(use_db_response) || use_db_response == "Cancel") return if(use_db_response == "Permanent") use_db = TRUE else use_db = FALSE if(QDELETED(usr)) return var/datum/admin_rank/custom_rank if(use_db) custom_rank = new(new_rank_name, RANK_SOURCE_DB, rights, excluded_rights, edit_rights) else custom_rank = new(new_rank_name, RANK_SOURCE_TEMPORARY, rights, excluded_rights, edit_rights) if(QDELETED(custom_rank)) to_chat(usr, span_danger("Rank creation failed, check runtimes."), confidential = TRUE) return GLOB.admin_ranks += custom_rank var/m1 = "[key_name_admin(usr)] created the new [use_db ? "permanent" : "temporary"] rank [new_rank_name]" var/m2 = "[key_name(usr)] created the new [use_db ? "permanent" : "temporary"] rank [new_rank_name]" if(!use_db) message_admins(m1) log_admin(m2) return // Shit check for conflicts var/datum/db_query/query_rank_in_db = SSdbcore.NewQuery( "SELECT 1 FROM [format_table_name("admin_ranks")] WHERE `rank` = :new_rank", list("new_rank" = custom_rank.name) ) if(!query_rank_in_db.warn_execute()) qdel(query_rank_in_db) return if(query_rank_in_db.NextRow()) qdel(query_rank_in_db) return QDEL_NULL(query_rank_in_db) var/datum/db_query/query_add_rank = SSdbcore.NewQuery({" INSERT INTO [format_table_name("admin_ranks")] (`rank`, flags, exclude_flags, can_edit_flags) VALUES (:new_rank, :rights, :excluded_rights, :edit_rights) "}, list("new_rank" = custom_rank.name, "rights" = rights, "excluded_rights" = excluded_rights, "edit_rights" = edit_rights)) if(!query_add_rank.warn_execute()) qdel(query_add_rank) return QDEL_NULL(query_add_rank) message_admins(m1) log_admin(m2) var/datum/db_query/query_add_rank_log = SSdbcore.NewQuery({" INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES (NOW(), :round_id, :adminckey, INET_ATON(:adminip), '[PERMISSIONS_ACTION_RANK_ADDED]', :new_rank, CONCAT('New rank added: ', :new_rank, ' (', :rights, ')', ' (', :excluded_rights, ')', ' (', :edit_rights, ')')) "}, list("round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "new_rank" = custom_rank.name, "rights" = rights, "excluded_rights" = excluded_rights, "edit_rights" = edit_rights)) if(!query_add_rank_log.warn_execute()) qdel(query_add_rank_log) return QDEL_NULL(query_add_rank_log) /// Removes a rank from the db/temp loading /datum/admins/proc/remove_rank(admin_rank) if(!admin_rank) return if(!check_rights(R_PERMISSIONS)) message_admins("[key_name_admin(usr)] attempted to remove a rank without sufficient rights.") log_admin("[key_name(usr)] attempted to remove a rank without sufficient rights.") return if(IsAdminAdvancedProcCall()) to_chat(usr, span_adminprefix("Rank Deletion blocked: Advanced ProcCall detected."), confidential = TRUE) return for(var/datum/admin_rank/R in GLOB.admin_ranks) if(R.name == admin_rank && ((R.rights & usr.client.holder.can_edit_rights_flags()) != R.rights)) to_chat(usr, span_adminprefix("You don't have edit rights to all the rights this rank has, rank deletion not permitted."), confidential = TRUE) return var/list/datum/admin_rank/target_ranks = ranks_from_rank_name(admin_rank) if (!target_ranks || length(target_ranks) > 1) return var/datum/admin_rank/target_rank = target_ranks[1] var/local_only_deletion switch(target_rank.source) if(RANK_SOURCE_LOCAL) to_chat(usr, span_adminprefix("Localhost rank cannot be deleted."), confidential = TRUE) return // This handles protected ranks on its own if(RANK_SOURCE_TXT) to_chat(usr, span_adminprefix("Text ranks cannot be meaningfully deleted, go modify admin_ranks.txt"), confidential = TRUE) return if(RANK_SOURCE_BACKUP) to_chat(usr, span_adminprefix("Backup ranks cannot usefully be deleted, as they are stored in a temp json, go uh... edit that? I guess?."), confidential = TRUE) return if(RANK_SOURCE_TEMPORARY) local_only_deletion = TRUE if(RANK_SOURCE_DB) local_only_deletion = FALSE if(!local_only_deletion && CONFIG_GET(flag/load_legacy_ranks_only)) to_chat(usr, span_adminprefix("Database Rank deletion not permitted while database rank loading is disabled, deleting our local copy."), confidential = TRUE) local_only_deletion = TRUE if(!local_only_deletion) var/datum/db_query/query_admins_with_rank = SSdbcore.NewQuery( "SELECT 1 FROM [format_table_name("admin")] WHERE `rank` = :admin_rank", list("admin_rank" = admin_rank) ) if(!query_admins_with_rank.warn_execute()) qdel(query_admins_with_rank) return if(query_admins_with_rank.NextRow()) qdel(query_admins_with_rank) to_chat(usr, span_danger("Error: Rank deletion attempted while db rank still used; Tell a coder, this shouldn't happen."), confidential = TRUE) return QDEL_NULL(query_admins_with_rank) for(var/admin_name in GLOB.admin_datums) var/datum/admins/existing_min = GLOB.admin_datums[admin_name] if(target_rank in existing_min.ranks) to_chat(usr, span_danger("Error: Rank deletion attempted while rank still used; Tell a coder, this shouldn't happen."), confidential = TRUE) return if(tgui_alert(usr,"Are you sure you want to remove [admin_rank]?", "Confirm Removal", list("Do it","Cancel")) != "Do it") return var/m1 = "[key_name_admin(usr)] removed rank [admin_rank] [local_only_deletion ? "temporarially" : "permanently"]" var/m2 = "[key_name(usr)] removed rank [admin_rank] [local_only_deletion ? "temporarially" : "permanently"]" GLOB.admin_ranks -= target_rank QDEL_NULL(target_rank) if(local_only_deletion) message_admins(m1) log_admin(m2) return var/datum/db_query/query_remove_rank = SSdbcore.NewQuery( "DELETE FROM [format_table_name("admin_ranks")] WHERE `rank` = :admin_rank", list("admin_rank" = admin_rank) ) if(!query_remove_rank.warn_execute()) qdel(query_remove_rank) return QDEL_NULL(query_remove_rank) message_admins(m1) log_admin(m2) var/datum/db_query/query_remove_rank_log = SSdbcore.NewQuery({" INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES (NOW(), :round_id, :adminckey, INET_ATON(:adminip), '[PERMISSIONS_ACTION_RANK_REMOVED]', :admin_rank, CONCAT('Rank removed: ', :admin_rank)) "}, list("round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "admin_rank" = admin_rank)) if(!query_remove_rank_log.warn_execute()) qdel(query_remove_rank_log) return QDEL_NULL(query_remove_rank_log) /// Changes the flags on either a DB or local rank /datum/admins/proc/change_rank(admin_rank) if(!admin_rank) return if(!check_rights(R_PERMISSIONS)) message_admins("[key_name_admin(usr)] attempted to edit rank permissions without sufficient rights.") log_admin("[key_name(usr)] attempted to edit rank permissions without sufficient rights.") return if(IsAdminAdvancedProcCall()) to_chat(usr, span_adminprefix("Rank Edit blocked: Advanced ProcCall detected."), confidential = TRUE) return var/datum/asset/permissions_assets = get_asset_datum(/datum/asset/simple/namespaced/common) permissions_assets.send(usr.client) var/list/datum/admin_rank/target_ranks = ranks_from_rank_name(admin_rank) if (!target_ranks || length(target_ranks) > 1) return var/datum/admin_rank/target_rank = target_ranks[1] if(target_rank.name != admin_rank) // Somehow to_chat(usr, span_adminprefix("Passed rank does not match target, somehow."), confidential = TRUE) return if((target_rank.rights & usr.client.holder.can_edit_rights_flags()) != target_rank.rights) to_chat(usr, span_adminprefix("You don't have edit rights to all the rights this rank has, you aren't allowed to modify it."), confidential = TRUE) return var/attempt_db = FALSE switch(target_rank.source) if(RANK_SOURCE_LOCAL) to_chat(usr, span_adminprefix("Localhost rank cannot be modified."), confidential = TRUE) return // This handles protected ranks on its own if(RANK_SOURCE_TXT) to_chat(usr, span_adminprefix("Text ranks cannot be meaningfully modified, go modify admin_ranks.txt"), confidential = TRUE) return if(RANK_SOURCE_BACKUP) to_chat(usr, span_adminprefix("Backup ranks cannot usefully be modified, as they are stored in a temp json, go uh... edit that? I guess?."), confidential = TRUE) return // For completeness if(RANK_SOURCE_TEMPORARY) attempt_db = FALSE if(RANK_SOURCE_DB) if(!check_rights(R_DBRANKS, FALSE)) message_admins("[key_name_admin(usr)] attempted to edit db rank permissions without sufficient rights.") log_admin("[key_name(usr)] attempted to edit db rank permissions without sufficient rights.") return attempt_db = TRUE // We do not block editing ranks on protected admins which are not also protected // This means an admin could in theory bypass protections if they modified a linked rank (such as game admin) which is not also protected // It might be wise to make the permissions afforded by protected ranks inviolable. I'm unsure. if(CONFIG_GET(flag/load_legacy_ranks_only)) to_chat(usr, span_adminprefix("Database rank loading is disabled, only temporary changes can be made to a rank's permissions."), confidential = TRUE) attempt_db = FALSE var/use_db = FALSE if(attempt_db) if(!SSdbcore.Connect()) to_chat(usr, span_danger("Unable to connect to database, canceling."), confidential = TRUE) return use_db = TRUE // Similarly, I want to shit check flags. This is sort of... paranoid but I want to be careful here var/working_rights = NONE var/working_exclude_rights = NONE var/working_can_edit_rights = NONE // Not allowed to permenantly edit a rank if it isn't IN the db already // This is a real shitcheck but just to be sure if(use_db) var/datum/db_query/query_db_rank_info = SSdbcore.NewQuery({" SELECT flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] WHERE rank = :rank_name "}, list("rank_name" = admin_rank)) if(!query_db_rank_info.warn_execute()) qdel(query_db_rank_info) if(query_db_rank_info.NextRow()) working_rights = query_db_rank_info.item[1] working_exclude_rights = query_db_rank_info.item[2] working_can_edit_rights = query_db_rank_info.item[3] else // Couldn't find anything, no db memes then to_chat(usr, span_adminprefix("Rank does not exist in database, exiting."), confidential = TRUE) qdel(query_db_rank_info) return QDEL_NULL(query_db_rank_info) else working_rights = target_rank.include_rights working_exclude_rights = target_rank.exclude_rights working_can_edit_rights = target_rank.can_edit_rights while(TRUE) var/what_to_edit = tgui_input_list(usr, "What do you want to edit", "Rank Editing", list("Rights", "Excluded Rights", "Edit Rights", "Finished")) var/existing_flags = NONE var/pretty_name switch(what_to_edit) if("Rights") existing_flags = working_rights pretty_name = "rights" if("Excluded Rights") existing_flags = working_exclude_rights pretty_name = "excluded rights" if("Edit Rights") existing_flags = working_can_edit_rights pretty_name = "editing rights" else return var/new_flags = input_bitfield( usr, "Editing [target_rank.name] [what_to_edit]", "admin_flags", existing_flags, 350, 590, allowed_edit_field = usr.client.holder.can_edit_rights_flags(), ) if(isnull(new_flags)) to_chat(usr, span_adminprefix("Canceled editing rank."), confidential = TRUE) return // Gotta turn it off and on again var/list/datum/admins/impacted_admins_to_client = list() for(var/admin_key in GLOB.admin_datums) var/datum/admins/checking = GLOB.admin_datums[admin_key] if(!checking.owner) continue if(!(target_rank in checking.ranks)) continue impacted_admins_to_client[checking] = checking.owner checking.disassociate() switch(what_to_edit) if("Rights") target_rank.include_rights = new_flags working_rights = new_flags if("Excluded Rights") target_rank.exclude_rights = new_flags working_exclude_rights = new_flags if("Edit Rights") target_rank.can_edit_rights = new_flags working_can_edit_rights = new_flags var/log = "[key_name(usr)] has [use_db ? "permenantly" : "temporarially"] updated the [pretty_name] of the [admin_rank] rank to [rights2text(new_flags)]" message_admins(log) log_admin(log) for(var/datum/admins/modified as anything in impacted_admins_to_client) modified.associate(impacted_admins_to_client[modified]) if(!use_db) continue // Only one at a time to avoid carrying over temp changes // Doing it as we are does technically mean conflicts can occur, but that's rare enough I'm ok with it var/datum/db_query/query_update_rank switch(what_to_edit) if("Rights") query_update_rank = SSdbcore.NewQuery({" UPDATE [format_table_name("admin_ranks")] SET flags = :flags WHERE rank = :rank_name "}, list("rank_name" = admin_rank, "flags" = new_flags)) if("Excluded Rights") query_update_rank = SSdbcore.NewQuery({" UPDATE [format_table_name("admin_ranks")] SET exclude_flags = :exclude_flags WHERE rank = :rank_name "}, list("rank_name" = admin_rank, "exclude_flags" = new_flags)) if("Editing Rights") query_update_rank = SSdbcore.NewQuery({" UPDATE [format_table_name("admin_ranks")] SET can_edit_flags = :can_edit_flags WHERE rank = :rank_name "}, list("rank_name" = admin_rank, "can_edit_flags" = new_flags)) if(!query_update_rank.warn_execute()) qdel(query_update_rank) return QDEL_NULL(query_update_rank) var/datum/db_query/query_update_rank_log = SSdbcore.NewQuery({" INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES (NOW(), :round_id, :adminckey, INET_ATON(:adminip), '[PERMISSIONS_ACTION_RANK_CHANGED]', :admin_rank, CONCAT('Rank changed: ', :admin_rank)) "}, list("round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "admin_rank" = admin_rank)) if(!query_update_rank_log.warn_execute()) qdel(query_update_rank_log) return QDEL_NULL(query_update_rank_log) /datum/admins/proc/sync_lastadminrank(admin_ckey, admin_key, datum/admins/target_holder) var/sqlrank = "Player" if (target_holder) sqlrank = target_holder.rank_names() var/datum/db_query/query_sync_lastadminrank = SSdbcore.NewQuery( "UPDATE [format_table_name("player")] SET lastadminrank = :rank WHERE ckey = :ckey", list("rank" = sqlrank, "ckey" = admin_ckey) ) if(!query_sync_lastadminrank.warn_execute()) qdel(query_sync_lastadminrank) return QDEL_NULL(query_sync_lastadminrank) to_chat(usr, span_admin("Sync of [admin_key] successful."), confidential = TRUE) #undef PERMISSIONS_LOGS_PER_PAGE