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 + + + +
+ + + + + + + "} + 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 \[+\]RANKPERMISSIONS
[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("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 += {" +
+ [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()) - 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 - - - -
- - - - - + + + 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 += "" - output += "" - output += "" - output += "" - output += "
CKEY \[+\]RANKPERMISSIONS
[adm_ckey]
[deadminlink]\[-\]\[SYNC TGDB\][verify_link]
[D.rank_names()][rights2text(D.rank_flags(), " ")]
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 =