diff --git a/code/__defines/admin.dm b/code/__defines/admin.dm
index e8c98d6575..d2405dc7f8 100644
--- a/code/__defines/admin.dm
+++ b/code/__defines/admin.dm
@@ -42,10 +42,11 @@
#define R_EVENT (1<<14)
#define R_HOST (1<<15) //higher than this will overflow
#define R_MENTOR (1<<16)
+#define R_DBRANKS (1<<17)
#define R_DEFAULT R_NONE
-#define R_EVERYTHING (1<<17)-1 //the sum of all other rank permissions, used for +EVERYTHING
+#define R_EVERYTHING (1<<18)-1 //the sum of all other rank permissions, used for +EVERYTHING
#define R_HOLDER ((R_EVERYTHING) & (~(R_MENTOR | R_STEALTH)))
#define SMITE_BREAKLEGS "Break Legs"
@@ -107,6 +108,28 @@
#define AHELP_CLOSED 2
#define AHELP_RESOLVED 3
+// Page numbers for the Permission Panel
+#define PERMISSIONS_PAGE_PERMISSIONS 1
+#define PERMISSIONS_PAGE_RANKS 2
+#define PERMISSIONS_PAGE_LOGGING 3
+#define PERMISSIONS_PAGE_HOUSEKEEPING 4
+
+// Actions that can be logged in the admin_log table, excepting NONE
+#define PERMISSIONS_ACTION_ADMIN_ADDED "add admin"
+#define PERMISSIONS_ACTION_ADMIN_REMOVED "remove admin"
+#define PERMISSIONS_ACTION_ADMIN_RANK_CHANGED "change admin rank"
+#define PERMISSIONS_ACTION_RANK_ADDED "add rank"
+#define PERMISSIONS_ACTION_RANK_REMOVED "remove rank"
+#define PERMISSIONS_ACTION_RANK_CHANGED "change rank flags"
+#define PERMISSIONS_ACTION_NONE "none"
+
+// The types of ranks you can have
+#define RANK_SOURCE_LOCAL "rank_local"
+#define RANK_SOURCE_TXT "rank_txt"
+#define RANK_SOURCE_DB "rank_db"
+#define RANK_SOURCE_BACKUP "rank_backup"
+#define RANK_SOURCE_TEMPORARY "rank_temp"
+
// LOG BROWSE TYPES
#define BROWSE_ROOT_ALL_LOGS 1
#define BROWSE_ROOT_RUNTIME_LOGS 2
diff --git a/code/__defines/admin_verb.dm b/code/__defines/admin_verb.dm
index 83eb564b36..27b2209ff8 100644
--- a/code/__defines/admin_verb.dm
+++ b/code/__defines/admin_verb.dm
@@ -83,6 +83,7 @@ _ADMIN_VERB(verb_path_name, verb_permissions, verb_name, verb_desc, verb_categor
#define ADMIN_CATEGORY_SHUTTLE "Admin.Shuttle"
#define ADMIN_CATEGORY_LOGS "Admin.Logs"
#define ADMIN_CATEGORY_MISC "Admin.Misc"
+#define ADMIN_CATEGORY_SECRETS "Admin.Secrets"
// Special categories that are separated
#define ADMIN_CATEGORY_DEBUG "Debug"
diff --git a/code/_global_vars/bitfields.dm b/code/_global_vars/bitfields.dm
index 9c39834eb5..8a5650b3a2 100644
--- a/code/_global_vars/bitfields.dm
+++ b/code/_global_vars/bitfields.dm
@@ -32,6 +32,7 @@ DEFINE_BITFIELD(admin_flags, list(
"REJUVINATE" = R_REJUVINATE,
"BAN" = R_BAN,
"BUILDMODE" = R_BUILDMODE,
+ "DBRANKS" = R_DBRANKS,
"DEBUG" = R_DEBUG,
"FUN" = R_FUN,
"PERMISSIONS" = R_PERMISSIONS,
diff --git a/code/_helpers/type2type.dm b/code/_helpers/type2type.dm
index 4a40a5530c..a70f7f4144 100644
--- a/code/_helpers/type2type.dm
+++ b/code/_helpers/type2type.dm
@@ -166,23 +166,44 @@ GLOBAL_LIST_INIT(modulo_angle_to_dir, list(NORTH,NORTHEAST,EAST,SOUTHEAST,SOUTH,
else return ICON_OVERLAY
// Converts a rights bitfield into a string
-/proc/rights2text(rights,seperator="")
- if (rights & R_BUILDMODE) . += "[seperator]+BUILDMODE"
- if (rights & R_ADMIN) . += "[seperator]+ADMIN"
- if (rights & R_BAN) . += "[seperator]+BAN"
- if (rights & R_FUN) . += "[seperator]+FUN"
- if (rights & R_SERVER) . += "[seperator]+SERVER"
- if (rights & R_DEBUG) . += "[seperator]+DEBUG"
- if (rights & R_POSSESS) . += "[seperator]+POSSESS"
- if (rights & R_PERMISSIONS) . += "[seperator]+PERMISSIONS"
- if (rights & R_STEALTH) . += "[seperator]+STEALTH"
- if (rights & R_REJUVINATE) . += "[seperator]+REJUVINATE"
- if (rights & R_VAREDIT) . += "[seperator]+VAREDIT"
- if (rights & R_SOUNDS) . += "[seperator]+SOUND"
- if (rights & R_SPAWN) . += "[seperator]+SPAWN"
- if (rights & R_MOD) . += "[seperator]+MODERATOR"
- if (rights & R_EVENT) . += "[seperator]+EVENT"
- if (rights & R_MENTOR) . += "[seperator]+MENTOR"
+/proc/rights2text(rights, seperator="", prefix = "+")
+ seperator += prefix
+ if(rights & R_BUILDMODE)
+ . += "[seperator]BUILDMODE"
+ if(rights & R_ADMIN)
+ . += "[seperator]ADMIN"
+ if(rights & R_BAN)
+ . += "[seperator]BAN"
+ if(rights & R_FUN)
+ . += "[seperator]FUN"
+ if(rights & R_SERVER)
+ . += "[seperator]SERVER"
+ if(rights & R_DEBUG)
+ . += "[seperator]DEBUG"
+ if(rights & R_POSSESS)
+ . += "[seperator]POSSESS"
+ if(rights & R_PERMISSIONS)
+ . += "[seperator]PERMISSIONS"
+ if(rights & R_STEALTH)
+ . += "[seperator]STEALTH"
+ if(rights & R_REJUVINATE)
+ . += "[seperator]REJUVINATE"
+ if(rights & R_VAREDIT)
+ . += "[seperator]VAREDIT"
+ if(rights & R_SOUNDS)
+ . += "[seperator]SOUND"
+ if(rights & R_SPAWN)
+ . += "[seperator]SPAWN"
+ if (rights & R_MOD)
+ . += "[seperator]MODERATOR"
+ if (rights & R_EVENT)
+ . += "[seperator]EVENT"
+ if (rights & R_MENTOR)
+ . += "[seperator]MENTOR"
+ if(rights & R_DBRANKS)
+ . += "[seperator]DBRANKS"
+ if(!.)
+ . = "NONE"
return .
// Converts a hexadecimal color (e.g. #FF0050) to a list of numbers for red, green, and blue (e.g. list(255,0,80) ).
diff --git a/code/game/gamemodes/cult/ritual.dm b/code/game/gamemodes/cult/ritual.dm
index 1c15cdd504..2d6d42bbdb 100644
--- a/code/game/gamemodes/cult/ritual.dm
+++ b/code/game/gamemodes/cult/ritual.dm
@@ -6,7 +6,7 @@ GLOBAL_LIST_INIT(engwords, list("travel", "blood", "join", "hell", "destroy", "t
GLOBAL_LIST_INIT(rnwords, list("ire","ego","nahlizet","certum","veri","jatkaa","mgar","balaq", "karazet", "geeri"))
/client/proc/check_words() // -- Urist
- set category = "Admin.Secrets"
+ set category = ADMIN_CATEGORY_SECRETS
set name = "Check Rune Words"
set desc = "Check the rune-word meaning"
if(!GLOB.cultwords["travel"])
diff --git a/code/modules/admin/admin_ranks.dm b/code/modules/admin/admin_ranks.dm
index 65cf7949f0..ad4a2e2137 100644
--- a/code/modules/admin/admin_ranks.dm
+++ b/code/modules/admin/admin_ranks.dm
@@ -5,13 +5,20 @@ GLOBAL_LIST_EMPTY(protected_ranks) //admin ranks loaded from txt
GLOBAL_PROTECT(protected_ranks)
/datum/admin_rank
+ /// Rank name, key'd to the db
var/name = "NoRank"
+ /// Rank source, see RANK_SOURCE_TXT and friends
+ var/source = null
+ /// Our functional rights, these are what we actually use in game
var/rights = R_DEFAULT
- var/exclude_rights = NONE
+ /// Rights we're allowed to use, pre filtering
var/include_rights = NONE
+ /// These are the rights of include_rights we aren't allowed to use. Frankly I have no idea why this exists
+ var/exclude_rights = NONE
+ /// Rights we're allowed to edit on other folks, impact of this is dependent on R_PERMISSIONS and R_DBRANKS
var/can_edit_rights = NONE
-/datum/admin_rank/New(init_name, init_rights, init_exclude_rights, init_edit_rights)
+/datum/admin_rank/New(init_name, init_source, init_rights, init_exclude_rights, init_edit_rights)
if(IsAdminAdvancedProcCall())
alert_to_permissions_elevation_attempt(usr)
if (name == "NoRank") //only del if this is a true creation (and not just a New() proc call), other wise trialmins/coders could abuse this to deadmin other admins
@@ -19,6 +26,10 @@ GLOBAL_PROTECT(protected_ranks)
CRASH("Admin proc call creation of admin datum")
return
name = init_name
+ source = init_source
+ if(!source)
+ qdel(src)
+ CRASH("Admin rank created without a source.")
if(!name)
qdel(src)
CRASH("Admin rank created without name.")
@@ -83,6 +94,8 @@ GLOBAL_PROTECT(protected_ranks)
flag = R_EVENT
if("MENTOR")
flag = R_MENTOR
+ if("DBRANKS")
+ flag = R_DBRANKS
if("@")
if(previous_rank)
switch(group_count)
@@ -104,6 +117,19 @@ GLOBAL_PROTECT(protected_ranks)
if(3)
can_edit_rights |= flag
+/datum/admin_rank/proc/pretty_print_source()
+ switch(source)
+ if(RANK_SOURCE_LOCAL)
+ return "Localhost"
+ if(RANK_SOURCE_TXT)
+ return "admin_ranks.txt"
+ if(RANK_SOURCE_DB)
+ return "Database"
+ if(RANK_SOURCE_BACKUP)
+ return "Backup JSON"
+ if(RANK_SOURCE_TEMPORARY)
+ return "Temporary"
+
/// 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)
@@ -117,17 +143,17 @@ GLOBAL_PROTECT(protected_ranks)
var/datum/admin_rank/previous_rank
var/regex/admin_ranks_regex = new(@"^Name\s*=\s*(.+?)\s*\n+Include\s*=\s*([\l @]*?)\s*\n+Exclude\s*=\s*([\l @]*?)\s*\n+Edit\s*=\s*([\l @]*?)\s*\n*$", "gm")
while(admin_ranks_regex.Find(ranks_text))
- var/datum/admin_rank/R = new(admin_ranks_regex.group[1])
- if(!R)
+ var/datum/admin_rank/txt_rank = new(admin_ranks_regex.group[1], RANK_SOURCE_TXT)
+ if(QDELETED(txt_rank))
continue
var/count = 1
for(var/i in admin_ranks_regex.group - admin_ranks_regex.group[1])
if(i)
- R.process_keyword(i, count, previous_rank)
+ txt_rank.process_keyword(i, count, previous_rank)
count++
- GLOB.admin_ranks += R
- GLOB.protected_ranks += R
- previous_rank = R
+ GLOB.admin_ranks += txt_rank
+ GLOB.protected_ranks += txt_rank
+ previous_rank = txt_rank
if(!CONFIG_GET(flag/admin_legacy_system) && !dbfail)
if(CONFIG_GET(flag/load_legacy_ranks_only))
if(!no_update)
@@ -146,14 +172,15 @@ GLOBAL_PROTECT(protected_ranks)
if(R.name == rank_name) //this rank was already loaded from txt override
skip = 1
break
- if(!skip)
- var/rank_flags = text2num(query_load_admin_ranks.item[2])
- var/rank_exclude_flags = text2num(query_load_admin_ranks.item[3])
- var/rank_can_edit_flags = text2num(query_load_admin_ranks.item[4])
- var/datum/admin_rank/R = new(rank_name, rank_flags, rank_exclude_flags, rank_can_edit_flags)
- if(!R)
- continue
- GLOB.admin_ranks += R
+ if(skip)
+ continue
+ var/rank_flags = text2num(query_load_admin_ranks.item[2])
+ var/rank_exclude_flags = text2num(query_load_admin_ranks.item[3])
+ var/rank_can_edit_flags = text2num(query_load_admin_ranks.item[4])
+ var/datum/admin_rank/db_rank = new(rank_name, RANK_SOURCE_DB, rank_flags, rank_exclude_flags, rank_can_edit_flags)
+ if(QDELETED(db_rank))
+ continue
+ GLOB.admin_ranks += db_rank
qdel(query_load_admin_ranks)
//load ranks from backup file
if(dbfail)
@@ -169,10 +196,10 @@ GLOBAL_PROTECT(protected_ranks)
skip = TRUE
if(skip)
continue
- var/datum/admin_rank/R = new("[J]", json["ranks"]["[J]"]["include rights"], json["ranks"]["[J]"]["exclude rights"], json["ranks"]["[J]"]["can edit rights"])
- if(!R)
+ var/datum/admin_rank/json_rank = new("[J]", RANK_SOURCE_BACKUP, json["ranks"]["[J]"]["include rights"], json["ranks"]["[J]"]["exclude rights"], json["ranks"]["[J]"]["can edit rights"])
+ if(QDELETED(json_rank))
continue
- GLOB.admin_ranks += R
+ GLOB.admin_ranks += json_rank
return json
#ifdef TESTING
var/msg = "Permission Sets Built:\n"
diff --git a/code/modules/admin/permissionedit.dm b/code/modules/admin/permissionedit.dm
index e5d3c9a3cc..dd08b3b4cc 100644
--- a/code/modules/admin/permissionedit.dm
+++ b/code/modules/admin/permissionedit.dm
@@ -1,39 +1,279 @@
-ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit admin permissions.", "Admin.Secrets")
- user.holder.edit_admin_permissions()
-/datum/admins/proc/edit_admin_permissions(action, target, operation, page)
+ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit admin permissions.", ADMIN_CATEGORY_SECRETS)
+ 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/list/output = list("\[Permissions\]")
- if(action)
- output += " | \[Log\] | \[Management\]
"
- else
- output += "
\[Log\]
\[Management\]"
- if(action == 1)
- var/logcount = 0
- var/logssperpage = 20
- var/pagecount = 0
- page = text2num(page)
- var/datum/db_query/query_count_admin_logs = SSdbcore.NewQuery(
- "SELECT COUNT(id) FROM [format_table_name("admin_log")] WHERE (:target IS NULL OR adminckey = :target) AND (:operation IS NULL OR operation = :operation)",
- list("target" = target, "operation" = operation)
+ var/new_line = " | "
+ if(action == PERMISSIONS_PAGE_PERMISSIONS)
+ new_line = "
"
+
+ output += {"
+ \
+ [action == PERMISSIONS_PAGE_PERMISSIONS ? "\[Permissions\]": "\[Permissions\]"]
+
+ [new_line]
+ [action == PERMISSIONS_PAGE_RANKS ? "\[Ranks\]": "\[Ranks\]"]
+
+ [new_line]
+ [action == PERMISSIONS_PAGE_LOGGING ? "\[Logging\]": "\[Logging\]"]
+
+ [new_line]
+ [action == PERMISSIONS_PAGE_HOUSEKEEPING ? "\[Housekeeping\]": "\[Housekeeping\]"]
+
+ "}
+
+ if(action != PERMISSIONS_PAGE_PERMISSIONS)
+ output += "
"
+
+ if(action == PERMISSIONS_PAGE_PERMISSIONS)
+ output += {"
+
+
+ Permissions Panel
+
+
+
+
+
+
+ | CKEY \[+\] |
+ RANK |
+ PERMISSIONS |
+
+ "}
+ 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 += {"
+
+ [admin_ckey]
+ [deadmin_link]
+ \[-\]
+ \[SYNC TGDB\]
+ [verify_link]
+ |
+
+ [admin_datum.rank_names()]
+ |
+
+ [rights2text(admin_datum.rank_flags(), " ")]
+ |
+
+ "}
+ output += {"
+
+
+
+ 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("erro_player")] WHERE [format_table_name("erro_player")].ckey = [format_table_name("admin")].ckey), ckey), [format_table_name("admin")].`rank` FROM [format_table_name("admin")]")
+ var/datum/db_query/query_extract_admins = SSdbcore.NewQuery("SELECT IFNULL((SELECT ckey FROM [format_table_name("erro_player")] WHERE [format_table_name("erro_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 += {"
+
+ "}
+ 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())
- logcount = text2num(query_count_admin_logs.item[1])
- qdel(query_count_admin_logs)
- if(logcount > logssperpage)
- output += "
Page: "
- while(logcount > 0)
- output += "|[pagecount == page ? "\[[pagecount]\]" : "\[[pagecount]\]"]"
- logcount -= logssperpage
- pagecount++
+ 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("erro_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("erro_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))
+ */
var/datum/db_query/query_search_admin_logs = SSdbcore.NewQuery({"
SELECT
datetime,
@@ -44,92 +284,124 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm
log
FROM [format_table_name("admin_log")]
LEFT JOIN [format_table_name("erro_player")] ON target = ckey
- WHERE (:target IS NULL OR ckey = :target) AND (:operation IS NULL OR operation = :operation)
+ 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" = target, "operation" = operation, "skip" = logssperpage * page, "take" = logssperpage))
+ "}, 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]
- operation = query_search_admin_logs.item[4]
- target = query_search_admin_logs.item[5]
+ 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]
- output += "[datetime] | Round ID [round_id] | Admin [admin_key] | Operation [operation] on [target]
[log]
"
- qdel(query_search_admin_logs)
- if(action == 2)
- output += "Admin ckeys with invalid ranks
"
- var/datum/db_query/query_check_admin_errors = SSdbcore.NewQuery("SELECT IFNULL((SELECT ckey FROM [format_table_name("erro_player")] WHERE [format_table_name("erro_player")].ckey = [format_table_name("admin")].ckey), ckey), [format_table_name("admin")].`rank` FROM [format_table_name("admin")] LEFT JOIN [format_table_name("admin_ranks")] ON [format_table_name("admin_ranks")].`rank` = [format_table_name("admin")].`rank` WHERE [format_table_name("admin_ranks")].`rank` IS NULL")
- if(!query_check_admin_errors.warn_execute())
- qdel(query_check_admin_errors)
- return
- while(query_check_admin_errors.NextRow())
- var/admin_key = query_check_admin_errors.item[1]
- var/admin_rank = query_check_admin_errors.item[2]
- output += "[admin_key] has non-existent rank [admin_rank] | \[Change Rank\] | \[Remove\]"
- output += "
"
- qdel(query_check_admin_errors)
- output += "Unused ranks
"
- var/datum/db_query/query_check_unused_rank = SSdbcore.NewQuery("SELECT [format_table_name("admin_ranks")].`rank`, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] LEFT JOIN [format_table_name("admin")] ON [format_table_name("admin")].`rank` = [format_table_name("admin_ranks")].`rank` WHERE [format_table_name("admin")].`rank` IS NULL")
- if(!query_check_unused_rank.warn_execute())
- qdel(query_check_unused_rank)
- return
- while(query_check_unused_rank.NextRow())
- var/admin_rank = query_check_unused_rank.item[1]
- output += {"Rank [admin_rank] is not held by any admin | \[Remove\]
-
Permissions: [rights2text(text2num(query_check_unused_rank.item[2])," ")]
-
Denied: [rights2text(text2num(query_check_unused_rank.item[3])," ", "-")]
-
Allowed to edit: [rights2text(text2num(query_check_unused_rank.item[4])," ", "*")]
-
"}
- qdel(query_check_unused_rank)
- else if(!action)
+ 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
-
-
-
-
-
- | CKEY \[+\] |
- RANK |
- PERMISSIONS |
-
+
+
+ Permissions Panel
+
"}
- for(var/adm_ckey in GLOB.admin_datums+GLOB.deadmins)
- var/datum/admins/D = GLOB.admin_datums[adm_ckey]
- if(!D)
- D = GLOB.deadmins[adm_ckey]
- if (!D)
- continue
- var/deadminlink = ""
- if(D.owner)
- adm_ckey = D.owner.key
- if (D.deadmined)
- deadminlink = " \[RA\]"
- else
- deadminlink = " \[DA\]"
- var/verify_link = ""
- //if (D.blocked_by_2fa)
- // verify_link += " | \[2FA VERIFY\]"
+ // 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("erro_player")] WHERE [format_table_name("erro_player")].ckey = [format_table_name("admin")].ckey), ckey), [format_table_name("admin")].`rank` FROM [format_table_name("admin")]")
+ var/datum/db_query/query_extract_admins = SSdbcore.NewQuery("SELECT IFNULL((SELECT ckey FROM [format_table_name("erro_player")] WHERE [format_table_name("erro_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]
+
+ "}
- output += ""
- output += "[adm_ckey] [deadminlink]\[-\]\[SYNC TGDB\][verify_link] | "
- output += "[D.rank_names()] | "
- output += "[rights2text(D.rank_flags(), " ")] | "
- output += "
"
- output += "
Search:
"
if(QDELETED(usr))
return
-
- var/datum/browser/popup = new(owner, "editrights", "Edit Rights", 1000, 650)
- popup.set_content(jointext(output, ""))
- popup.open()
+ usr << browse("[jointext(output, "")]","window=editrights;size=1000x650")
/datum/admins/proc/edit_rights_topic(list/href_list)
if(!check_rights(R_PERMISSIONS))
@@ -153,7 +425,7 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm
var/use_db
var/skip
var/legacy_only
- if(task == "activate" || task == "deactivate" || task == "sync" || task == "verify")
+ 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)
@@ -166,14 +438,14 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm
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))
- if(!skip)
+
+ 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(use_db == "Cancel")
+ if(isnull(use_db) || use_db == "Cancel")
return
if(use_db == "Permanent")
use_db = TRUE
@@ -208,118 +480,156 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm
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()
+ //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 = tgui_input_text(usr, "New admin's key","Admin key")
+ 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)
- //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 Management tab if they don't appear in the list of admins."), 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)
- 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
+ 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)
- 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), 'add admin', :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 FALSE
+ 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/D)
- if(tgui_alert(usr,"Are you sure you want to remove [admin_ckey]?","Confirm Removal",list("Do it","Cancel")) == "Do it")
- GLOB.admin_datums -= admin_ckey
- GLOB.deadmins -= admin_ckey
- if(D)
- D.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)
- var/datum/db_query/query_add_rank = SSdbcore.NewQuery(
- "DELETE FROM [format_table_name("admin")] WHERE ckey = :ckey",
- list("ckey" = admin_ckey)
- )
- if(!query_add_rank.warn_execute())
- qdel(query_add_rank)
- return
- qdel(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), 'remove admin', :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_add_rank_log.warn_execute())
- qdel(query_add_rank_log)
- return
- qdel(query_add_rank_log)
- sync_lastadminrank(admin_ckey, admin_key)
+/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)
-
-/datum/admins/proc/force_readmin(admin_key, datum/admins/D)
- if(!D || !D.deadmined)
return
- D.activate()
+ 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/D)
- if(!D || D.deadmined)
+/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]")
- D.deactivate() //after logs so the deadmined admin can see the message.
+ 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/observer/dead))
+ // 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/D, legacy_only)
- if(!check_rights(R_PERMISSIONS))
+/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)
- rank_names[admin_rank.name] = admin_rank
+ 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)
@@ -333,7 +643,7 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm
if (!(rank_name in display_rank_names))
display_rank_names += rank_name
- var/next_rank = tgui_input_list(usr, "Please select a rank, or select [RANK_DONE] if you are finished.", "Pick Rank", display_rank_names)
+ 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
@@ -350,20 +660,24 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm
continue
if (next_rank == "*New Rank*")
- var/new_rank_name = tgui_input_text(usr, "Please input a new rank", "New custom 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))
- if (D)
- custom_rank = new(new_rank_name, D.rank_flags())
- else
- custom_rank = new(new_rank_name)
+ if(!isnull(custom_rank))
+ continue
- GLOB.admin_ranks += custom_rank
- custom_ranks += custom_rank
- new_rank_names += new_rank_name
+ 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
@@ -394,7 +708,7 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm
old_rank = "NEW ADMIN"
else
old_rank = query_admin_in_db.item[1]
- qdel(query_admin_in_db)
+ 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
@@ -405,25 +719,26 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm
if(!query_rank_in_db.warn_execute())
qdel(query_rank_in_db)
return
- if(!query_rank_in_db.NextRow())
+ if(query_rank_in_db.NextRow())
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
+ 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)
- 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), 'add rank', :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
+ 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)
- qdel(query_rank_in_db)
+ 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)
@@ -431,27 +746,30 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm
if(!query_change_rank.warn_execute())
qdel(query_change_rank)
return
- qdel(query_change_rank)
+ 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), 'change admin rank', :target, CONCAT('Rank of ', :target, ' changed from ', :old_rank, ' to ', :new_rank))
+ 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(query_change_rank_log)
- if(D) //they were previously an admin
- D.disassociate() //existing admin needs to be disassociated
- D.ranks = new_ranks //set the admin_rank as our rank
- //D.bypass_2fa = TRUE // Another admin has cleared us
- var/client/C = GLOB.directory[admin_ckey]
- D.associate(C)
+ QDEL_NULL(query_change_rank_log)
else
- D = new(new_ranks, admin_ckey) //new admin
- //D.bypass_2fa = TRUE // Another admin has cleared us
- D.activate()
- message_admins(m1)
- log_admin(m2)
+ 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
@@ -464,7 +782,7 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm
return
var/new_flags = input_bitfield(
usr,
- "Admin rights
This will affect only the current admin [admin_key]",
+ "Admin rights
This will affect only the current admin ([admin_ckey]), temporarily",
"admin_flags",
admin_holder.rank_flags(),
350,
@@ -486,6 +804,7 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm
// 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.
@@ -503,57 +822,406 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm
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))
+ 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
- if(!CONFIG_GET(flag/admin_legacy_system) && CONFIG_GET(flag/protect_legacy_ranks) && (admin_rank in GLOB.protected_ranks))
- to_chat(usr, span_adminprefix("Deletion of protected ranks is not permitted, it must be removed from admin_ranks.txt."), confidential = TRUE)
+
+ var/list/datum/admin_rank/target_ranks = ranks_from_rank_name(admin_rank)
+ if (!target_ranks || length(target_ranks) > 1)
return
- if(CONFIG_GET(flag/load_legacy_ranks_only))
- to_chat(usr, span_adminprefix("Rank deletion not permitted while database rank loading is disabled."), confidential = TRUE)
- return
- 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 rank still used; Tell a coder, this shouldn't happen."), confidential = TRUE)
- return
- qdel(query_admins_with_rank)
- if(tgui_alert(usr,"Are you sure you want to remove [admin_rank]?","Confirm Removal",list("Do it","Cancel")) == "Do it")
- var/m1 = "[key_name_admin(usr)] removed rank [admin_rank] permanently"
- var/m2 = "[key_name(usr)] removed rank [admin_rank] permanently"
- var/datum/db_query/query_add_rank = SSdbcore.NewQuery(
- "DELETE FROM [format_table_name("admin_ranks")] WHERE `rank` = :admin_rank",
+ 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_add_rank.warn_execute())
- qdel(query_add_rank)
+ if(!query_admins_with_rank.warn_execute())
+ qdel(query_admins_with_rank)
return
- qdel(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), 'remove rank', :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_add_rank_log.warn_execute())
- qdel(query_add_rank_log)
+ 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(query_add_rank_log)
+ 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)
-/datum/admins/proc/sync_lastadminrank(admin_ckey, admin_key, datum/admins/D)
+/// 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 (D)
- sqlrank = D.rank_names()
+ if (target_holder)
+ sqlrank = target_holder.rank_names()
var/datum/db_query/query_sync_lastadminrank = SSdbcore.NewQuery(
"UPDATE [format_table_name("erro_player")] SET lastadminrank = :rank WHERE ckey = :ckey",
list("rank" = sqlrank, "ckey" = admin_ckey)
@@ -561,5 +1229,7 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm
if(!query_sync_lastadminrank.warn_execute())
qdel(query_sync_lastadminrank)
return
- qdel(query_sync_lastadminrank)
+ QDEL_NULL(query_sync_lastadminrank)
to_chat(usr, span_admin("Sync of [admin_key] successful."), confidential = TRUE)
+
+#undef PERMISSIONS_LOGS_PER_PAGE
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index a95c4c06c3..7b832cd4de 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -48,19 +48,28 @@
// mentor_commands(href, href_list, src) - Skip because client is already admin & contents handled above
else if(href_list["editrightsbrowser"])
- edit_admin_permissions(0)
+ edit_admin_permissions(PERMISSIONS_PAGE_PERMISSIONS)
- else if(href_list["editrightsbrowserlog"])
- edit_admin_permissions(1, href_list["editrightstarget"], href_list["editrightsoperation"], href_list["editrightspage"])
+ else if(href_list["editrightsbrowserranks"])
+ if(href_list["editrightsaddrank"])
+ add_rank()
+ else if(href_list["editrightsremoverank"])
+ remove_rank(href_list["editrightsremoverank"])
+ else if(href_list["editrightseditrank"])
+ change_rank(href_list["editrightseditrank"])
+ edit_admin_permissions(PERMISSIONS_PAGE_RANKS)
- if(href_list["editrightsbrowsermanage"])
+ else if(href_list["editrightsbrowserlogging"])
+ edit_admin_permissions(PERMISSIONS_PAGE_LOGGING, href_list["editrightslogtarget"], href_list["editrightslogactor"], href_list["editrightslogoperation"], href_list["editrightslogpage"])
+
+ if(href_list["editrightsbrowserhousekeep"])
if(href_list["editrightschange"])
change_admin_rank(ckey(href_list["editrightschange"]), href_list["editrightschange"], TRUE)
else if(href_list["editrightsremove"])
remove_admin(ckey(href_list["editrightsremove"]), href_list["editrightsremove"], TRUE)
else if(href_list["editrightsremoverank"])
remove_rank(href_list["editrightsremoverank"])
- edit_admin_permissions(2)
+ edit_admin_permissions(PERMISSIONS_PAGE_HOUSEKEEPING)
else if(href_list["editrights"])
edit_rights_topic(href_list)
diff --git a/code/modules/admin/verbs/secrets.dm b/code/modules/admin/verbs/secrets.dm
index 94888f4698..f44bcfd779 100644
--- a/code/modules/admin/verbs/secrets.dm
+++ b/code/modules/admin/verbs/secrets.dm
@@ -1,4 +1,4 @@
-ADMIN_VERB(secrets, R_HOLDER, "Secrets", "Abuse harder than you ever have before with this handy dandy semi-misc stuff menu.", "Admin.Secrets")
+ADMIN_VERB(secrets, R_HOLDER, "Secrets", "Abuse harder than you ever have before with this handy dandy semi-misc stuff menu.", ADMIN_CATEGORY_SECRETS)
var/datum/secrets_menu/tgui = new(user)
tgui.tgui_interact(user.mob)
//BLACKBOX_LOG_ADMIN_VERB("Secrets Panel")
diff --git a/config/example/admin_ranks.txt b/config/example/admin_ranks.txt
index abc3d84f66..43835a9e71 100644
--- a/config/example/admin_ranks.txt
+++ b/config/example/admin_ranks.txt
@@ -30,6 +30,7 @@
#SOUND = allows you to upload and play SOUND.
#SPAWN = mob transformations, spawning of most atoms including mobs (high-risk atoms, e.g. blackholes, will require the +FUN flag too).
#EVENT = a group of verbs that make it possible to run an event, or other badminnery.
+#DBRANKS = when sql-based admin loading is enabled, allows for non-temporary changes in the permissions panel to be saved (requires DB).
#EVERYTHING = Simply gives you everything without having to type every flag.
#@ = special keyword for the current permission type that adds all the keywords that the preceding rank has of the same type.
@@ -87,3 +88,8 @@ Name = Developer
Include = DEBUG VAREDIT SERVER SPAWN REJUVINATE POSSESS BUILD
Exclude =
Edit =
+
+Name = Mentor
+Include = MENTOR
+Exclude =
+Edit =