diff --git a/SQL/feedback_schema.sql b/SQL/feedback_schema.sql index 2664fcd1dd..09657498b9 100644 --- a/SQL/feedback_schema.sql +++ b/SQL/feedback_schema.sql @@ -1,22 +1,29 @@ --- Table structure for table `erro_admin` -CREATE TABLE `erro_admin` ( - `id` int(11) NOT NULL AUTO_INCREMENT, +CREATE TABLE `admin` ( `ckey` varchar(32) NOT NULL, - `rank` varchar(32) NOT NULL DEFAULT 'Administrator', - `level` int(2) NOT NULL DEFAULT '0', - `flags` int(16) NOT NULL DEFAULT '0', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1 ; + `rank` varchar(32) NOT NULL, + `feedback` varchar(255) DEFAULT NULL, + PRIMARY KEY (`ckey`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; --- Table structure for table `erro_admin_log` -CREATE TABLE `erro_admin_log` ( +CREATE TABLE `admin_log` ( `id` int(11) NOT NULL AUTO_INCREMENT, `datetime` datetime NOT NULL, + `round_id` int(11) unsigned NULL, `adminckey` varchar(32) NOT NULL, - `adminip` varchar(18) NOT NULL, - `log` text NOT NULL, + `adminip` int(10) unsigned NOT NULL, + `operation` enum('add admin','remove admin','change admin rank','add rank','remove rank','change rank flags') NOT NULL, + `target` varchar(32) NOT NULL, + `log` varchar(1000) NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1 ; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +CREATE TABLE `admin_ranks` ( + `rank` varchar(32) NOT NULL, + `flags` mediumint(5) unsigned NOT NULL, + `exclude_flags` mediumint(5) unsigned NOT NULL, + `can_edit_flags` mediumint(5) unsigned NOT NULL, + PRIMARY KEY (`rank`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- Table structure for table `erro_ban` CREATE TABLE `erro_ban` ( diff --git a/SQL/starter_admin_ranks.sql b/SQL/starter_admin_ranks.sql new file mode 100644 index 0000000000..b6c390fa9f --- /dev/null +++ b/SQL/starter_admin_ranks.sql @@ -0,0 +1,15 @@ +INSERT INTO admin_ranks + (rank, flags, exclude_flags, can_edit_flags) +VALUES + ('Host', 65535, 0, 65535), + ('Head Admin', 65535, 0, 65535), + ('Game Master', 65535, 0, 65535), + ('Moderator', 8192, 0, 0), + ('Game Admin', 8191, 0, 0), + ('Dev Mod', 13937, 0, 0), + ('Developer', 5745, 0, 0), + ('Badmin', 5727, 0, 0), + ('Trial Admin', 5638, 0, 0), + ('Admin Candidate', 2, 0, 0), + ('Retired Admin', 258, 0, 0), + ('Admin Observer', 0, 0, 0); diff --git a/code/__defines/_bitfields.dm b/code/__defines/_bitfields.dm new file mode 100644 index 0000000000..19a962fbff --- /dev/null +++ b/code/__defines/_bitfields.dm @@ -0,0 +1,4 @@ +#define DEFINE_BITFIELD(_variable, _flags) /datum/bitfield/##_variable { \ + flags = ##_flags; \ + variable = #_variable; \ +} diff --git a/code/__defines/admin.dm b/code/__defines/admin.dm index e4934f522b..8f0189008f 100644 --- a/code/__defines/admin.dm +++ b/code/__defines/admin.dm @@ -22,25 +22,29 @@ #define ROUNDSTART_LOGOUT_REPORT_TIME 6000 // Amount of time (in deciseconds) after the rounds starts, that the player disconnect report is issued. -// Admin permissions. -#define R_BUILDMODE 0x1 -#define R_ADMIN 0x2 -#define R_BAN 0x4 -#define R_FUN 0x8 -#define R_SERVER 0x10 -#define R_DEBUG 0x20 -#define R_POSSESS 0x40 -#define R_PERMISSIONS 0x80 -#define R_STEALTH 0x100 -#define R_REJUVINATE 0x200 -#define R_VAREDIT 0x400 -#define R_SOUNDS 0x800 -#define R_SPAWN 0x1000 -#define R_MOD 0x2000 -#define R_EVENT 0x4000 -#define R_HOST 0x8000 //higher than this will overflow +//Admin Permissions +/// Used for signifying that all admins can use this regardless of actual permissions +#define R_NONE NONE +#define R_BUILDMODE (1<<0) +#define R_ADMIN (1<<1) +#define R_BAN (1<<2) +#define R_FUN (1<<3) +#define R_SERVER (1<<4) +#define R_DEBUG (1<<5) +#define R_POSSESS (1<<6) +#define R_PERMISSIONS (1<<7) +#define R_STEALTH (1<<8) +#define R_REJUVINATE (1<<9) +#define R_VAREDIT (1<<10) +#define R_SOUNDS (1<<11) +#define R_SPAWN (1<<12) +#define R_MOD (1<<13) +#define R_EVENT (1<<14) +#define R_HOST (1<<15) //higher than this will overflow -#define R_MAXPERMISSION 0x8000 // This holds the maximum value for a permission. It is used in iteration, so keep it updated. +#define R_DEFAULT R_NONE + +#define R_EVERYTHING (1<<16)-1 //the sum of all other rank permissions, used for +EVERYTHING #define SMITE_BREAKLEGS "Break Legs" #define SMITE_BLUESPACEARTILLERY "Bluespace Artillery" @@ -72,3 +76,6 @@ #define AHELP_ACTIVE 1 #define AHELP_CLOSED 2 #define AHELP_RESOLVED 3 + +/// A value for /datum/admins/cached_feedback_link to indicate empty, rather than unobtained +#define NO_FEEDBACK_LINK "no_feedback_link" diff --git a/code/__defines/admin_verb.dm b/code/__defines/admin_verb.dm new file mode 100644 index 0000000000..ae8c75b058 --- /dev/null +++ b/code/__defines/admin_verb.dm @@ -0,0 +1,94 @@ +/client/CanProcCall(procname) + if(findtext(procname, "__avd_") == 1) + message_admins("[key_name_admin(usr)] attempted to directly call admin verb '[procname]'.") + log_admin("[key_name(usr)] attempted to directly call admin verb '[procname]'.") + return FALSE + return ..() + +/** + * This is the only macro you should use to define admin verbs. + * It will define the verb and the verb holder for you. + * Using it is very simple: + * ADMIN_VERB(verb_path, R_PERM, "Name", "Description", "Admin.Category", args...) + * This sets up all of the above and also acts as syntatic sugar as a verb delcaration for the verb itself. + * Note that the verb args have an injected `client/user` argument that is the user that called the verb. + * Do not use usr in your verb; technically you can but I'll kill you. + */ +#define _ADMIN_VERB(verb_path_name, verb_permissions, verb_name, verb_desc, verb_category, show_in_context_menu, verb_args...) \ +/datum/admin_verb/##verb_path_name \ +{ \ + name = ##verb_name; \ + description = ##verb_desc; \ + category = ##verb_category; \ + permissions = ##verb_permissions; \ + verb_path = /client/proc/__avd_##verb_path_name; \ +}; \ +/client/proc/__avd_##verb_path_name(##verb_args) \ +{ \ + set name = ##verb_name; \ + set desc = ##verb_desc; \ + set hidden = FALSE; /* this is explicitly needed as the proc begins with an underscore */ \ + set popup_menu = ##show_in_context_menu; \ + set category = ##verb_category; \ + var/list/_verb_args = list(usr, /datum/admin_verb/##verb_path_name); \ + _verb_args += args; \ + SSadmin_verbs.dynamic_invoke_verb(arglist(_verb_args)); \ +}; \ +/datum/admin_verb/##verb_path_name/__avd_do_verb(client/user, ##verb_args) + +#define ADMIN_VERB(verb_path_name, verb_permissions, verb_name, verb_desc, verb_category, verb_args...) \ +_ADMIN_VERB(verb_path_name, verb_permissions, verb_name, verb_desc, verb_category, FALSE, ##verb_args) + +#define ADMIN_VERB_ONLY_CONTEXT_MENU(verb_path_name, verb_permissions, verb_name, verb_args...) \ +_ADMIN_VERB(verb_path_name, verb_permissions, verb_name, ADMIN_VERB_NO_DESCRIPTION, ADMIN_CATEGORY_HIDDEN, TRUE, ##verb_args) + +#define ADMIN_VERB_AND_CONTEXT_MENU(verb_path_name, verb_permissions, verb_name, verb_desc, verb_category, verb_args...) \ +_ADMIN_VERB(verb_path_name, verb_permissions, verb_name, verb_desc, verb_category, TRUE, ##verb_args) + +/// Used to define a special check to determine if the admin verb should exist at all. Useful for verbs such as play sound which require configuration. +#define ADMIN_VERB_CUSTOM_EXIST_CHECK(verb_path_name) \ +/datum/admin_verb/##verb_path_name/__avd_check_should_exist() + +/// Used to define the visibility flag of the verb. If the admin does not have this flag enabled they will not see the verb. +#define ADMIN_VERB_VISIBILITY(verb_path_name, verb_visibility) /datum/admin_verb/##verb_path_name/visibility_flag = ##verb_visibility + +// These are put here to prevent the "procedure override precedes definition" error. +/datum/admin_verb/proc/__avd_get_verb_path() + CRASH("__avd_get_verb_path not defined. use the macro") +/datum/admin_verb/proc/__avd_do_verb(...) + CRASH("__avd_do_verb not defined. use the macro") +/datum/admin_verb/proc/__avd_check_should_exist() + return TRUE + +/* + * This is an example of how to use the above macro: + * ``` + * ADMIN_VERB(name_of_verb, R_ADMIN, "Verb Name", "Verb Desc", "Verb Category", mob/target in world) + * to_chat(user, "Hello!") + * ``` + * Note the implied `client/user` argument that is injected into the verb. + * Also note that byond is shit and you cannot multi-line the macro call. + */ + +/// Use this to mark your verb as not having a description. Should ONLY be used if you are also hiding the verb! +#define ADMIN_VERB_NO_DESCRIPTION "" +/// Used to verbs you do not want to show up in the master verb panel. +#define ADMIN_CATEGORY_HIDDEN null + +// Admin verb categories +#define ADMIN_CATEGORY_MAIN "Admin" +#define ADMIN_CATEGORY_EVENTS "Admin.Events" +#define ADMIN_CATEGORY_FUN "Admin.Fun" +#define ADMIN_CATEGORY_GAME "Admin.Game" +#define ADMIN_CATEGORY_SHUTTLE "Admin.Shuttle" + +// Special categories that are separated +#define ADMIN_CATEGORY_DEBUG "Debug" +#define ADMIN_CATEGORY_SERVER "Server" +#define ADMIN_CATEGORY_OBJECT "Object" +#define ADMIN_CATEGORY_MAPPING "Mapping" +#define ADMIN_CATEGORY_PROFILE "Profile" +#define ADMIN_CATEGORY_IPINTEL "Admin.IPIntel" + +// Visibility flags +#define ADMIN_VERB_VISIBLITY_FLAG_MAPPING_DEBUG "Map-Debug" diff --git a/code/__defines/dcs/signals/signals_client.dm b/code/__defines/dcs/signals/signals_client.dm new file mode 100644 index 0000000000..1fe8737c5d --- /dev/null +++ b/code/__defines/dcs/signals/signals_client.dm @@ -0,0 +1,2 @@ +/// Called after a client logs into a mob: (mob) +#define COMSIG_CLIENT_MOB_LOGIN "client_mob_changed" diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm index 5d92678e30..6e3cfba2fd 100644 --- a/code/__defines/subsystems.dm +++ b/code/__defines/subsystems.dm @@ -118,6 +118,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G // The numbers just define the ordering, they are meaningless otherwise. #define INIT_ORDER_TITLE 99 //CHOMPEdit #define INIT_ORDER_SERVER_MAINT 93 +#define INIT_ORDER_ADMIN_VERBS 84 // needs to be pretty high, admins can't do much without it #define INIT_ORDER_WEBHOOKS 50 #define INIT_ORDER_SQLITE 41 #define INIT_ORDER_GARBAGE 40 diff --git a/code/_global_vars/bitfields.dm b/code/_global_vars/bitfields.dm index 90f98b6fa1..a8a9be511b 100644 --- a/code/_global_vars/bitfields.dm +++ b/code/_global_vars/bitfields.dm @@ -1,41 +1,77 @@ -GLOBAL_LIST_INIT(bitfields, list( - "datum_flags" = list( - "DF_VAR_EDITED" = DF_VAR_EDITED, - "DF_ISPROCESSING" = DF_ISPROCESSING - ), - "appearance_flags" = list( - "KEEP_APART" = KEEP_APART, - "KEEP_TOGETHER" = KEEP_TOGETHER, - "LONG_GLIDE" = LONG_GLIDE, - "NO_CLIENT_COLOR" = NO_CLIENT_COLOR, - "PIXEL_SCALE" = PIXEL_SCALE, - "PLANE_MASTER" = PLANE_MASTER, - "RESET_ALPHA" = RESET_ALPHA, - "RESET_COLOR" = RESET_COLOR, - "RESET_TRANSFORM" = RESET_TRANSFORM, - "TILE_BOUND" = TILE_BOUND, - "PASS_MOUSE" = PASS_MOUSE, - "TILE_MOVER" = TILE_MOVER - ), - "vis_flags" = list( - "VIS_HIDE" = VIS_HIDE, - "VIS_INHERIT_DIR" = VIS_INHERIT_DIR, - "VIS_INHERIT_ICON" = VIS_INHERIT_ICON, - "VIS_INHERIT_ICON_STATE" = VIS_INHERIT_ICON_STATE, - "VIS_INHERIT_ID" = VIS_INHERIT_ID, - "VIS_INHERIT_LAYER" = VIS_INHERIT_LAYER, - "VIS_INHERIT_PLANE" = VIS_INHERIT_PLANE, - "VIS_UNDERLAY" = VIS_UNDERLAY, - ), - "sight" = list( - "BLIND" = BLIND, - "SEE_BLACKNESS" = SEE_BLACKNESS, - "SEE_INFRA" = SEE_INFRA, - "SEE_MOBS" = SEE_MOBS, - "SEE_OBJS" = SEE_OBJS, - "SEE_PIXELS" = SEE_PIXELS, - "SEE_SELF" = SEE_SELF, - "SEE_THRU" = SEE_THRU, - "SEE_TURFS" = SEE_TURFS, - ), +GLOBAL_LIST_INIT(bitfields, generate_bitfields()) + +/// Specifies a bitfield for smarter debugging +/datum/bitfield + /// The variable name that contains the bitfield + var/variable + + /// An associative list of the readable flag and its true value + var/list/flags + +/// Turns /datum/bitfield subtypes into a list for use in debugging +/proc/generate_bitfields() + var/list/bitfields = list() + for (var/_bitfield in subtypesof(/datum/bitfield)) + var/datum/bitfield/bitfield = new _bitfield + bitfields[bitfield.variable] = bitfield.flags + return bitfields + +DEFINE_BITFIELD(admin_flags, list( + "ADMIN" = R_ADMIN, + "REJUVINATE" = R_REJUVINATE, + "BAN" = R_BAN, + "BUILDMODE" = R_BUILDMODE, + "DEBUG" = R_DEBUG, + "FUN" = R_FUN, + "PERMISSIONS" = R_PERMISSIONS, + "MOD" = R_MOD, + "POSSESS" = R_POSSESS, + "SERVER" = R_SERVER, + "SOUNDS" = R_SOUNDS, + "SPAWN" = R_SPAWN, + "STEALTH" = R_STEALTH, + "VAREDIT" = R_VAREDIT, +)) + +DEFINE_BITFIELD(datum_flags, list( + "DF_VAR_EDITED" = DF_VAR_EDITED, + "DF_ISPROCESSING" = DF_ISPROCESSING +)) + +DEFINE_BITFIELD(appearance_flags, list( + "KEEP_APART" = KEEP_APART, + "KEEP_TOGETHER" = KEEP_TOGETHER, + "LONG_GLIDE" = LONG_GLIDE, + "NO_CLIENT_COLOR" = NO_CLIENT_COLOR, + "PIXEL_SCALE" = PIXEL_SCALE, + "PLANE_MASTER" = PLANE_MASTER, + "RESET_ALPHA" = RESET_ALPHA, + "RESET_COLOR" = RESET_COLOR, + "RESET_TRANSFORM" = RESET_TRANSFORM, + "TILE_BOUND" = TILE_BOUND, + "PASS_MOUSE" = PASS_MOUSE, + "TILE_MOVER" = TILE_MOVER +)) + +DEFINE_BITFIELD(vis_flags, list( + "VIS_HIDE" = VIS_HIDE, + "VIS_INHERIT_DIR" = VIS_INHERIT_DIR, + "VIS_INHERIT_ICON" = VIS_INHERIT_ICON, + "VIS_INHERIT_ICON_STATE" = VIS_INHERIT_ICON_STATE, + "VIS_INHERIT_ID" = VIS_INHERIT_ID, + "VIS_INHERIT_LAYER" = VIS_INHERIT_LAYER, + "VIS_INHERIT_PLANE" = VIS_INHERIT_PLANE, + "VIS_UNDERLAY" = VIS_UNDERLAY, +)) + +DEFINE_BITFIELD(sight, list( + "BLIND" = BLIND, + "SEE_BLACKNESS" = SEE_BLACKNESS, + "SEE_INFRA" = SEE_INFRA, + "SEE_MOBS" = SEE_MOBS, + "SEE_OBJS" = SEE_OBJS, + "SEE_PIXELS" = SEE_PIXELS, + "SEE_SELF" = SEE_SELF, + "SEE_THRU" = SEE_THRU, + "SEE_TURFS" = SEE_TURFS, )) diff --git a/code/_helpers/admin.dm b/code/_helpers/admin.dm new file mode 100644 index 0000000000..87ca2a8759 --- /dev/null +++ b/code/_helpers/admin.dm @@ -0,0 +1,9 @@ +/// Returns if the given client is an admin, REGARDLESS of if they're deadminned or not. +///proc/is_admin(client/client) +// return !isnull(GLOB.admin_datums[client.ckey]) || !isnull(GLOB.deadmins[client.ckey]) + +/// Sends a message in the event that someone attempts to elevate their permissions through invoking a certain proc. +/proc/alert_to_permissions_elevation_attempt(mob/user) + var/message = " has tried to elevate permissions!" + message_admins(key_name_admin(user) + message) + log_admin(key_name(user) + message) diff --git a/code/_helpers/logging.dm b/code/_helpers/logging.dm index 93862ae051..7595e1f1ae 100644 --- a/code/_helpers/logging.dm +++ b/code/_helpers/logging.dm @@ -352,7 +352,7 @@ . += "" if(C && C.holder && C.holder.fakekey) - . += C.holder.rank // CHOMPEdit: Stealth mode displays staff rank in PM Messages + . += C.holder.rank_names() // CHOMPEdit: Stealth mode displays staff rank in PM Messages else . += key diff --git a/code/_helpers/text.dm b/code/_helpers/text.dm index 88163e5561..954cff017b 100644 --- a/code/_helpers/text.dm +++ b/code/_helpers/text.dm @@ -631,3 +631,14 @@ GLOBAL_LIST_EMPTY(text_tag_cache) /proc/sanitize_css_class_name(name) var/static/regex/regex = new(@"[^a-zA-Z0-9]","g") return replacetext(name, regex, "") + +//finds the first occurrence of one of the characters from needles argument inside haystack +//it may appear this can be optimised, but it really can't. findtext() is so much faster than anything you can do in byondcode. +//stupid byond :( +/proc/findchar(haystack, needles, start=1, end=0) + var/temp + var/len = length(needles) + for(var/i=1, i<=len, i++) + temp = findtextEx(haystack, ascii2text(text2ascii(needles,i)), start, end) //Note: ascii2text(text2ascii) is faster than copytext() + if(temp) end = temp + return end diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index a8ad9fd256..90338caae6 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -438,8 +438,17 @@ /datum/config_entry/flag/no_click_cooldown -/// Defines whether the server uses the legacy admin system with admins.txt or the SQL system. Config option in config.txt -/datum/config_entry/flag/admin_legacy_system +/datum/config_entry/flag/admin_legacy_system //Defines whether the server uses the legacy admin system with admins.txt or the SQL system + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/protect_legacy_admins //Stops any admins loaded by the legacy system from having their rank edited by the permissions panel + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/protect_legacy_ranks //Stops any ranks loaded by the legacy system from having their flags edited by the permissions panel + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/flag/load_legacy_ranks_only //Loads admin ranks only from legacy admin_ranks.txt, while enabled ranks are mirrored to the database + protection = CONFIG_ENTRY_LOCKED /// Defines whether the server uses the legacy banning system with the files in /data or the SQL system. Config option in config.txt /datum/config_entry/flag/ban_legacy_system @@ -721,3 +730,5 @@ /// The endpoint for the chat to fetch the chatlogs from (for example, the last 2500 messages on init for the history) /// REQUIRES chatlog_database_backend to be enabled /datum/config_entry/string/chatlog_database_api_endpoint + +/datum/config_entry/flag/forbid_admin_profiling diff --git a/code/controllers/subsystems/persistence.dm b/code/controllers/subsystems/persistence.dm index 69a92f14e2..4d4bdc4f01 100644 --- a/code/controllers/subsystems/persistence.dm +++ b/code/controllers/subsystems/persistence.dm @@ -53,7 +53,7 @@ SUBSYSTEM_DEF(persistence) return var/list/dat = list("") - var/can_modify = check_rights(R_ADMIN, 0, user) + var/can_modify = check_rights_for(user.client, (R_ADMIN|R_DEBUG)) for(var/thing in persistence_datums) var/datum/persistent/P = persistence_datums[thing] if(P.has_admin_data) diff --git a/code/controllers/subsystems/statpanel.dm b/code/controllers/subsystems/statpanel.dm index 413c38d343..c1c5edc83d 100644 --- a/code/controllers/subsystems/statpanel.dm +++ b/code/controllers/subsystems/statpanel.dm @@ -175,7 +175,9 @@ SUBSYSTEM_DEF(statpanels) /datum/controller/subsystem/statpanels/proc/set_tickets_tab(client/target) /* CHOMPRemove Start, our tickets are handled differently var/list/tickets = list() - if(check_rights(R_ADMIN|R_SERVER|R_MOD,FALSE,target)) //Prevents non-staff from opening the list of ahelp tickets + if(check_rights_for(target, R_ADMIN|R_SERVER|R_MOD)) //Prevents non-staff from opening the list of ahelp tickets + tickets += GLOB.ahelp_tickets.stat_entry(target) + tickets += GLOB.mhelp_tickets.stat_entry(target) */// CHOMPRemove End var/list/tickets = GLOB.tickets.stat_entry(target) // CHOMPEdit target.stat_panel.send_message("update_tickets", tickets) diff --git a/code/datums/browser.dm b/code/datums/browser.dm index 3b297a3152..7f68c185c6 100644 --- a/code/datums/browser.dm +++ b/code/datums/browser.dm @@ -87,7 +87,8 @@ - + + [head_content] diff --git a/code/datums/diseases/_MobProcs.dm b/code/datums/diseases/_MobProcs.dm index 37677ddb7d..4827f2a5be 100644 --- a/code/datums/diseases/_MobProcs.dm +++ b/code/datums/diseases/_MobProcs.dm @@ -169,7 +169,7 @@ set name = "Release Virus" set desc = "Release a pre-set virus." - if(!is_admin()) + if(!check_rights(R_FUN|R_EVENT)) return FALSE var/disease = tgui_input_list(usr, "Choose virus", "Viruses", subtypesof(/datum/disease), subtypesof(/datum/disease)) diff --git a/code/datums/managed_browsers/feedback_viewer.dm b/code/datums/managed_browsers/feedback_viewer.dm index eca1fff099..6b9bc8c13d 100644 --- a/code/datums/managed_browsers/feedback_viewer.dm +++ b/code/datums/managed_browsers/feedback_viewer.dm @@ -23,7 +23,7 @@ var/database/query/last_query = null /datum/managed_browser/feedback_viewer/New(client/new_client) - if(!check_rights(R_ADMIN|R_DEBUG|R_EVENT, new_client)) // Just in case someone figures out a way to spawn this as non-staff. + if(!check_rights_for(new_client, R_ADMIN|R_DEBUG|R_EVENT)) // Just in case someone figures out a way to spawn this as non-staff. message_admins("[new_client] tried to view feedback with insufficent permissions.") qdel(src) diff --git a/code/datums/mind.dm b/code/datums/mind.dm index ebb2221b16..06017b0b43 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -403,7 +403,7 @@ take_uplink() memory = null//Remove any memory they may have had. if("crystals") - if (usr.client.holder.rights & R_FUN) + if (check_rights_for(usr.client, R_FUN)) // var/obj/item/uplink/hidden/suplink = find_syndicate_uplink() No longer needed, uses stored in mind var/crystals crystals = tcrystals diff --git a/code/game/jobs/job_controller.dm b/code/game/jobs/job_controller.dm index e9cc8070d9..9d144e2a23 100644 --- a/code/game/jobs/job_controller.dm +++ b/code/game/jobs/job_controller.dm @@ -418,7 +418,7 @@ var/global/datum/controller/occupations/job_master permitted = 1 // Check if they're whitelisted for this gear (in alien whitelist? seriously?) - if(G.whitelisted && !is_alien_whitelisted(H, GLOB.all_species[G.whitelisted])) + if(G.whitelisted && !is_alien_whitelisted(H.client, GLOB.all_species[G.whitelisted])) permitted = 0 // If they aren't, tell them diff --git a/code/game/jobs/whitelist.dm b/code/game/jobs/whitelist.dm index f46b6688b1..7facdf3cdc 100644 --- a/code/game/jobs/whitelist.dm +++ b/code/game/jobs/whitelist.dm @@ -46,13 +46,13 @@ GLOBAL_LIST_EMPTY(alien_whitelist) // CHOMPEdit - Managed Globals GLOB.alien_whitelist[key] = our_whitelists // CHOMPEdit - Managed Globals our_whitelists += left_and_right[2] -/proc/is_alien_whitelisted(mob/M, var/datum/species/species) +/proc/is_alien_whitelisted(client/C, var/datum/species/species) //They are admin or the whitelist isn't in use - if(whitelist_overrides(M)) + if(whitelist_overrides(C)) return TRUE //You did something wrong - if(!M || !species) + if(!C || !species) return FALSE //The species isn't even whitelisted @@ -60,7 +60,7 @@ GLOBAL_LIST_EMPTY(alien_whitelist) // CHOMPEdit - Managed Globals return TRUE //Search the whitelist - var/list/our_whitelists = GLOB.alien_whitelist[M.ckey] // CHOMPEdit - Managed Globals + var/list/our_whitelists = GLOB.alien_whitelist[C.ckey] // CHOMPEdit - Managed Globals if("All" in our_whitelists) return TRUE if(species.name in our_whitelists) @@ -112,10 +112,14 @@ GLOBAL_LIST_EMPTY(alien_whitelist) // CHOMPEdit - Managed Globals if(findtext(s,"[M.ckey] - All")) return 1 -/proc/whitelist_overrides(mob/M) +/proc/whitelist_overrides(client/C) if(!CONFIG_GET(flag/usealienwhitelist)) return TRUE - if(check_rights(R_ADMIN|R_EVENT, 0, M)) + if(ismob(C)) //Someone fed a mob into this by mistake. Bad, but we planned ahead for these mistakes. + var/mob/mob = C + C = mob.client + + if(check_rights_for(C, R_ADMIN|R_EVENT|R_DEBUG)) return TRUE return FALSE diff --git a/code/game/objects/structures/ghost_pods/event_vr.dm b/code/game/objects/structures/ghost_pods/event_vr.dm index 41e4ed21c1..ee76fde064 100644 --- a/code/game/objects/structures/ghost_pods/event_vr.dm +++ b/code/game/objects/structures/ghost_pods/event_vr.dm @@ -211,7 +211,7 @@ return //No whitelist - if(!is_alien_whitelisted(user, GLOB.all_species[user.client.prefs.species])) + if(!is_alien_whitelisted(user.client, GLOB.all_species[user.client.prefs.species])) to_chat(user, span_warning("You cannot use this spawnpoint to spawn as a species you are not whitelisted for!")) return diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm index 299221c60f..a28d8a98c9 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -130,7 +130,7 @@ /obj/structure/mirror/raider/attack_hand(var/mob/living/carbon/human/user) if(istype(get_area(src),/area/syndicate_mothership)) - if(istype(user) && user.mind && user.mind.special_role == "Raider" && user.species.name != SPECIES_VOX && is_alien_whitelisted(user, SPECIES_VOX)) + if(istype(user) && user.mind && user.mind.special_role == "Raider" && user.species.name != SPECIES_VOX && is_alien_whitelisted(user.client, SPECIES_VOX)) var/choice = tgui_alert(user, "Do you wish to become a true Vox of the Shoal? This is not reversible.", "Become Vox?", list("No","Yes")) if(choice && choice == "Yes") var/mob/living/carbon/human/vox/vox = new(get_turf(src),SPECIES_VOX) diff --git a/code/game/world.dm b/code/game/world.dm index d88b85278a..3987ac8c12 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -36,6 +36,8 @@ config.Load(params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) + load_admins() + ConfigLoaded() makeDatumRefLists() VgsNew() @@ -165,7 +167,7 @@ var/world_topic_spam_protect_time = world.timeofday if(C.holder) if(C.holder.fakekey) continue - admins[C.key] = C.holder.rank + admins[C.key] = C.holder.rank_names() players += C.key if(isliving(C.mob)) active++ @@ -536,7 +538,7 @@ var/world_topic_spam_protect_time = world.timeofday continue var/title = "Moderator" - var/rights = admin_ranks[title] + var/rights = GLOB.admin_ranks[title] var/ckey = copytext(line, 1, length(line)+1) var/datum/admins/D = new /datum/admins(title, rights, ckey) diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index 4d99d90743..6acbf36fc2 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -1,7 +1,7 @@ #ifndef OVERRIDE_BAN_SYSTEM //Blocks an attempt to connect before even creating our client datum thing. /world/IsBanned(key,address,computer_id) - if(ckey(key) in admin_datums) + if(ckey(key) in GLOB.admin_datums) return ..() //Guest Checking diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index eebf0c1712..48287ef138 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -7,7 +7,7 @@ var/global/floorIsLava = 0 //log_adminwarn(msg) //log_and_message_admins is for this for(var/client/C in GLOB.admins) - if((R_ADMIN|R_MOD) & C.holder.rights) + if(check_rights_for(C, (R_ADMIN|R_MOD|R_SERVER))) to_chat(C, type = MESSAGE_TYPE_ADMINLOG, html = msg, @@ -16,7 +16,7 @@ var/global/floorIsLava = 0 /proc/msg_admin_attack(var/text) //Toggleable Attack Messages var/rendered = span_filter_attacklog(span_log_message(span_prefix("ATTACK:") + span_message("[text]"))) for(var/client/C in GLOB.admins) - if((R_ADMIN|R_MOD) & C.holder.rights) + if(check_rights_for(C, (R_ADMIN|R_MOD))) if(C.prefs?.read_preference(/datum/preference/toggle/show_attack_logs)) var/msg = rendered to_chat(C, @@ -26,8 +26,16 @@ var/global/floorIsLava = 0 /proc/admin_notice(var/message, var/rights) for(var/mob/M in mob_list) - if(check_rights(rights, 0, M)) - to_chat(M,message) + var/C = M.client + + if(!C) + return + + if(!(istype(C, /client))) + return + + if(check_rights_for(C, rights)) + to_chat(C, message) ///////////////////////////////////////////////////////////////////////////////////////////////Panels @@ -49,7 +57,7 @@ var/global/floorIsLava = 0 body += "Options panel for" + span_bold("[M]") if(M.client) body += " played by " + span_bold("[M.client]") - body += "\[[M.client.holder ? M.client.holder.rank : "Player"]\]" + body += "\[[M.client.holder ? M.client.holder.rank_names() : "Player"]\]" if(isnewplayer(M)) body += span_bold(" Hasn't Entered Game") @@ -1353,7 +1361,7 @@ var/datum/announcement/minor/admin_min_announcer = new if(istype(whom, /mob)) M = whom C = M.client - if(R_HOST & C.holder.rights) + if(check_rights_for(C, R_HOST)) return 1 else return 0 @@ -1562,12 +1570,12 @@ var/datum/announcement/minor/admin_min_announcer = new if(P.sender) // sent as a reply log_admin("[key_name(src.owner)] replied to a fax message from [key_name(P.sender)]") for(var/client/C in GLOB.admins) - if((R_ADMIN | R_MOD | R_EVENT) & C.holder.rights) + if(check_rights_for(C, (R_ADMIN | R_MOD | R_EVENT))) to_chat(C, span_log_message("[span_prefix("FAX LOG:")][key_name_admin(src.owner)] replied to a fax message from [key_name_admin(P.sender)] (VIEW)")) else log_admin("[key_name(src.owner)] has sent a fax message to [destination.department]") for(var/client/C in GLOB.admins) - if((R_ADMIN | R_MOD | R_EVENT) & C.holder.rights) + if(check_rights_for(C, (R_ADMIN | R_MOD | R_EVENT))) to_chat(C, span_log_message("[span_prefix("FAX LOG:")][key_name_admin(src.owner)] has sent a fax message to [destination.department] (VIEW)")) var/plaintext_title = P.sender ? "replied to [key_name(P.sender)]'s fax" : "sent a fax message to [destination.department]" @@ -1590,3 +1598,18 @@ var/datum/announcement/minor/admin_min_announcer = new qdel(P) faxreply = null return + +/datum/admins/proc/set_uplink(mob/living/carbon/human/H as mob) + set category = "Debug.Events" + set name = "Set Uplink" + set desc = "Allows admins to set up an uplink on a character. This will be required for a character to use telecrystals." + set popup_menu = FALSE + + if(check_rights(R_ADMIN|R_DEBUG)) + traitors.spawn_uplink(H) + H.mind.tcrystals = DEFAULT_TELECRYSTAL_AMOUNT + H.mind.accept_tcrystals = 1 + var/msg = "[key_name(usr)] has given [H.ckey] an uplink." + message_admins(msg) + else + to_chat(usr, "You do not have access to this command.") diff --git a/code/modules/admin/admin_ranks.dm b/code/modules/admin/admin_ranks.dm index 6dda2e8077..59adf89e0a 100644 --- a/code/modules/admin/admin_ranks.dm +++ b/code/modules/admin/admin_ranks.dm @@ -1,175 +1,398 @@ -var/list/admin_ranks = list() //list of all ranks with associated rights +GLOBAL_LIST_EMPTY(admin_ranks) //list of all admin_rank datums +GLOBAL_PROTECT(admin_ranks) -//load our rank - > rights associations -/proc/load_admin_ranks() - admin_ranks.Cut() +GLOBAL_LIST_EMPTY(protected_ranks) //admin ranks loaded from txt +GLOBAL_PROTECT(protected_ranks) - var/previous_rights = 0 +/datum/admin_rank + var/name = "NoRank" + var/rights = R_DEFAULT + var/exclude_rights = NONE + var/include_rights = NONE + var/can_edit_rights = NONE - //Clear profile access - for(var/A in world.GetConfig("admin")) - world.SetConfig("APP/admin", A, null) +/datum/admin_rank/New(init_name, 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 + QDEL_IN(src, 0) + CRASH("Admin proc call creation of admin datum") + return + name = init_name + if(!name) + qdel(src) + CRASH("Admin rank created without name.") + if(init_rights) + rights = init_rights + include_rights = rights + if(init_exclude_rights) + exclude_rights = init_exclude_rights + rights &= ~exclude_rights + if(init_edit_rights) + can_edit_rights = init_edit_rights - //load text from file - var/list/Lines = file2list("config/admin_ranks.txt") +/datum/admin_rank/Destroy() + if(IsAdminAdvancedProcCall()) + alert_to_permissions_elevation_attempt(usr) + return QDEL_HINT_LETMELIVE + . = ..() - //process each line seperately - for(var/line in Lines) - if(!length(line)) continue - if(copytext(line,1,2) == "#") continue +/datum/admin_rank/vv_edit_var(var_name, var_value) + return FALSE - var/list/List = splittext(line,"+") - if(!List.len) continue - - var/rank = ckeyEx(List[1]) - switch(rank) - if(null,"") continue - if("Removed") continue //Reserved - - var/rights = 0 - for(var/i=2, i<=List.len, i++) - switch(ckey(List[i])) - if("@","prev") rights |= previous_rights - if("buildmode","build") rights |= R_BUILDMODE - if("admin") rights |= R_ADMIN - if("ban") rights |= R_BAN - if("fun") rights |= R_FUN - if("server") rights |= R_SERVER - if("debug") rights |= R_DEBUG - if("permissions","rights") rights |= R_PERMISSIONS - if("possess") rights |= R_POSSESS - if("stealth") rights |= R_STEALTH - if("rejuv","rejuvinate") rights |= R_REJUVINATE - if("varedit") rights |= R_VAREDIT - if("everything","host","all") rights |= (R_HOST | R_BUILDMODE | R_ADMIN | R_BAN | R_FUN | R_SERVER | R_DEBUG | R_PERMISSIONS | R_POSSESS | R_STEALTH | R_REJUVINATE | R_VAREDIT | R_SOUNDS | R_SPAWN | R_MOD| R_EVENT) - if("sound","sounds") rights |= R_SOUNDS - if("spawn","create") rights |= R_SPAWN - if("mod") rights |= R_MOD - if("event") rights |= R_EVENT - - admin_ranks[rank] = rights - previous_rights = rights +// Adds/removes rights to this admin_rank +/datum/admin_rank/proc/process_keyword(group, group_count, datum/admin_rank/previous_rank) + if(IsAdminAdvancedProcCall()) + alert_to_permissions_elevation_attempt(usr) + return + var/list/keywords = splittext(group, " ") + var/flag = 0 + for(var/k in keywords) + switch(k) + if("BUILD") + flag = R_BUILDMODE + if("ADMIN") + flag = R_ADMIN + if("BAN") + flag = R_BAN + if("FUN") + flag = R_FUN + if("SERVER") + flag = R_SERVER + if("DEBUG") + flag = R_DEBUG + if("PERMISSIONS") + flag = R_PERMISSIONS + if("POSSESS") + flag = R_POSSESS + if("STEALTH") + flag = R_STEALTH + if("REJUVINATE") + flag = R_REJUVINATE + if("VAREDIT") + flag = R_VAREDIT + if("EVERYTHING") + flag = R_EVERYTHING + if("SOUND") + flag = R_SOUNDS + if("SPAWN") + flag = R_SPAWN + if("MOD") + flag = R_MOD + if("EVENT") + flag = R_EVENT + if("@") + if(previous_rank) + switch(group_count) + if(1) + flag = previous_rank.include_rights + if(2) + flag = previous_rank.exclude_rights + if(3) + flag = previous_rank.can_edit_rights + else + continue + switch(group_count) + if(1) + rights |= flag + include_rights |= flag + if(2) + rights &= ~flag + exclude_rights |= flag + if(3) + can_edit_rights |= flag +/// Loads admin ranks. +/// Return a list containing the backup data if they were loaded from the database backup json +/proc/load_admin_ranks(dbfail, no_update) + if(IsAdminAdvancedProcCall()) + to_chat(usr, "Admin Reload blocked: Advanced ProcCall detected.", confidential = TRUE) + return + GLOB.admin_ranks.Cut() + GLOB.protected_ranks.Cut() + //load text from file and process each entry + var/ranks_text = file2text("[global.config.directory]/admin_ranks.txt") + 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) + 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) + count++ + GLOB.admin_ranks += R + GLOB.protected_ranks += R + previous_rank = R + if(!CONFIG_GET(flag/admin_legacy_system) && !dbfail) + if(CONFIG_GET(flag/load_legacy_ranks_only)) + if(!no_update) + sync_ranks_with_db() + else + var/datum/db_query/query_load_admin_ranks = SSdbcore.NewQuery("SELECT `rank`, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")]") + if(!query_load_admin_ranks.Execute()) + message_admins("Error loading admin ranks from database. Loading from backup.") + log_sql("Error loading admin ranks from database. Loading from backup.") + dbfail = TRUE + else + while(query_load_admin_ranks.NextRow()) + var/skip + var/rank_name = query_load_admin_ranks.item[1] + for(var/datum/admin_rank/R in GLOB.admin_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 + qdel(query_load_admin_ranks) + //load ranks from backup file + if(dbfail) + var/backup_file = file2text("data/admins_backup.json") + if(backup_file == null) + log_world("Unable to locate admins backup file.") + return FALSE + var/list/json = json_decode(backup_file) + for(var/J in json["ranks"]) + var/skip + for(var/datum/admin_rank/R in GLOB.admin_ranks) + if(R.name == "[J]") //this rank was already loaded from txt override + 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) + continue + GLOB.admin_ranks += R + return json #ifdef TESTING var/msg = "Permission Sets Built:\n" - for(var/rank in admin_ranks) - msg += "\t[rank] - [admin_ranks[rank]]\n" + for(var/datum/admin_rank/R in GLOB.admin_ranks) + msg += "\t[R.name]" + var/rights = rights2text(R.rights,"\n\t\t") + if(rights) + msg += "\t\t[rights]\n" testing(msg) #endif -/hook/startup/proc/loadAdmins() - load_admins() - return 1 +/// Converts a rank name (such as "Coder+Moth") into a list of /datum/admin_rank +/proc/ranks_from_rank_name(rank_name) + var/list/rank_names = splittext(rank_name, "+") + var/list/ranks = list() -/proc/load_admins() + for (var/datum/admin_rank/rank as anything in GLOB.admin_ranks) + if (rank.name in rank_names) + rank_names -= rank.name + ranks += rank + + if (rank_names.len == 0) + break + + if (rank_names.len > 0) + log_config("Admin rank names were invalid: [jointext(ranks, ", ")]") + + return ranks + +/// Takes a list of rank names and joins them with + +/proc/join_admin_ranks(list/datum/admin_rank/ranks) + var/list/names = list() + + for (var/datum/admin_rank/rank as anything in ranks) + names += rank.name + + return jointext(names, "+") + +/// (Re)Loads the admin list. +/// returns TRUE if database admins had to be loaded from the backup json +/proc/load_admins(no_update) + var/dbfail + if(!CONFIG_GET(flag/admin_legacy_system) && !SSdbcore.Connect()) + message_admins("Failed to connect to database while loading admins. Loading from backup.") + log_sql("Failed to connect to database while loading admins. Loading from backup.") + dbfail = TRUE //clear the datums references - admin_datums.Cut() + GLOB.admin_datums.Cut() for(var/client/C in GLOB.admins) C.remove_admin_verbs() C.holder = null GLOB.admins.Cut() - load_admin_ranks() //CHOMP Edit: moved this from "f(config.admin_legacy_system)" and put it here instead, literally just moved it 3 lines. + GLOB.protected_admins.Cut() + GLOB.deadmins.Cut() + var/list/backup_file_json = load_admin_ranks(dbfail, no_update) + dbfail = backup_file_json != null + //Clear profile access + for(var/A in world.GetConfig("admin")) + world.SetConfig("APP/admin", A, null) + var/list/rank_names = list() + for(var/datum/admin_rank/R in GLOB.admin_ranks) + rank_names[R.name] = R + //ckeys listed in admins.txt are always made admins before sql loading is attempted + var/admins_text = file2text("[global.config.directory]/admins.txt") + var/regex/admins_regex = new(@"^(?!#)(.+?)\s+=\s+(.+)", "gm") - if(CONFIG_GET(flag/admin_legacy_system)) // CHOMPEdit - //Clear profile access - for(var/A in world.GetConfig("admin")) - world.SetConfig("APP/admin", A, null) + while(admins_regex.Find(admins_text)) + var/admin_key = admins_regex.group[1] + var/admin_rank = admins_regex.group[2] + new /datum/admins(ranks_from_rank_name(admin_rank), ckey(admin_key), force_active = FALSE, protected = TRUE) - //load text from file - var/list/Lines = file2list("config/admins.txt") + if(!CONFIG_GET(flag/admin_legacy_system) && !dbfail) + var/datum/db_query/query_load_admins = SSdbcore.NewQuery("SELECT ckey, `rank`, feedback FROM [format_table_name("admin")] ORDER BY `rank`") + if(!query_load_admins.Execute()) + message_admins("Error loading admins from database. Loading from backup.") + log_sql("Error loading admins from database. Loading from backup.") + dbfail = 1 + else + while(query_load_admins.NextRow()) + var/admin_ckey = ckey(query_load_admins.item[1]) + var/admin_rank = query_load_admins.item[2] + var/admin_feedback = query_load_admins.item[3] + var/skip - //process each line seperately - for(var/line in Lines) - if(!length(line)) continue - if(copytext(line,1,2) == "#") continue - - //Split the line at every "-" - var/list/List = splittext(line, "-") - if(!List.len) continue - - //ckey is before the first "-" - var/ckey = ckey(List[1]) - if(!ckey) continue - - //rank follows the first "-" - var/rank = "" - if(List.len >= 2) - rank = ckeyEx(List[2]) - - //load permissions associated with this rank - var/rights = admin_ranks[rank] - - //create the admin datum and store it for later use - var/datum/admins/D = new /datum/admins(rank, rights, ckey) - - if(D.rights & R_DEBUG) //grant profile access - world.SetConfig("APP/admin", ckey, "role=admin") - - //find the client for a ckey if they are connected and associate them with the new admin datum - D.associate(GLOB.directory[ckey]) - - else - //The current admin system uses SQL - - establish_db_connection() - if(!SSdbcore.IsConnected()) - error("Failed to connect to database in load_admins(). Reverting to legacy system.") - log_misc("Failed to connect to database in load_admins(). Reverting to legacy system.") - CONFIG_SET(flag/admin_legacy_system, TRUE) - load_admins() - return - - var/datum/db_query/query = SSdbcore.NewQuery("SELECT ckey, rank, level, flags FROM erro_admin") - query.Execute() - while(query.NextRow()) - var/ckey = query.item[1] - var/rank = query.item[2] - if(rank == "Removed") continue //This person was de-adminned. They are only in the admin list for archive purposes. - - var/rights = query.item[4] - if(istext(rights)) rights = text2num(rights) - var/datum/admins/D = new /datum/admins(rank, rights, ckey) - - if(D.rights & R_DEBUG) //grant profile access - world.SetConfig("APP/admin", ckey, "role=admin") - - //find the client for a ckey if they are connected and associate them with the new admin datum - D.associate(GLOB.directory[ckey]) - qdel(query) - if(!admin_datums) - error("The database query in load_admins() resulted in no admins being added to the list. Reverting to legacy system.") - log_misc("The database query in load_admins() resulted in no admins being added to the list. Reverting to legacy system.") - CONFIG_SET(flag/admin_legacy_system, TRUE) - load_admins() - return + var/list/admin_ranks = ranks_from_rank_name(admin_rank) + if(admin_ranks.len == 0) + message_admins("[admin_ckey] loaded with invalid admin rank [admin_rank].") + skip = 1 + if(GLOB.admin_datums[admin_ckey] || GLOB.deadmins[admin_ckey]) + skip = 1 + if(!skip) + var/datum/admins/admin_holder = new(admin_ranks, admin_ckey) + admin_holder.cached_feedback_link = admin_feedback || NO_FEEDBACK_LINK + qdel(query_load_admins) + if (!no_update) + save_admin_backup() + sync_admins_with_db() + //load admins from backup file + if(dbfail) + if(!backup_file_json) + if(backup_file_json != null) + //already tried + return + var/backup_file = file2text("data/admins_backup.json") + if(backup_file == null) + log_world("Unable to locate admins backup file.") + return + backup_file_json = json_decode(backup_file) + for(var/backup_admin_ckey in backup_file_json["admins"]) + var/skip + for(var/admin_ckey in GLOB.admin_datums + GLOB.deadmins) + if(ckey(admin_ckey) == ckey("[backup_admin_ckey]")) //this admin was already loaded from txt override + skip = TRUE + break + if(skip) + continue + new /datum/admins(ranks_from_rank_name(backup_file_json["admins"]["[backup_admin_ckey]"]), ckey("[backup_admin_ckey]")) #ifdef TESTING var/msg = "Admins Built:\n" - for(var/ckey in admin_datums) - var/rank - var/datum/admins/D = admin_datums[ckey] - if(D) rank = D.rank - msg += "\t[ckey] - [rank]\n" + for(var/ckey in GLOB.admin_datums) + var/datum/admins/D = GLOB.admin_datums[ckey] + msg += "\t[ckey] - [D.rank_names()]\n" testing(msg) #endif + return dbfail -#ifdef TESTING -/client/verb/changerank(newrank in admin_ranks) - if(holder) - holder.rank = newrank - holder.rights = admin_ranks[newrank] - else - holder = new /datum/admins(newrank,admin_ranks[newrank],ckey) - remove_admin_verbs() - holder.associate(src) +/proc/sync_ranks_with_db() + set waitfor = FALSE -/client/verb/changerights(newrights as num) - if(holder) - holder.rights = newrights - else - holder = new /datum/admins("testing",newrights,ckey) - remove_admin_verbs() - holder.associate(src) + if(IsAdminAdvancedProcCall()) + to_chat(usr, "Admin rank DB Sync blocked: Advanced ProcCall detected.", confidential = TRUE) + return -#endif + var/list/sql_ranks = list() + for(var/datum/admin_rank/R as anything in GLOB.protected_ranks) + sql_ranks += list(list("rank" = R.name, "flags" = R.include_rights, "exclude_flags" = R.exclude_rights, "can_edit_flags" = R.can_edit_rights)) + SSdbcore.MassInsert(format_table_name("admin_ranks"), sql_ranks, duplicate_key = TRUE) + update_everything_flag_in_db() + + +/proc/update_everything_flag_in_db() + for(var/datum/admin_rank/R as anything in GLOB.admin_ranks) + var/list/flags = list() + if(R.include_rights == R_EVERYTHING) + flags += "flags" + if(R.exclude_rights == R_EVERYTHING) + flags += "exclude_flags" + if(R.can_edit_rights == R_EVERYTHING) + flags += "can_edit_flags" + if(!flags.len) + continue + var/flags_to_check = flags.Join(" != [R_EVERYTHING] AND ") + " != [R_EVERYTHING]" + var/datum/db_query/query_check_everything_ranks = SSdbcore.NewQuery( + "SELECT flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] WHERE rank = :rank AND ([flags_to_check])", + list("rank" = R.name) + ) + if(!query_check_everything_ranks.Execute()) + qdel(query_check_everything_ranks) + return + if(query_check_everything_ranks.NextRow()) //no row is returned if the rank already has the correct flag value + var/flags_to_update = flags.Join(" = [R_EVERYTHING], ") + " = [R_EVERYTHING]" + var/datum/db_query/query_update_everything_ranks = SSdbcore.NewQuery( + "UPDATE [format_table_name("admin_ranks")] SET [flags_to_update] WHERE rank = :rank", + list("rank" = R.name) + ) + if(!query_update_everything_ranks.Execute()) + qdel(query_update_everything_ranks) + return + qdel(query_update_everything_ranks) + qdel(query_check_everything_ranks) + + +/proc/sync_admins_with_db() + if(IsAdminAdvancedProcCall()) + to_chat(usr, "Admin rank DB Sync blocked: Advanced ProcCall detected.") + return + + if(CONFIG_GET(flag/admin_legacy_system) || !SSdbcore.IsConnected()) //we're already using legacy system so there's nothing to save + return + sync_ranks_with_db() + var/list/sql_admins = list() + for(var/holder_ckey in GLOB.protected_admins) + var/datum/admins/holder = GLOB.protected_admins[holder_ckey] + sql_admins += list(list("ckey" = holder.target, "rank" = holder.rank_names())) + SSdbcore.MassInsert(format_table_name("admin"), sql_admins, duplicate_key = TRUE) + var/datum/db_query/query_admin_rank_update = SSdbcore.NewQuery("UPDATE [format_table_name("player")] AS p INNER JOIN [format_table_name("admin")] AS a ON p.ckey = a.ckey SET p.lastadminrank = a.rank") + query_admin_rank_update.Execute() + qdel(query_admin_rank_update) + +/proc/save_admin_backup() + if(IsAdminAdvancedProcCall()) + to_chat(usr, "Admin rank DB Sync blocked: Advanced ProcCall detected.") + return + + if(CONFIG_GET(flag/admin_legacy_system)) //we're already using legacy system so there's nothing to save + return + + //json format backup file generation stored per server + var/json_file = file("data/admins_backup.json") + var/list/file_data = list( + "ranks" = list(), + "admins" = list() + ) + for(var/datum/admin_rank/R as anything in GLOB.admin_ranks) + file_data["ranks"]["[R.name]"] = list() + file_data["ranks"]["[R.name]"]["include rights"] = R.include_rights + file_data["ranks"]["[R.name]"]["exclude rights"] = R.exclude_rights + file_data["ranks"]["[R.name]"]["can edit rights"] = R.can_edit_rights + + for(var/admin_ckey in GLOB.admin_datums + GLOB.deadmins) + var/datum/admins/admin = GLOB.admin_datums[admin_ckey] + + if(!admin) + admin = GLOB.deadmins[admin_ckey] + if (!admin) + continue + + file_data["admins"][admin_ckey] = admin.rank_names() + + //admin.backup_connections() + + fdel(json_file) + WRITE_FILE(json_file, json_encode(file_data, JSON_PRETTY_PRINT)) diff --git a/code/modules/admin/admin_secrets.dm b/code/modules/admin/admin_secrets.dm index 6c65a272e2..d88ea17270 100644 --- a/code/modules/admin/admin_secrets.dm +++ b/code/modules/admin/admin_secrets.dm @@ -55,7 +55,7 @@ var/datum/admin_secrets/admin_secrets = new() return name /datum/admin_secret_item/proc/can_view(var/mob/user) - return check_rights(permissions, 0, user) + return check_rights_for(user.client, permissions) /datum/admin_secret_item/proc/can_execute(var/mob/user) if(can_view(user)) diff --git a/code/modules/admin/admin_verb_lists.dm b/code/modules/admin/admin_verb_lists.dm index 8f898a28f1..43957efe31 100644 --- a/code/modules/admin/admin_verb_lists.dm +++ b/code/modules/admin/admin_verb_lists.dm @@ -3,7 +3,7 @@ var/list/admin_verbs_default = list( /datum/admins/proc/show_player_panel, //shows an interface for individual players, with various links (links require additional flags, /client/proc/player_panel_new, //shows an interface for all players, with links to various panels, /client/proc/player_panel, - /client/proc/deadmin_self, //destroys our own admin datum so we can play as a regular player, + /client/proc/deadmin, //destroys our own admin datum so we can play as a regular player, /client/proc/hide_verbs, //hides all our adminverbs, /client/proc/hide_most_verbs, //hides all our hideable adminverbs, /client/proc/debug_variables, //allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify, @@ -273,7 +273,7 @@ var/list/admin_verbs_rejuv = list( //verbs which can be hidden - needs work var/list/admin_verbs_hideable = list( - /client/proc/deadmin_self, + /client/proc/deadmin, // /client/proc/deadchat, /datum/admins/proc/show_traitor_panel, /datum/admins/proc/toggleenter, diff --git a/code/modules/admin/admin_verb_lists_vr.dm b/code/modules/admin/admin_verb_lists_vr.dm index 5e7f479c5c..3d1c682b7d 100644 --- a/code/modules/admin/admin_verb_lists_vr.dm +++ b/code/modules/admin/admin_verb_lists_vr.dm @@ -3,7 +3,7 @@ var/list/admin_verbs_default = list( // /datum/admins/proc/show_player_panel, //shows an interface for individual players, with various links (links require additional flags, //VOREStation Remove, // /client/proc/player_panel_new, //shows an interface for all players, with links to various panels, //VOREStation Remove, // /client/proc/player_panel, //VOREStation Remove, - /client/proc/deadmin_self, //destroys our own admin datum so we can play as a regular player, + /client/proc/deadmin, //destroys our own admin datum so we can play as a regular player, /client/proc/cmd_admin_say, //VOREStation Add, /client/proc/cmd_mod_say, //VOREStation Add, /client/proc/cmd_event_say, //VOREStation Add, @@ -320,7 +320,7 @@ var/list/admin_verbs_rejuv = list( //verbs which can be hidden - needs work var/list/admin_verbs_hideable = list( - /client/proc/deadmin_self, + /client/proc/deadmin, // /client/proc/deadchat, /datum/admins/proc/show_traitor_panel, /datum/admins/proc/toggleenter, @@ -596,24 +596,25 @@ var/list/admin_verbs_event_manager = list( /client/proc/add_admin_verbs() if(holder) + var/rights = holder.rank_flags() add_verb(src, admin_verbs_default) - if(holder.rights & R_BUILDMODE) add_verb(src, /client/proc/togglebuildmodeself) - if(holder.rights & R_ADMIN) add_verb(src, admin_verbs_admin) - if(holder.rights & R_BAN) add_verb(src, admin_verbs_ban) - if(holder.rights & R_FUN) add_verb(src, admin_verbs_fun) - if(holder.rights & R_SERVER) add_verb(src, admin_verbs_server) - if(holder.rights & R_DEBUG) + if(rights & R_BUILDMODE) add_verb(src, /client/proc/togglebuildmodeself) + if(rights & R_ADMIN) add_verb(src, admin_verbs_admin) + if(rights & R_BAN) add_verb(src, admin_verbs_ban) + if(rights & R_FUN) add_verb(src, admin_verbs_fun) + if(rights & R_SERVER) add_verb(src, admin_verbs_server) + if(rights & R_DEBUG) add_verb(src, admin_verbs_debug) - if(CONFIG_GET(flag/debugparanoid) && !(holder.rights & R_ADMIN)) + if(CONFIG_GET(flag/debugparanoid) && !(rights & R_ADMIN)) remove_verb(src, admin_verbs_paranoid_debug) //Right now it's just callproc but we can easily add others later on. - if(holder.rights & R_POSSESS) add_verb(src, admin_verbs_possess) - if(holder.rights & R_PERMISSIONS) add_verb(src, admin_verbs_permissions) - if(holder.rights & R_STEALTH) add_verb(src, /client/proc/stealth) - if(holder.rights & R_REJUVINATE) add_verb(src, admin_verbs_rejuv) - if(holder.rights & R_SOUNDS) add_verb(src, admin_verbs_sounds) - if(holder.rights & R_SPAWN) add_verb(src, admin_verbs_spawn) - if(holder.rights & R_MOD) add_verb(src, admin_verbs_mod) - if(holder.rights & R_EVENT) add_verb(src, admin_verbs_event_manager) + if(rights & R_POSSESS) add_verb(src, admin_verbs_possess) + if(rights & R_PERMISSIONS) add_verb(src, admin_verbs_permissions) + if(rights & R_STEALTH) add_verb(src, /client/proc/stealth) + if(rights & R_REJUVINATE) add_verb(src, admin_verbs_rejuv) + if(rights & R_SOUNDS) add_verb(src, admin_verbs_sounds) + if(rights & R_SPAWN) add_verb(src, admin_verbs_spawn) + if(rights & R_MOD) add_verb(src, admin_verbs_mod) + if(rights & R_EVENT) add_verb(src, admin_verbs_event_manager) //CHOMPEdit Begin /client/proc/remove_admin_verbs() diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index e5302f3cb2..cb198a5870 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -206,7 +206,7 @@ if(!check_rights(R_ADMIN)) return if(!warned_ckey || !istext(warned_ckey)) return - if(warned_ckey in admin_datums) + if(warned_ckey in GLOB.admin_datums) to_chat(usr, span_warning("Error: warn(): You can't warn admins.")) return @@ -335,36 +335,22 @@ log_admin("[key_name(usr)] used 'kill air'.") message_admins(span_blue("[key_name_admin(usr)] used 'kill air'."), 1) -/client/proc/readmin_self() - set name = "Re-Admin self" +/client/proc/deadmin() + set name = "DeAdmin" set category = "Admin.Misc" + set desc = "Shed your admin powers." - if(deadmin_holder) - deadmin_holder.reassociate() - log_admin("[src] re-admined themself.") - message_admins("[src] re-admined themself.", 1) - to_chat(src, span_filter_system(span_interface("You now have the keys to control the planet, or at least a small space station"))) - remove_verb(src, /client/proc/readmin_self) - if(isobserver(mob)) - var/mob/observer/dead/our_mob = mob - our_mob.visualnet?.addVisibility(our_mob, src) - -/client/proc/deadmin_self() - set name = "De-admin self" - set category = "Admin.Misc" - - if(holder) - if(tgui_alert(usr, "Confirm self-deadmin for the round? You can't re-admin yourself without someone promoting you.","Deadmin",list("Yes","No")) == "Yes") - log_admin("[src] deadmined themself.") - message_admins("[src] deadmined themself.", 1) - deadmin() - to_chat(src, span_filter_system(span_interface("You are now a normal player."))) - add_verb(src, /client/proc/readmin_self) - if(isobserver(mob)) - var/mob/observer/dead/our_mob = mob - our_mob.visualnet?.removeVisibility(our_mob, src) + src.holder.deactivate() + to_chat(src, span_interface("You are now a normal player.")) + log_admin("[key_name(src)] deadminned themselves.") + message_admins("[key_name_admin(src)] deadminned themselves.") + //BLACKBOX_LOG_ADMIN_VERB("Deadmin") feedback_add_details("admin_verb","DAS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + if(isobserver(mob)) + var/mob/observer/dead/our_mob = mob + our_mob.visualnet?.removeVisibility(our_mob, src) + /client/proc/toggle_log_hrefs() set name = "Toggle href logging" set category = "Server.Config" @@ -584,3 +570,144 @@ return SSmotiontracker.hide_all = !SSmotiontracker.hide_all log_admin("[key_name(usr)] changed the motion echo visibility to [SSmotiontracker.hide_all ? "hidden" : "visible"].") + +/client/proc/adminorbit() + set category = "Fun.Event Kit" + set name = "Orbit Things" + set desc = "Makes something orbit around something else." + set popup_menu = FALSE + + if(!check_rights(R_FUN)) + return + + var/center + var/atom/movable/orbiter + var/input + + if(holder.marked_datum) + input = tgui_alert(usr, "You have \n[holder.marked_datum] marked, should this be the center of the orbit, or the orbiter?", "Orbit", list("Center", "Orbiter", "Neither")) + switch(input) + if("Center") + center = holder.marked_datum + if("Orbiter") + orbiter = holder.marked_datum + var/list/possible_things = list() + for(var/T as mob in view(view)) //Let's do mobs before objects + if(ismob(T)) + possible_things |= T + for(var/T as obj in view(view)) + if(isobj(T)) + possible_things |= T + if(!center) + center = tgui_input_list(src, "What should act as the center of the orbit?", "Center", possible_things) + possible_things -= center + if(!orbiter) + orbiter = tgui_input_list(src, "What should act as the orbiter of the orbit?", "Orbiter", possible_things) + if(!center || !orbiter) + to_chat(usr, span_warning("A center of orbit and an orbiter must be configured. You can also do this by marking a target.")) + return + if(center == orbiter) + to_chat(usr, span_warning("The center of the orbit cannot also be the orbiter.")) + return + if(isturf(orbiter)) + to_chat(usr, span_warning("The orbiter cannot be a turf. It can only be used as a center.")) + return + var/distance = tgui_input_number(usr, "How large will their orbit radius be? (In pixels. 32 is 'near around a character)", "Orbit Radius", 32) + var/speed = tgui_input_number(usr, "How fast will they orbit (negative numbers spin clockwise)", "Orbit Speed", 20) + var/segments = tgui_input_number(usr, "How many segments will they have in their orbit? (3 is a triangle, 36 is a circle, etc)", "Orbit Segments", 36) + var/clock = FALSE + if(!distance) + distance = 32 + if(!speed) + speed = 20 + else if (speed < 0) + clock = TRUE + speed *= -1 + if(!segments) + segments = 36 + if(tgui_alert(usr, "\The [orbiter] will orbit around [center]. Is this okay?", "Confirm Orbit", list("Yes", "No")) == "Yes") + orbiter.orbit(center, distance, clock, speed, segments) + +/client/proc/removetickets() + set name = "Security Tickets" + set category = "Admin.Investigate" + set desc = "Allows one to remove tickets from the global list." + + if(!check_rights(R_ADMIN)) + return + + if(security_printer_tickets.len >= 1) + var/input = tgui_input_list(usr, "Which message?", "Security Tickets", security_printer_tickets) + if(!input) + return + if(tgui_alert(usr, "Do you want to remove the following message from the global list? \"[input]\"", "Remove Ticket", list("Yes", "No")) == "Yes") + security_printer_tickets -= input + log_and_message_admins("removed a security ticket from the global list: \"[input]\"", usr) + + else + tgui_alert_async(usr, "The ticket list is empty.","Empty") + +/client/proc/delbook() + set name = "Delete Book" + set desc = "Permamently deletes a book from the database." + set category = "Admin.Game" + if(!src.holder) + to_chat(src, "Only administrators may use this command.") + return + + var/obj/machinery/librarycomp/our_comp + for(var/obj/machinery/librarycomp/l in world) + if(istype(l, /obj/machinery/librarycomp)) + our_comp = l + break + + if(!our_comp) + to_chat(usr, span_warning("Unable to locate a library computer to use for book deleting.")) + return + + var/dat = "Book Inventory Management\n" + dat += "

ADMINISTRATIVE MANAGEMENT

" + establish_db_connection() + + if(!SSdbcore.IsConnected()) + dat += span_red(span_bold("ERROR") + ": Unable to contact External Archive. Please contact your system administrator for assistance.") + else + dat += {"(Order book by SS13BN)

+
+
TITLE[dat]", "window=library") + onclose(usr, "library") + +/client/proc/toggle_spawning_with_recolour() + set name = "Toggle Simple/Robot recolour verb" + set desc = "Makes it so new robots/simple_mobs spawn with a verb to recolour themselves for this round. You must set them separately." + set category = "Server.Game" + + if(!check_rights(R_ADMIN|R_EVENT|R_FUN)) + return + + var/which = tgui_alert(usr, "Which do you want to toggle?", "Choose Recolour Toggle", list("Robot", "Simple Mob")) + switch(which) + if("Robot") + CONFIG_SET(flag/allow_robot_recolor, !CONFIG_GET(flag/allow_robot_recolor)) + to_chat(usr, "You have [CONFIG_GET(flag/allow_robot_recolor) ? "enabled" : "disabled"] newly spawned cyborgs to spawn with the recolour verb") + if("Simple Mob") + CONFIG_SET(flag/allow_simple_mob_recolor, !CONFIG_GET(flag/allow_simple_mob_recolor)) + to_chat(usr, "You have [CONFIG_GET(flag/allow_simple_mob_recolor) ? "enabled" : "disabled"] newly spawned simple mobs to spawn with the recolour verb") diff --git a/code/modules/admin/admin_vr.dm b/code/modules/admin/admin_vr.dm deleted file mode 100644 index 9076957fa6..0000000000 --- a/code/modules/admin/admin_vr.dm +++ /dev/null @@ -1,14 +0,0 @@ -/datum/admins/proc/set_uplink(mob/living/carbon/human/H as mob) - set category = "Debug.Events" - set name = "Set Uplink" - set desc = "Allows admins to set up an uplink on a character. This will be required for a character to use telecrystals." - set popup_menu = FALSE - - if(check_rights(R_ADMIN|R_DEBUG)) - traitors.spawn_uplink(H) - H.mind.tcrystals = DEFAULT_TELECRYSTAL_AMOUNT - H.mind.accept_tcrystals = 1 - var/msg = "[key_name(usr)] has given [H.ckey] an uplink." - message_admins(msg) - else - to_chat(usr, "You do not have access to this command.") diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm index a9547bf6d1..548e8e9f95 100644 --- a/code/modules/admin/holder2.dm +++ b/code/modules/admin/holder2.dm @@ -1,13 +1,18 @@ +GLOBAL_LIST_EMPTY(admin_datums) +GLOBAL_PROTECT(admin_datums) +GLOBAL_LIST_EMPTY(protected_admins) +GLOBAL_PROTECT(protected_admins) + GLOBAL_VAR_INIT(href_token, GenerateToken()) GLOBAL_PROTECT(href_token) -var/list/admin_datums = list() - /datum/admins - var/rank = "Temporary Admin" - var/client/owner = null - var/rights = 0 - var/fakekey = null + var/list/datum/admin_rank/ranks + + var/target + var/name = "nobody's admin datum (no rank)" //Makes for better runtimes + var/client/owner = null + var/fakekey = null var/datum/marked_datum @@ -18,49 +23,194 @@ var/list/admin_datums = list() var/href_token + /// Link from the database pointing to the admin's feedback forum + var/cached_feedback_link -/datum/admins/New(initial_rank = "Temporary Admin", initial_rights = 0, ckey) - if(!ckey) - error("Admin datum created without a ckey argument. Datum has been deleted") - qdel(src) + var/deadmined + + var/given_profiling = FALSE + + +/datum/admins/New(list/datum/admin_rank/ranks, ckey, force_active = FALSE, protected) + if(IsAdminAdvancedProcCall()) + alert_to_permissions_elevation_attempt(usr) + if (!target) //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 + QDEL_IN(src, 0) + CRASH("Admin proc call creation of admin datum") return + if(!ckey) + QDEL_IN(src, 0) + CRASH("Admin datum created without a ckey") + if(!istype(ranks)) + QDEL_IN(src, 0) + CRASH("Admin datum created with invalid ranks: [ranks] ([json_encode(ranks)])") + target = ckey + name = "[ckey]'s admin datum ([join_admin_ranks(ranks)])" + src.ranks = ranks admincaster_signature = "[using_map.company_name] Officer #[rand(0,9)][rand(0,9)][rand(0,9)]" href_token = GenerateToken() - rank = initial_rank - rights = initial_rights - admin_datums[ckey] = src - if(rights & R_DEBUG) //grant profile access - world.SetConfig("APP/admin", ckey, "role=admin") + if(protected) + GLOB.protected_admins[target] = src + activate() -/datum/admins/proc/associate(client/C) - if(istype(C)) - owner = C - owner.holder = src - owner.add_admin_verbs() //TODO - owner.init_verbs() //re-initialize the verb list - GLOB.admins |= C +/datum/admins/Destroy() + if(IsAdminAdvancedProcCall()) + alert_to_permissions_elevation_attempt(usr) + return QDEL_HINT_LETMELIVE + . = ..() + +/datum/admins/proc/activate() + if(IsAdminAdvancedProcCall()) + alert_to_permissions_elevation_attempt(usr) + return + GLOB.deadmins -= target + GLOB.admin_datums[target] = src + deadmined = FALSE + //plane_debug = new(src) + if (GLOB.directory[target]) + associate(GLOB.directory[target]) //find the client for a ckey if they are connected and associate them with us + +/datum/admins/proc/deactivate() + if(IsAdminAdvancedProcCall()) + alert_to_permissions_elevation_attempt(usr) + return + GLOB.deadmins[target] = src + GLOB.admin_datums -= target + //QDEL_NULL(plane_debug) + deadmined = TRUE + + var/client/client = owner || GLOB.directory[target] + + if (!isnull(client)) + disassociate() + add_verb(client, /client/proc/readmin) + //client.disable_combo_hud() + //client.update_special_keybinds() + +/datum/admins/proc/associate(client/client) + if(IsAdminAdvancedProcCall()) + alert_to_permissions_elevation_attempt(usr) + return + + if(!istype(client)) + return + + if(client?.ckey != target) + var/msg = " has attempted to associate with [target]'s admin datum" + message_admins("[key_name_admin(client)][msg]") + log_admin("[key_name(client)][msg]") + return + + if (deadmined) + activate() + + owner = client + owner.holder = src + owner.add_admin_verbs() + remove_verb(owner, /client/proc/readmin) + owner.init_verbs() //re-initialize the verb list + //owner.update_special_keybinds() + GLOB.admins |= client + + try_give_profiling() /datum/admins/proc/disassociate() + if(IsAdminAdvancedProcCall()) + alert_to_permissions_elevation_attempt(usr) + return if(owner) GLOB.admins -= owner owner.remove_admin_verbs() - owner.init_verbs() //re-initialize the verb list - owner.deadmin_holder = owner.holder + // owner.init_verbs() //re-initialize the verb list owner.holder = null + owner = null -/datum/admins/proc/reassociate() - if(owner) - GLOB.admins += owner - owner.holder = src - owner.deadmin_holder = null - owner.add_admin_verbs() +/// Returns the feedback forum thread for the admin holder's owner, as according to DB. +/datum/admins/proc/feedback_link() + // This intentionally does not follow the 10-second maximum TTL rule, + // as this can be reloaded through the Reload-Admins verb. + if (cached_feedback_link == NO_FEEDBACK_LINK) + return null + + if (!isnull(cached_feedback_link)) + return cached_feedback_link + + if (!SSdbcore.IsConnected()) + return FALSE + + var/datum/db_query/feedback_query = SSdbcore.NewQuery("SELECT feedback FROM [format_table_name("admin")] WHERE ckey = '[owner.ckey]'") + + if(!feedback_query.Execute()) + log_sql("Error retrieving feedback link for [src]") + qdel(feedback_query) + return FALSE + + if(!feedback_query.NextRow()) + qdel(feedback_query) + return FALSE // no feedback link exists + + cached_feedback_link = feedback_query.item[1] || NO_FEEDBACK_LINK + qdel(feedback_query) + + if (cached_feedback_link == NO_FEEDBACK_LINK) // Because we don't want to send fake clickable links. + return null + + return cached_feedback_link + +/datum/admins/proc/check_for_rights(rights_required) + if(rights_required && !(rights_required & rank_flags())) + return FALSE + return TRUE + +/datum/admins/proc/check_if_greater_rights_than_holder(datum/admins/other) + if(!other) + return TRUE //they have no rights + if(rank_flags() == R_EVERYTHING) + return TRUE //we have all the rights + if(src == other) + return TRUE //you always have more rights than yourself + if(rank_flags() != other.rank_flags()) + if( (rank_flags() & other.rank_flags()) == other.rank_flags() ) + return TRUE //we have all the rights they have and more + return FALSE + +/// Get the rank name of the admin +/datum/admins/proc/rank_names() + return join_admin_ranks(ranks) + +/// Get the rank flags of the admin +/datum/admins/proc/rank_flags() + var/combined_flags = NONE + + for (var/datum/admin_rank/rank as anything in ranks) + combined_flags |= rank.rights + + return combined_flags + +/// Get the permissions this admin is allowed to edit on other ranks +/datum/admins/proc/can_edit_rights_flags() + var/combined_flags = NONE + + for (var/datum/admin_rank/rank as anything in ranks) + combined_flags |= rank.can_edit_rights + + return combined_flags + +/datum/admins/proc/try_give_profiling() + if (CONFIG_GET(flag/forbid_admin_profiling)) + return + + if (given_profiling) + return + + if (!(rank_flags() & R_DEBUG)) + return + + given_profiling = TRUE + world.SetConfig("APP/admin", owner.ckey, "role=admin") /datum/admins/vv_edit_var(var_name, var_value) - if(var_name == NAMEOF(src, rights) || var_name == NAMEOF(src, owner) || var_name == NAMEOF(src, rank)) - return FALSE - return ..() - -//TODO: Proccall guard, when all try/catch are removed and WrapAdminProccall is ported. + return FALSE //nice try trialmin /* checks if usr is an admin with at least ONE of the flags in rights_required. (Note, they don't need all the flags) @@ -69,45 +219,36 @@ if it doesn't return 1 and show_msg=1 it will prints a message explaining why th generally it would be used like so: /proc/admin_proc() - if(!check_rights(R_ADMIN)) return - to_world("you have enough rights!") + if(!check_rights(R_ADMIN)) + return + to_chat(world, "you have enough rights!", confidential = TRUE) -NOTE: It checks usr by default. Supply the "user" argument if you wish to check for a specific mob. +NOTE: it checks usr! not src! So if you're checking somebody's rank in a proc which they did not call +you will have to do something like if(client.rights & R_ADMIN) yourself. */ -/proc/check_rights(rights_required, show_msg=1, var/client/C = usr) - if(ismob(C)) - var/mob/M = C - C = M.client - if(!C) - return FALSE - if(!(istype(C, /client))) // If we still didn't find a client, something is wrong. - return FALSE - if(!C.holder) - if(show_msg) - to_chat(C, span_filter_adminlog(span_warning("Error: You are not an admin."))) - return FALSE - - if(rights_required) - if(rights_required & C.holder.rights) +/proc/check_rights(rights_required, show_msg=1) + if(usr?.client) + if (check_rights_for(usr.client, rights_required)) return TRUE else if(show_msg) - to_chat(C, span_filter_adminlog(span_warning("Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")]."))) - return FALSE - else - return TRUE + to_chat(usr, "Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")].", confidential = TRUE) + return FALSE //probably a bit iffy - will hopefully figure out a better solution /proc/check_if_greater_rights_than(client/other) - if(usr && usr.client) + if(usr?.client) if(usr.client.holder) if(!other || !other.holder) - return 1 - if(usr.client.holder.rights != other.holder.rights) - if( (usr.client.holder.rights & other.holder.rights) == other.holder.rights ) - return 1 //we have all the rights they have and more - to_chat(usr, span_filter_adminlog(span_warning("Error: Cannot proceed. They have more or equal rights to us."))) - return 0 + return TRUE + return usr.client.holder.check_if_greater_rights_than_holder(other.holder) + return FALSE + +//This proc checks whether subject has at least ONE of the rights specified in rights_required. +/proc/check_rights_for(client/subject, rights_required) + if(subject?.holder) + return subject.holder.check_for_rights(rights_required) + return FALSE /client/proc/mark_datum(datum/D) if(!holder) @@ -122,20 +263,6 @@ NOTE: It checks usr by default. Supply the "user" argument if you wish to check set name = "Mark Object" mark_datum(D) -/client/proc/deadmin() - if(holder) - holder.disassociate() - //qdel(holder) - return 1 - -//This proc checks whether subject has at least ONE of the rights specified in rights_required. -/proc/check_rights_for(client/subject, rights_required) - if(subject && subject.holder) - if(rights_required && !(rights_required & subject.holder.rights)) - return 0 - return 1 - return 0 - /proc/GenerateToken() . = "" for(var/I in 1 to 32) diff --git a/code/modules/admin/permissionedit.dm b/code/modules/admin/permissionedit.dm new file mode 100644 index 0000000000..fc7feb429c --- /dev/null +++ b/code/modules/admin/permissionedit.dm @@ -0,0 +1,561 @@ +/client/proc/edit_admin_permissions() + set category = "Admin.Secrets" + set name = "Permissions Panel" + set desc = "Edit admin permissions" + + if(!check_rights(R_PERMISSIONS)) + return + + usr.client.holder.edit_admin_permissions() + +/datum/admins/proc/edit_admin_permissions(action, target, operation, 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("\[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) + ) + 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++ + output += "|" + var/datum/db_query/query_search_admin_logs = SSdbcore.NewQuery({" + SELECT + datetime, + round_id, + IFNULL((SELECT ckey FROM [format_table_name("erro_player")] WHERE ckey = adminckey), adminckey), + operation, + IF(ckey IS NULL, target, ckey), + 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) + ORDER BY datetime DESC + LIMIT :skip, :take + "}, list("target" = target, "operation" = operation, "skip" = logssperpage * page, "take" = logssperpage)) + if(!query_search_admin_logs.warn_execute()) + qdel(query_search_admin_logs) + return + 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/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) + output += {" + + + 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\]" + + 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 + usr << browse("[jointext(output, "")]","window=editrights;size=1000x650") + +/datum/admins/proc/edit_rights_topic(list/href_list) + if(!check_rights(R_PERMISSIONS)) + message_admins("[key_name_admin(usr)] attempted to edit admin permissions without sufficient rights.") + log_admin("[key_name(usr)] attempted to edit admin permissions without sufficient rights.") + return + if(IsAdminAdvancedProcCall()) + to_chat(usr, "Admin Edit blocked: Advanced ProcCall detected.", confidential = TRUE) + return + var/datum/asset/permissions_assets = get_asset_datum(/datum/asset/simple/namespaced/common) + permissions_assets.send(usr.client) + var/admin_key = href_list["key"] + var/admin_ckey = ckey(admin_key) + + var/task = href_list["editrights"] + var/datum/admins/target_admin_datum = GLOB.admin_datums[admin_ckey] + if(!target_admin_datum) + target_admin_datum = GLOB.deadmins[admin_ckey] + if (!target_admin_datum && task != "add") + return + var/use_db + var/skip + var/legacy_only + if(task == "activate" || task == "deactivate" || task == "sync" || task == "verify") + skip = TRUE + if(!CONFIG_GET(flag/admin_legacy_system) && CONFIG_GET(flag/protect_legacy_admins) && task == "rank") + if(admin_ckey in GLOB.protected_admins) + to_chat(usr, "Editing the rank of this admin is blocked by server configuration.", confidential = TRUE) + return + if(!CONFIG_GET(flag/admin_legacy_system) && CONFIG_GET(flag/protect_legacy_ranks) && task == "permissions") + if((target_admin_datum.ranks & GLOB.protected_ranks).len > 0) + to_chat(usr, "Editing the flags of this rank is blocked by server configuration.", confidential = TRUE) + return + if(CONFIG_GET(flag/load_legacy_ranks_only) && (task == "add" || task == "rank" || task == "permissions")) + to_chat(usr, "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(!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") + return + if(use_db == "Permanent") + use_db = TRUE + else + use_db = FALSE + if(QDELETED(usr)) + return + + if(target_admin_datum && (task != "sync" && task != "verify") && !check_if_greater_rights_than_holder(target_admin_datum)) + message_admins("[key_name_admin(usr)] attempted to change the rank of [admin_key] without sufficient rights.") + log_admin("[key_name(usr)] attempted to change the rank of [admin_key] without sufficient rights.") + return + switch(task) + if("add") + admin_ckey = add_admin(admin_ckey, admin_key, use_db) + if(!admin_ckey) + return + + if(!admin_key) // Prevents failures in logging admin rank changes. + admin_key = admin_ckey + + change_admin_rank(admin_ckey, admin_key, use_db, null, legacy_only) + if("remove") + remove_admin(admin_ckey, admin_key, use_db, target_admin_datum) + if("rank") + change_admin_rank(admin_ckey, admin_key, use_db, target_admin_datum, legacy_only) + if("permissions") + change_admin_flags(admin_ckey, admin_key, target_admin_datum) + if("activate") + force_readmin(admin_key, target_admin_datum) + if("deactivate") + force_deadmin(admin_key, target_admin_datum) + if("sync") + sync_lastadminrank(admin_ckey, admin_key, target_admin_datum) + /* + if("verify") + var/msg = "has authenticated [admin_ckey]" + message_admins("[key_name_admin(usr)] [msg]") + log_admin("[key_name(usr)] [msg]") + + target_admin_datum.bypass_2fa = TRUE + target_admin_datum.associate(GLOB.directory[admin_ckey]) + */ + edit_admin_permissions() + +/datum/admins/proc/add_admin(admin_ckey, admin_key, use_db) + if(admin_ckey) + . = admin_ckey + else + admin_key = input("New admin's key","Admin key") as text|null + . = ckey(admin_key) + if(!.) + return FALSE + if(!admin_ckey && (. in (GLOB.admin_datums+GLOB.deadmins))) + to_chat(usr, span_danger("[admin_key] is already an admin."), confidential = TRUE) + return FALSE + if(use_db) + //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 + 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 + 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 + qdel(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) + message_admins(m1) + log_admin(m2) + +/datum/admins/proc/force_readmin(admin_key, datum/admins/D) + if(!D || !D.deadmined) + return + D.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) + 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. + +#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)) + return + + 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 + + var/list/new_rank_names = list() + var/list/custom_ranks = list() + + while (TRUE) + var/list/display_rank_names = list(RANK_DONE) + + if (new_rank_names.len > 0) + display_rank_names += "** SELECTED **" + for (var/rank_name in new_rank_names) + display_rank_names += rank_name + display_rank_names += "---------" + + for (var/rank_name in rank_names) + if (!(rank_name in display_rank_names)) + display_rank_names += rank_name + + var/next_rank = input("Please select a rank, or select [RANK_DONE] if you are finished.") as null|anything in display_rank_names + + if (isnull(next_rank)) + return + + if (next_rank == RANK_DONE) + break + + // They clicked "** SELECTED **" or something silly. + if (!(next_rank in rank_names)) + continue + + if (next_rank in new_rank_names) + new_rank_names -= next_rank + continue + + if (next_rank == "*New Rank*") + var/new_rank_name = input("Please input a new rank", "New custom rank") as text|null + if (!new_rank_name) + return + + var/datum/admin_rank/custom_rank = rank_names[new_rank_name] + if (isnull(custom_rank)) + if (D) + custom_rank = new(new_rank_name, D.rank_flags()) + else + custom_rank = new(new_rank_name) + + GLOB.admin_ranks += custom_rank + custom_ranks += custom_rank + new_rank_names += new_rank_name + + new_rank_names += next_rank + + var/list/new_ranks = list() + for (var/datum/admin_rank/admin_rank as anything in GLOB.admin_ranks) + if (admin_rank.name in new_rank_names) + new_ranks += admin_rank + new_rank_names -= admin_rank.name + + if (new_rank_names.len == 0) + break + + var/joined_rank = join_admin_ranks(new_ranks) + var/m1 = "[key_name_admin(usr)] edited the admin rank of [admin_key] to [joined_rank] [use_db ? "permanently" : "temporarily"]" + var/m2 = "[key_name(usr)] edited the admin rank of [admin_key] to [joined_rank] [use_db ? "permanently" : "temporarily"]" + if(use_db) + //if a player was tempminned before having a permanent change made to their rank they won't yet be in the db + var/old_rank + var/datum/db_query/query_admin_in_db = SSdbcore.NewQuery( + "SELECT `rank` FROM [format_table_name("admin")] WHERE ckey = :admin_ckey", + list("admin_ckey" = admin_ckey) + ) + if(!query_admin_in_db.warn_execute()) + qdel(query_admin_in_db) + return + if(!query_admin_in_db.NextRow()) + add_admin(admin_ckey, admin_key, TRUE) + old_rank = "NEW ADMIN" + else + old_rank = query_admin_in_db.item[1] + qdel(query_admin_in_db) + + for (var/datum/admin_rank/custom_rank in custom_ranks) + //similarly if a temp rank is created it won't be in the db if someone is permanently changed to it + var/datum/db_query/query_rank_in_db = SSdbcore.NewQuery( + "SELECT 1 FROM [format_table_name("admin_ranks")] WHERE `rank` = :new_rank", + list("new_rank" = custom_rank.name) + ) + if(!query_rank_in_db.warn_execute()) + qdel(query_rank_in_db) + return + if(!query_rank_in_db.NextRow()) + QDEL_NULL(query_rank_in_db) + var/datum/db_query/query_add_rank = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("admin_ranks")] (`rank`, flags, exclude_flags, can_edit_flags) + VALUES (:new_rank, '0', '0', '0') + "}, list("new_rank" = custom_rank.name)) + if(!query_add_rank.warn_execute()) + qdel(query_add_rank) + return + qdel(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 + qdel(query_add_rank_log) + qdel(query_rank_in_db) + var/datum/db_query/query_change_rank = SSdbcore.NewQuery( + "UPDATE [format_table_name("admin")] SET `rank` = :new_rank WHERE ckey = :admin_ckey", + list("new_rank" = joined_rank, "admin_ckey" = admin_ckey) + ) + if(!query_change_rank.warn_execute()) + qdel(query_change_rank) + return + qdel(query_change_rank) + 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)) + "}, 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) + 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) + +#undef RANK_DONE + +/datum/admins/proc/change_admin_flags(admin_ckey, admin_key, datum/admins/admin_holder) + var/new_flags = input_bitfield( + usr, + "Admin rights
This will affect only the current admin [admin_key]", + "admin_flags", + admin_holder.rank_flags(), + 350, + 590, + allowed_edit_list = usr.client.holder.can_edit_rights_flags(), + ) + + admin_holder.disassociate() + + if (findtext(admin_holder.rank_names(), "([admin_ckey])")) + var/datum/admin_rank/rank = admin_holder.ranks[1] + rank.rights = new_flags + rank.include_rights = new_flags + rank.exclude_rights = NONE + rank.can_edit_rights = rank.can_edit_rights + else + // Not a modified subrank, need to duplicate the admin_rank datum to prevent modifying others too. + var/datum/admin_rank/new_admin_rank = new( + /* init_name = */ "[admin_holder.rank_names()]([admin_ckey])", + /* init_rights = */ new_flags, + + // rank_flags() includes the exclude rights, so we no longer need to handle them separately. + /* init_exclude_rights = */ NONE, + + /* init_edit_rights = */ admin_holder.can_edit_rights_flags(), + ) + + admin_holder.ranks = list(new_admin_rank) + + var/log = "[key_name(usr)] has updated the admin rights of [admin_ckey] into [rights2text(new_flags)]" + message_admins(log) + log_admin(log) + + var/client/admin_client = GLOB.directory[admin_ckey] + admin_holder.associate(admin_client) + +/datum/admins/proc/remove_rank(admin_rank) + if(!admin_rank) + return + for(var/datum/admin_rank/R in GLOB.admin_ranks) + if(R.name == admin_rank && (!(R.rights & usr.client.holder.can_edit_rights_flags()) == R.rights)) + to_chat(usr, "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, "Deletion of protected ranks is not permitted, it must be removed from admin_ranks.txt.", confidential = TRUE) + return + if(CONFIG_GET(flag/load_legacy_ranks_only)) + to_chat(usr, "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", + list("admin_rank" = admin_rank) + ) + 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 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) + return + qdel(query_add_rank_log) + message_admins(m1) + log_admin(m2) + +/datum/admins/proc/sync_lastadminrank(admin_ckey, admin_key, datum/admins/D) + var/sqlrank = "Player" + if (D) + sqlrank = D.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) + ) + if(!query_sync_lastadminrank.warn_execute()) + qdel(query_sync_lastadminrank) + return + qdel(query_sync_lastadminrank) + to_chat(usr, span_admin("Sync of [admin_key] successful."), confidential = TRUE) diff --git a/code/modules/admin/permissionverbs/permissionedit.dm b/code/modules/admin/permissionverbs/permissionedit.dm deleted file mode 100644 index ba0f4636a5..0000000000 --- a/code/modules/admin/permissionverbs/permissionedit.dm +++ /dev/null @@ -1,158 +0,0 @@ -/client/proc/edit_admin_permissions() - set category = "Admin.Secrets" - set name = "Permissions Panel" - set desc = "Edit admin permissions" - if(!check_rights(R_PERMISSIONS)) return - usr.client.holder.edit_admin_permissions() - -/datum/admins/proc/edit_admin_permissions() - if(!check_rights(R_PERMISSIONS)) return - - var/output = {" - - -Permissions Panel - - - - -
- - - - -"} - - for(var/adm_ckey in admin_datums) - var/datum/admins/D = admin_datums[adm_ckey] - if(!D) continue - var/rank = D.rank ? D.rank : "*none*" - var/rights = rights2text(D.rights," ") - if(!rights) rights = "*none*" - - output += "" - output += "" - output += "" - output += "" - output += "" - - output += {" -
CKEY \[+\]RANKPERMISSIONS
[adm_ckey] \[-\][rank][rights]
-
Search:
- -"} - - usr << browse(output,"window=editrights;size=600x500") - -/datum/admins/proc/log_admin_rank_modification(var/adm_ckey, var/new_rank) - if(CONFIG_GET(flag/admin_legacy_system)) return - - if(!usr.client) - return - - if(!usr.client.holder || !(usr.client.holder.rights & R_PERMISSIONS)) - to_chat(usr, span_filter_adminlog("[span_red("You do not have permission to do this!")]")) - return - - establish_db_connection() - - if(!SSdbcore.IsConnected()) - to_chat(usr, span_filter_adminlog("[span_red("Failed to establish database connection")]")) - return - - if(!adm_ckey || !new_rank) - return - - adm_ckey = ckey(adm_ckey) - - if(!adm_ckey) - return - - if(!istext(adm_ckey) || !istext(new_rank)) - return - - var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT id FROM erro_admin WHERE ckey = '[adm_ckey]'") - select_query.Execute() - - var/new_admin = 1 - var/admin_id - while(select_query.NextRow()) - new_admin = 0 - admin_id = text2num(select_query.item[1]) - - qdel(select_query) - if(new_admin) - var/datum/db_query/insert_query = SSdbcore.NewQuery("INSERT INTO `erro_admin` (`id`, `ckey`, `rank`, `level`, `flags`) VALUES (null, '[adm_ckey]', '[new_rank]', -1, 0)") - insert_query.Execute() - qdel(insert_query) - var/datum/db_query/log_query = SSdbcore.NewQuery("INSERT INTO `test`.`erro_admin_log` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Added new admin [adm_ckey] to rank [new_rank]');") - log_query.Execute() - qdel(log_query) - to_chat(usr, span_filter_adminlog("[span_blue("New admin added.")]")) - else - if(!isnull(admin_id) && isnum(admin_id)) - var/datum/db_query/insert_query = SSdbcore.NewQuery("UPDATE `erro_admin` SET rank = '[new_rank]' WHERE id = [admin_id]") - insert_query.Execute() - qdel(insert_query) - var/datum/db_query/log_query = SSdbcore.NewQuery("INSERT INTO `test`.`erro_admin_log` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Edited the rank of [adm_ckey] to [new_rank]');") - log_query.Execute() - qdel(log_query) - to_chat(usr, span_filter_adminlog("[span_blue("Admin rank changed.")]")) - -/datum/admins/proc/log_admin_permission_modification(var/adm_ckey, var/new_permission) - if(CONFIG_GET(flag/admin_legacy_system)) return - - if(!usr.client) - return - - if(!usr.client.holder || !(usr.client.holder.rights & R_PERMISSIONS)) - to_chat(usr, span_filter_adminlog("[span_red(">You do not have permission to do this!")]")) - return - - establish_db_connection() - if(!SSdbcore.IsConnected()) - to_chat(usr, span_filter_adminlog("[span_red("Failed to establish database connection!")]")) - return - - if(!adm_ckey || !new_permission) - return - - adm_ckey = ckey(adm_ckey) - - if(!adm_ckey) - return - - if(istext(new_permission)) - new_permission = text2num(new_permission) - - if(!istext(adm_ckey) || !isnum(new_permission)) - return - - var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT id, flags FROM erro_admin WHERE ckey = '[adm_ckey]'") - select_query.Execute() - - var/admin_id - var/admin_rights - while(select_query.NextRow()) - admin_id = text2num(select_query.item[1]) - admin_rights = text2num(select_query.item[2]) - qdel(select_query) - if(!admin_id) - return - - if(admin_rights & new_permission) //This admin already has this permission, so we are removing it. - var/datum/db_query/insert_query = SSdbcore.NewQuery("UPDATE `erro_admin` SET flags = [admin_rights & ~new_permission] WHERE id = [admin_id]") - insert_query.Execute() - qdel(insert_query) - var/datum/db_query/log_query = SSdbcore.NewQuery("INSERT INTO `test`.`erro_admin_log` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Removed permission [rights2text(new_permission)] (flag = [new_permission]) to admin [adm_ckey]');") - log_query.Execute() - qdel(log_query) - to_chat(usr, span_filter_adminlog("[span_blue("Permission removed.")]")) - else //This admin doesn't have this permission, so we are adding it. - var/datum/db_query/insert_query = SSdbcore.NewQuery("UPDATE `erro_admin` SET flags = '[admin_rights | new_permission]' WHERE id = [admin_id]") - insert_query.Execute() - qdel(insert_query) - var/datum/db_query/log_query = SSdbcore.NewQuery("INSERT INTO `test`.`erro_admin_log` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Added permission [rights2text(new_permission)] (flag = [new_permission]) to admin [adm_ckey]')") - log_query.Execute() - qdel(log_query) - to_chat(usr, span_filter_adminlog("[span_blue("Permission added.")]")) diff --git a/code/modules/admin/player_notes.dm b/code/modules/admin/player_notes.dm index 53479bdcad..3074026aca 100644 --- a/code/modules/admin/player_notes.dm +++ b/code/modules/admin/player_notes.dm @@ -27,7 +27,7 @@ var/datum/player_info/P = new if (user) P.author = user.key - P.rank = user.client.holder.rank + P.rank = user.client.holder.rank_names() else P.author = "Adminbot" P.rank = "Friendly Robot" diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 1ef0c4981d..ad982d5983 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -129,99 +129,23 @@ if((bantype == BANTYPE_PERMA || bantype == BANTYPE_TEMP) && playermob.client) qdel(playermob.client) + else if(href_list["editrightsbrowser"]) + edit_admin_permissions(0) + + else if(href_list["editrightsbrowserlog"]) + edit_admin_permissions(1, href_list["editrightstarget"], href_list["editrightsoperation"], href_list["editrightspage"]) + + if(href_list["editrightsbrowsermanage"]) + 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) + else if(href_list["editrights"]) - if(!check_rights(R_PERMISSIONS)) - message_admins("[key_name_admin(usr)] attempted to edit the admin permissions without sufficient rights.") - log_admin("[key_name(usr)] attempted to edit the admin permissions without sufficient rights.") - return - - var/adm_ckey - - var/task = href_list["editrights"] - if(task == "add") - var/new_ckey = ckey(tgui_input_text(usr,"New admin's ckey","Admin ckey", null)) - if(!new_ckey) return - if(new_ckey in admin_datums) - to_chat(usr, span_filter_adminlog(span_warning("Error: Topic 'editrights': [new_ckey] is already an admin"))) - return - adm_ckey = new_ckey - task = "rank" - else if(task != "show") - adm_ckey = ckey(href_list["ckey"]) - if(!adm_ckey) - to_chat(usr, span_filter_adminlog(span_warning("Error: Topic 'editrights': No valid ckey"))) - return - - var/datum/admins/D = admin_datums[adm_ckey] - - if(task == "remove") - if(tgui_alert(usr, "Are you sure you want to remove [adm_ckey]?","Message",list("Yes","Cancel")) == "Yes") - if(!D) return - admin_datums -= adm_ckey - D.disassociate() - - message_admins("[key_name_admin(usr)] removed [adm_ckey] from the admins list") - log_admin("[key_name(usr)] removed [adm_ckey] from the admins list") - log_admin_rank_modification(adm_ckey, "Removed") - - else if(task == "rank") - var/new_rank - if(admin_ranks.len) - new_rank = tgui_input_list(usr, "Please select a rank", "New rank", (admin_ranks|"*New Rank*")) - else - new_rank = tgui_input_list(usr, "Please select a rank", "New rank", list("Game Master","Head Admin","Game Admin", "Trial Admin", "Admin Observer","Moderator","Mentor","Badmin","Retired Admin","Event Manager","Developer","DevMod","*New Rank*")) //CHOMP Edit bandaid fix to assigning titles because we're having some funky database issues, I think. Other option is to manually edit database entry for someone's title. - - var/rights = 0 - if(D) - rights = D.rights - switch(new_rank) - if(null,"") return - if("*New Rank*") - new_rank = tgui_input_text(usr, "Please input a new rank", "New custom rank") - if(CONFIG_GET(flag/admin_legacy_system)) - new_rank = ckeyEx(new_rank) - if(!new_rank) - to_chat(usr, span_filter_adminlog(span_warning("Error: Topic 'editrights': Invalid rank"))) - return - if(CONFIG_GET(flag/admin_legacy_system)) - if(admin_ranks.len) - if(new_rank in admin_ranks) - rights = admin_ranks[new_rank] //we typed a rank which already exists, use its rights - else - admin_ranks[new_rank] = 0 //add the new rank to admin_ranks - else - if(CONFIG_GET(flag/admin_legacy_system)) - new_rank = ckeyEx(new_rank) - rights = admin_ranks[new_rank] //we input an existing rank, use its rights - - if(D) - D.disassociate() //remove adminverbs and unlink from client - D.rank = new_rank //update the rank - D.rights = rights //update the rights based on admin_ranks (default: 0) - else - D = new /datum/admins(new_rank, rights, adm_ckey) - - var/client/C = GLOB.directory[adm_ckey] //find the client with the specified ckey (if they are logged in) - D.associate(C) //link up with the client and add verbs - - message_admins("[key_name_admin(usr)] edited the admin rank of [adm_ckey] to [new_rank]") - log_admin("[key_name(usr)] edited the admin rank of [adm_ckey] to [new_rank]") - log_admin_rank_modification(adm_ckey, new_rank) - - else if(task == "permissions") - if(!D) return - var/list/permissionlist = list() - for(var/i=1, i<=R_MAXPERMISSION, i<<=1) //that <<= is shorthand for i = i << 1. Which is a left bitshift - permissionlist[rights2text(i)] = i - var/new_permission = tgui_input_list(usr, "Select a permission to turn on/off", "Permission toggle", permissionlist) - if(!new_permission) return - D.rights ^= permissionlist[new_permission] - - message_admins("[key_name_admin(usr)] toggled the [new_permission] permission of [adm_ckey]") - log_admin("[key_name(usr)] toggled the [new_permission] permission of [adm_ckey]") - log_admin_permission_modification(adm_ckey, permissionlist[new_permission]) - - edit_admin_permissions() + edit_rights_topic(href_list) else if(href_list["call_shuttle"]) if(!check_rights(R_ADMIN|R_EVENT)) return @@ -692,7 +616,7 @@ return if(M != usr) //we can jobban ourselves - if(M.client && M.client.holder && (M.client.holder.rights & R_BAN)) //they can ban too. So we can't ban them + if(M.client && M.client.holder && (check_rights_for(M.client, R_BAN))) //they can ban too. So we can't ban them tgui_alert_async(usr, "You cannot perform this action. You must be of a higher administrative rank!") return @@ -1364,7 +1288,7 @@ if(ismob(M)) var/take_msg = span_notice("ADMINHELP: [key_name(usr.client)] is attending to [key_name(M)]'s adminhelp, please don't dogpile them.") for(var/client/X in GLOB.admins) - if((R_ADMIN|R_MOD|R_SERVER) & X.holder.rights) //VOREStation Edit + if(check_rights_for(X, (R_ADMIN|R_MOD|R_SERVER))) to_chat(X, take_msg) to_chat(M, span_filter_pm(span_boldnotice("Your adminhelp is being attended to by [usr.client]. Thanks for your patience!"))) // VoreStation Edit Start diff --git a/code/modules/admin/verb_datums/_admin_verb_datum.dm b/code/modules/admin/verb_datums/_admin_verb_datum.dm new file mode 100644 index 0000000000..6255d962c4 --- /dev/null +++ b/code/modules/admin/verb_datums/_admin_verb_datum.dm @@ -0,0 +1,27 @@ +GENERAL_PROTECT_DATUM(/datum/admin_verb) + +/** + * This is the admin verb datum. It is used to store the verb's information and handle the verb's functionality. + * All of this is setup for you, and you should not be defining this manually. + * That means you reader. + */ +/datum/admin_verb + var/name //! The name of the verb. + var/description //! The description of the verb. + var/category //! The category of the verb. + var/permissions //! The permissions required to use the verb. + var/visibility_flag //! The flag that determines if the verb is visible. + VAR_PROTECTED/verb_path //! The path to the verb proc. + +/datum/admin_verb/Destroy(force) + if(!force) + return QDEL_HINT_LETMELIVE + return ..() + +/// Assigns the verb to the admin. +/datum/admin_verb/proc/assign_to_client(client/admin) + add_verb(admin, verb_path) + +/// Unassigns the verb from the admin. +/datum/admin_verb/proc/unassign_from_client(client/admin) + remove_verb(admin, verb_path) diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index 413f94f680..933cda8fe2 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -296,7 +296,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) //send this msg to all admins for(var/client/X in GLOB.admins) -// if(!check_rights(R_ADMIN, 0, X)) //CHOMP Remove let everyone hear the ahelp +// if(!check_rights_for(X, R_ADMIN)) //CHOMP Remove let everyone hear the ahelp // continue //CHOMP Remove let everyone hear the ahelp if(X.prefs?.read_preference(/datum/preference/toggle/holder/play_adminhelp_ping)) X << 'sound/effects/adminhelp.ogg' @@ -723,7 +723,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) . = list("total" = list(), "noflags" = list(), "afk" = list(), "stealth" = list(), "present" = list()) for(var/client/X in GLOB.admins) .["total"] += X - if(requiredflags != 0 && !check_rights(rights_required = requiredflags, show_msg = FALSE, C = X)) + if(requiredflags != 0 && !check_rights_for(X, requiredflags)) .["noflags"] += X else if(X.is_afk()) .["afk"] += X diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm index 95e371bc7a..673fd91893 100644 --- a/code/modules/admin/verbs/adminpm.dm +++ b/code/modules/admin/verbs/adminpm.dm @@ -202,14 +202,14 @@ if(irc) log_admin("PM: [key_name(src)]->IRC: [rawmsg]") for(var/client/X in GLOB.admins) - if(!check_rights(R_ADMIN|R_SERVER, 0, X)) //CHOMPEdit + if(!check_rights_for(X, R_ADMIN|R_SERVER)) //CHOMPEdit continue to_chat(X, span_admin_pm_notice(span_bold("PM: [key_name(src, X, 0)]->IRC:") + " [keywordparsedmsg]")) else log_admin("PM: [key_name(src)]->[key_name(recipient)]: [rawmsg]") //we don't use message_admins here because the sender/receiver might get it too for(var/client/X in GLOB.admins) - if(!check_rights(R_ADMIN|R_SERVER, 0, X)) //CHOMPEdit + if(!check_rights_for(X, R_ADMIN|R_SERVER)) //CHOMPEdit continue if(X.key!=key && X.key!=recipient.key) //check client/X is an admin and isn't the sender or recipient to_chat(X, span_admin_pm_notice(span_bold("PM: [key_name(src, X, 0)]->[key_name(recipient, X, 0)]:") + " [keywordparsedmsg]")) diff --git a/code/modules/admin/verbs/adminsay.dm b/code/modules/admin/verbs/adminsay.dm index df6963de41..18c3f117b8 100644 --- a/code/modules/admin/verbs/adminsay.dm +++ b/code/modules/admin/verbs/adminsay.dm @@ -12,7 +12,7 @@ log_adminsay(msg,src) for(var/client/C in GLOB.admins) - if(check_rights(R_ADMIN, 0, C)) + if(check_rights_for(C, R_ADMIN)) to_chat(C, span_admin_channel(create_text_tag("admin", "ADMIN:", C) + " " + span_name("[key_name(usr, 1)]") + "([admin_jump_link(mob, src)]): " + span_name("[msg]") )) feedback_add_details("admin_verb","M") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/admin/verbs/antag-ooc.dm b/code/modules/admin/verbs/antag-ooc.dm index 0d2b6e4fe8..8309e19091 100644 --- a/code/modules/admin/verbs/antag-ooc.dm +++ b/code/modules/admin/verbs/antag-ooc.dm @@ -27,10 +27,10 @@ display_name = usr.client.holder.fakekey // Name shown to other players. Admins whom are not also antags have their rank displayed. - var/player_display = (is_admin && !is_antag) ? "[display_name]([usr.client.holder.rank])" : display_name + var/player_display = (is_admin && !is_antag) ? "[display_name]([usr.client.holder.rank_names()])" : display_name for(var/mob/M in mob_list) - if(check_rights(R_ADMIN|R_MOD|R_EVENT, 0, M)) // Staff can see AOOC unconditionally, and with more details. + if(check_rights_for(M.client, R_ADMIN|R_MOD|R_EVENT)) // Staff can see AOOC unconditionally, and with more details. to_chat(M, span_ooc(span_aooc("[create_text_tag("aooc", "Antag-OOC:", M.client)] [get_options_bar(src, 0, 1, 1)]([admin_jump_link(usr, M.client.holder)]): " + span_message("[msg]")))) else if(M.client) // Players can only see AOOC if observing, or if they are an antag type allowed to use AOOC. var/datum/antagonist/A = null diff --git a/code/modules/admin/verbs/deadsay.dm b/code/modules/admin/verbs/deadsay.dm index a46068d0b7..4a0ceb1554 100644 --- a/code/modules/admin/verbs/deadsay.dm +++ b/code/modules/admin/verbs/deadsay.dm @@ -18,7 +18,7 @@ if (src.handle_spam_prevention(msg,MUTE_DEADCHAT)) return - var/stafftype = uppertext(holder.rank) + var/stafftype = uppertext(holder.rank_names()) msg = sanitize(msg) log_admin("DSAY: [key_name(src)] : [msg]") diff --git a/code/modules/admin/verbs/pray.dm b/code/modules/admin/verbs/pray.dm index ce715b2a27..1623268036 100644 --- a/code/modules/admin/verbs/pray.dm +++ b/code/modules/admin/verbs/pray.dm @@ -16,7 +16,7 @@ var/msg = span_filter_pray(span_blue("[icon2html(cross, GLOB.admins)] " + span_purple("PRAY: ") + "[key_name(src, 1)] [ADMIN_QUE(src)] [ADMIN_PP(src)] [ADMIN_VV(src)] [ADMIN_SM(src)] ([admin_jump_link(src, src)]) [ADMIN_CA(src)] [ADMIN_SC(src)] [ADMIN_SMITE(src)]: [raw_msg]")) for(var/client/C in GLOB.admins) - if(!check_rights(R_ADMIN|R_EVENT, 0, C)) //CHOMPEdit + if(!check_rights_for(C, R_ADMIN|R_EVENT)) //CHOMPEdit continue if(C.prefs?.read_preference(/datum/preference/toggle/show_chat_prayers)) to_chat(C, msg, type = MESSAGE_TYPE_PRAYER, confidential = TRUE) @@ -29,7 +29,7 @@ /proc/CentCom_announce(var/msg, var/mob/Sender, var/iamessage) msg = span_blue(span_bold(span_orange("[uppertext(using_map.boss_short)]M[iamessage ? " IA" : ""]:") + "[key_name(Sender, 1)] [ADMIN_PP(Sender)] [ADMIN_VV(Sender)] [ADMIN_SM(Sender)] ([admin_jump_link(Sender)]) [ADMIN_CA(Sender)] [ADMIN_BSA(Sender)] [ADMIN_CENTCOM_REPLY(Sender)]:") + " [msg]") for(var/client/C in GLOB.admins) //VOREStation Edit - GLOB admins - if(!check_rights(R_ADMIN|R_EVENT, 0, C)) //CHOMPEdit + if(!check_rights_for(C, R_ADMIN|R_EVENT)) //CHOMPEdit continue to_chat(C,msg) C << 'sound/machines/signal.ogg' @@ -37,7 +37,7 @@ /proc/Syndicate_announce(var/msg, var/mob/Sender) msg = span_blue(span_bold(span_crimson("ILLEGAL:") + "[key_name(Sender, 1)] [ADMIN_PP(Sender)] [ADMIN_VV(Sender)] [ADMIN_SM(Sender)] ([admin_jump_link(Sender)]) [ADMIN_CA(Sender)] [ADMIN_BSA(Sender)] [ADMIN_SYNDICATE_REPLY(Sender)]:") + " [msg]") for(var/client/C in GLOB.admins) //VOREStation Edit - GLOB admins - if(!check_rights(R_ADMIN|R_EVENT, 0, C)) //CHOMPEdit + if(!check_rights_for(C, R_ADMIN|R_EVENT)) //CHOMPEdit continue to_chat(C,msg) C << 'sound/machines/signal.ogg' diff --git a/code/modules/admin/verbs/special_verbs.dm b/code/modules/admin/verbs/special_verbs.dm new file mode 100644 index 0000000000..4dd67e052d --- /dev/null +++ b/code/modules/admin/verbs/special_verbs.dm @@ -0,0 +1,30 @@ +// Admin Verbs in this file are special and cannot use the AVD system for some reason or another. + +/client/proc/readmin() + set name = "Readmin" + set category = "Admin.Misc" + set desc = "Regain your admin powers." + + var/datum/admins/A = GLOB.deadmins[ckey] + + if(!A) + A = GLOB.admin_datums[ckey] + if (!A) + var/msg = " is trying to readmin but they have no deadmin entry" + message_admins("[key_name_admin(src)][msg]") + log_admin_private("[key_name(src)][msg]") + return + + A.associate(src) + + if(!holder) + return //This can happen if an admin attempts to vv themself into somebody elses's deadmin datum by getting ref via brute force + + to_chat(src, span_interface("You are now an admin."), confidential = TRUE) + message_admins("[src] re-adminned themselves.") + log_admin("[src] re-adminned themselves.") + //BLACKBOX_LOG_ADMIN_VERB("Readmin") + + if(isobserver(mob)) + var/mob/observer/dead/our_mob = mob + our_mob.visualnet?.addVisibility(our_mob, src) diff --git a/code/modules/admin/view_variables/helpers.dm b/code/modules/admin/view_variables/helpers.dm index 7577487713..286017520a 100644 --- a/code/modules/admin/view_variables/helpers.dm +++ b/code/modules/admin/view_variables/helpers.dm @@ -172,21 +172,24 @@ /client/VV_ckey_edit() return list("key", "ckey") -/datum/proc/may_edit_var(var/user, var/var_to_edit) +/datum/proc/may_edit_var(var/user, var/var_to_edit) //User must be a CLIENT that is passed to this. if(!user) return FALSE + if(ismob(user)) //Failsafe catch in case someone feeds a mob into us. + var/mob/living = user + user = living.client if(!(var_to_edit in vars)) to_chat(user, span_warning("\The [src] does not have a var '[var_to_edit]'")) return FALSE if(var_to_edit in VV_static()) return FALSE - if((var_to_edit in VV_secluded()) && !check_rights(R_ADMIN|R_DEBUG, FALSE, C = user)) + if((var_to_edit in VV_secluded()) && !check_rights_for(user, R_ADMIN|R_DEBUG)) return FALSE - if((var_to_edit in VV_locked()) && !check_rights(R_DEBUG, C = user)) + if((var_to_edit in VV_locked()) && !check_rights_for(user, R_DEBUG)) return FALSE - if((var_to_edit in VV_ckey_edit()) && !check_rights(R_SPAWN|R_DEBUG, C = user)) + if((var_to_edit in VV_ckey_edit()) && !check_rights_for(user, R_SPAWN|R_DEBUG)) return FALSE - if((var_to_edit in VV_icon_edit_lock()) && !check_rights(R_FUN|R_DEBUG, C = user)) + if((var_to_edit in VV_icon_edit_lock()) && !check_rights_for(user, R_FUN|R_DEBUG)) return FALSE return TRUE diff --git a/code/modules/asset_cache/assets/browser.dm b/code/modules/asset_cache/assets/browser.dm index 86c408c5ab..82f2c1c1ac 100644 --- a/code/modules/asset_cache/assets/browser.dm +++ b/code/modules/asset_cache/assets/browser.dm @@ -1,7 +1,5 @@ /datum/asset/simple/generic assets = list( - "search.js" = 'html/search.js', - "panels.css" = 'html/panels.css', "loading.gif" = 'html/images/loading.gif', "ntlogo.png" = 'html/images/ntlogo.png', "sglogo.png" = 'html/images/sglogo.png', diff --git a/code/modules/asset_cache/assets/common.dm b/code/modules/asset_cache/assets/common.dm new file mode 100644 index 0000000000..931b4999b4 --- /dev/null +++ b/code/modules/asset_cache/assets/common.dm @@ -0,0 +1,3 @@ +/datum/asset/simple/namespaced/common + assets = list("padlock.png" = 'icons/ui/common/padlock.png') + parents = list("common.css" = 'html/browser/common.css') diff --git a/code/modules/asset_cache/assets/permissions.dm b/code/modules/asset_cache/assets/permissions.dm new file mode 100644 index 0000000000..6cc6c0e754 --- /dev/null +++ b/code/modules/asset_cache/assets/permissions.dm @@ -0,0 +1,11 @@ +/datum/asset/simple/permissions + assets = list( + "search.js" = 'html/admin/search.js', + "panels.css" = 'html/admin/panels.css' + ) + +/datum/asset/group/permissions + children = list( + /datum/asset/simple/permissions, + /datum/asset/simple/namespaced/common + ) diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm index 7963516df7..7f304457b4 100644 --- a/code/modules/client/client defines.dm +++ b/code/modules/client/client defines.dm @@ -40,7 +40,6 @@ show_verb_panel = FALSE ///Contains admin info. Null if client is not an admin. var/datum/admins/holder = null - var/datum/admins/deadmin_holder = null var/buildmode = 0 ///Contains the last message sent by this client - used to protect against copy-paste spamming. diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 9f73e5b987..62cecb8f16 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -262,7 +262,7 @@ GLOB.tickets.ClientLogin(src) // CHOMPedit - Tickets System //Admin Authorisation - holder = admin_datums[ckey] + holder = GLOB.admin_datums[ckey] if(holder) GLOB.admins += src holder.owner = src @@ -465,7 +465,7 @@ var/admin_rank = "Player" if(src.holder) - admin_rank = src.holder.rank + admin_rank = src.holder.rank_names() var/sql_ip = sql_sanitize_text(src.address) var/sql_computerid = sql_sanitize_text(src.computer_id) @@ -475,7 +475,7 @@ //Panic bunker code if (isnum(player_age) && player_age == 0) //first connection - if (CONFIG_GET(flag/panic_bunker) && !holder && !deadmin_holder) + if (CONFIG_GET(flag/panic_bunker) && !holder && !GLOB.deadmins[key]) log_adminwarn("Failed Login: [key] - New account attempting to connect during panic bunker") message_admins(span_adminnotice("Failed Login: [key] - New account attempting to connect during panic bunker")) disconnect_with_message("Sorry but the server is currently not accepting connections from never before seen players.") diff --git a/code/modules/client/preference_setup/general/03_body.dm b/code/modules/client/preference_setup/general/03_body.dm index 80f38d4473..12e7b527be 100644 --- a/code/modules/client/preference_setup/general/03_body.dm +++ b/code/modules/client/preference_setup/general/03_body.dm @@ -60,7 +60,7 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O continue if(instance.ckeys_allowed && (!client || !(client.ckey in instance.ckeys_allowed))) continue - if(instance.species_allowed && (!species || !(species in instance.species_allowed)) && (!client || !check_rights(R_ADMIN | R_EVENT | R_FUN, 0, client)) && (!custom_base || !(custom_base in instance.species_allowed))) + if(instance.species_allowed && (!species || !(species in instance.species_allowed)) && (!client || !check_rights_for(client, R_ADMIN | R_EVENT | R_FUN)) && (!custom_base || !(custom_base in instance.species_allowed))) continue .[instance.name] = instance @@ -539,7 +539,7 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O else return TOPIC_NOACTION - if(((!(setting_species.spawn_flags & SPECIES_CAN_JOIN)) || (!is_alien_whitelisted(preference_mob(),setting_species))) && !check_rights(R_ADMIN|R_EVENT, 0) && !(setting_species.spawn_flags & SPECIES_WHITELIST_SELECTABLE)) + if(((!(setting_species.spawn_flags & SPECIES_CAN_JOIN)) || (!is_alien_whitelisted(user.client,setting_species))) && !check_rights(R_ADMIN|R_EVENT, 0) && !(setting_species.spawn_flags & SPECIES_WHITELIST_SELECTABLE)) return TOPIC_NOACTION var/prev_species = pref.species @@ -1237,7 +1237,7 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O if(!(current_species.spawn_flags & SPECIES_CAN_JOIN)) restricted = 2 - else if(!is_alien_whitelisted(preference_mob(),current_species)) + else if(!is_alien_whitelisted(user.client,current_species)) restricted = 1 if(restricted) diff --git a/code/modules/client/preference_setup/loadout/loadout.dm b/code/modules/client/preference_setup/loadout/loadout.dm index 246857c897..185fd2bbe4 100644 --- a/code/modules/client/preference_setup/loadout/loadout.dm +++ b/code/modules/client/preference_setup/loadout/loadout.dm @@ -83,7 +83,7 @@ var/list/gear_datums = list() if(G.whitelisted && CONFIG_GET(flag/loadout_whitelist) != LOADOUT_WHITELIST_OFF && pref.client) //VOREStation Edit. if(CONFIG_GET(flag/loadout_whitelist) == LOADOUT_WHITELIST_STRICT && G.whitelisted != pref.species) continue - if(CONFIG_GET(flag/loadout_whitelist) == LOADOUT_WHITELIST_LAX && !is_alien_whitelisted(preference_mob(), GLOB.all_species[G.whitelisted])) + if(CONFIG_GET(flag/loadout_whitelist) == LOADOUT_WHITELIST_LAX && !is_alien_whitelisted(preference_mob.client, GLOB.all_species[G.whitelisted])) continue if(max_cost && G.cost > max_cost) diff --git a/code/modules/client/preferences/types/game/admin.dm b/code/modules/client/preferences/types/game/admin.dm index de30ef2cc3..b90b2f7197 100644 --- a/code/modules/client/preferences/types/game/admin.dm +++ b/code/modules/client/preferences/types/game/admin.dm @@ -9,7 +9,7 @@ if(!.) return - return check_rights(R_MOD|R_ADMIN, FALSE, preferences.client) + return check_rights_for(preferences.client, R_MOD|R_ADMIN) /datum/preference/toggle/show_debug_logs category = PREFERENCE_CATEGORY_GAME_PREFERENCES @@ -22,7 +22,7 @@ if(!.) return - return check_rights(R_DEBUG|R_ADMIN, FALSE, preferences.client) + return check_rights_for(preferences.client, R_DEBUG|R_ADMIN) /datum/preference/toggle/show_chat_prayers category = PREFERENCE_CATEGORY_GAME_PREFERENCES @@ -35,7 +35,7 @@ if(!.) return - return check_rights(R_EVENT|R_ADMIN, FALSE, preferences.client) + return check_rights_for(preferences.client, R_EVENT|R_ADMIN) // General holder prefs /datum/preference/toggle/holder @@ -85,4 +85,4 @@ if(!..(preferences)) return FALSE - return CONFIG_GET(flag/allow_admin_ooccolor) && check_rights(R_ADMIN|R_EVENT|R_FUN, 0, preferences.client) + return CONFIG_GET(flag/allow_admin_ooccolor) && check_rights_for(preferences.client, R_ADMIN|R_EVENT|R_FUN) diff --git a/code/modules/client/verbs/advanced_who.dm b/code/modules/client/verbs/advanced_who.dm index 347c7c82e2..d9f8589abf 100644 --- a/code/modules/client/verbs/advanced_who.dm +++ b/code/modules/client/verbs/advanced_who.dm @@ -8,7 +8,7 @@ var/list/Lines = list() - if(check_rights(R_ADMIN|R_SERVER|R_MOD,FALSE,src)) + if(check_rights_for(src, R_ADMIN|R_SERVER|R_MOD)) for(var/client/C in GLOB.clients) var/entry = "
[C.key]" if(C.holder && C.holder.fakekey) diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm index b952a37c53..1856329f47 100644 --- a/code/modules/client/verbs/ooc.dm +++ b/code/modules/client/verbs/ooc.dm @@ -51,13 +51,13 @@ if(holder && !holder.fakekey) ooc_style = "elevated" - if(holder.rights & R_EVENT) //Retired Admins + if(check_rights(R_EVENT)) //Retired Admins ooc_style = "event_manager" - if(holder.rights & R_ADMIN && !(holder.rights & R_BAN)) //Game Masters + if(check_rights(R_ADMIN) && !(check_rights(R_BAN))) //Game Masters ooc_style = "moderator" - if(holder.rights & R_DEBUG && !(holder.rights & R_BAN)) //Developers + if(check_rights(R_SERVER) && !(check_rights(R_BAN))) //Developers ooc_style = "developer" - if(holder.rights & R_ADMIN && holder.rights & R_BAN) //Admins + if(check_rights(R_ADMIN) && check_rights(R_BAN)) //Admins ooc_style = "admin" msg = GLOB.is_valid_url.Replace(msg,span_linkify("$1")) @@ -74,7 +74,7 @@ else display_name = holder.fakekey var/pref_color = prefs.read_preference(/datum/preference/color/ooc_color) - if(holder && !holder.fakekey && (holder.rights & R_ADMIN|R_FUN|R_EVENT) && CONFIG_GET(flag/allow_admin_ooccolor) && pref_color != "#010000") // keeping this for the badmins + if(holder && !holder.fakekey && (check_rights(R_ADMIN|R_FUN|R_EVENT)) && CONFIG_GET(flag/allow_admin_ooccolor) && pref_color != "#010000") // keeping this for the badmins to_chat(target, span_ooc("" + create_text_tag("ooc", "OOC:", target) + " [display_name]: [span_message(msg)]")) else to_chat(target, span_ooc("" + create_text_tag("ooc", "OOC:", target) + " [display_name]: " + span_message(msg)) + "") @@ -163,7 +163,7 @@ // Admins with RLOOC displayed who weren't already in for(var/client/admin in GLOB.admins) if(!(admin in receivers) && admin.prefs?.read_preference(/datum/preference/toggle/holder/show_rlooc)) - if(check_rights(R_ADMIN|R_SERVER, FALSE, admin)) //Stop rLOOC showing for retired staff //CHOMPEdit, admins should see LOOC + if(check_rights_for(admin, R_ADMIN|R_SERVER)) //Stop rLOOC showing for retired staff //CHOMPEdit, admins should see LOOC r_receivers |= admin msg = GLOB.is_valid_url.Replace(msg,span_linkify("$1")) diff --git a/code/modules/client/verbs/who.dm b/code/modules/client/verbs/who.dm index 098d43240a..4ba5054bf2 100644 --- a/code/modules/client/verbs/who.dm +++ b/code/modules/client/verbs/who.dm @@ -73,19 +73,19 @@ if(C.holder.fakekey && !check_rights_for(src, R_ADMIN|R_MOD)) // Only admins and mods can see stealthmins continue // VOREStation Edit End - if(check_rights(R_BAN, FALSE, C)) // admins //VOREStation Edit + if(check_rights_for(C, R_BAN)) // admins //VOREStation Edit num_admins_online++ - else if(check_rights(R_ADMIN, FALSE, C) && !check_rights(R_SERVER, FALSE, C)) // mods //VOREStation Edit: Game masters + else if(check_rights_for(C, R_ADMIN) && !check_rights_for(C, R_SERVER)) // mods //VOREStation Edit: Game masters category = R_MOD num_mods_online++ - else if(check_rights(R_SERVER, FALSE, C)) // developers + else if(check_rights_for(C, R_SERVER)) // developers category = R_SERVER num_devs_online++ - else if(check_rights(R_STEALTH, FALSE, C)) // event managers //VOREStation Edit: Retired Staff + else if(check_rights_for(C, R_STEALTH)) // event managers //VOREStation Edit: Retired Staff category = R_EVENT num_event_managers_online++ - temp += "\t[C] is a [C.holder.rank]" + temp += "\t[C] is a [C.holder.rank_names()]" if(holder) if(C.holder.fakekey) temp += " " + span_italics("(as [C.holder.fakekey])") diff --git a/code/modules/mentor/mentor.dm b/code/modules/mentor/mentor.dm index 0db6a4ca0e..5b6d28c104 100644 --- a/code/modules/mentor/mentor.dm +++ b/code/modules/mentor/mentor.dm @@ -55,7 +55,7 @@ var/list/mentor_verbs_default = list( if(!target) return var/client/C = targets[target] - if(has_mentor_powers(C) || C.deadmin_holder) // If an admin is deadminned you could mentor them and that will cause fuckery if they readmin + if(has_mentor_powers(C) || GLOB.deadmins[C.ckey]) // If an admin is deadminned you could mentor them and that will cause fuckery if they readmin to_chat(src, span_admin_pm_warning("Error: They already have mentor powers.")) return var/datum/mentor/M = new /datum/mentor(C.ckey) diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 176c847093..4b98167c93 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -148,7 +148,7 @@ visualnet = ghostnet /mob/observer/dead/proc/checkStatic() - return !(check_rights(R_ADMIN|R_FUN|R_EVENT|R_SERVER, 0, src) || (client && client.buildmode) || isbelly(loc)) + return !(check_rights_for(src.client, R_ADMIN|R_FUN|R_EVENT|R_SERVER) || (client && client.buildmode) || isbelly(loc)) /mob/observer/dead/Moved(atom/old_loc, direction, forced) . = ..() @@ -260,7 +260,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp announce_ghost_joinleave(ghostize(1)) else var/response - if(check_rights(R_ADMIN|R_SERVER|R_MOD,FALSE,src)) //No need to sanity check for client and holder here as that is part of check_rights + if(check_rights_for(src.client, R_ADMIN|R_SERVER|R_MOD)) //No need to sanity check for client and holder here as that is part of check_rights response = tgui_alert(src, "You have the ability to Admin-Ghost. The regular Ghost verb will announce your presence to dead chat. Both variants will allow you to return to your body using 'aghost'.\n\nWhat do you wish to do?", "Are you sure you want to ghost?", list("Admin Ghost", "Ghost", "Stay in body")) if(response == "Admin Ghost") if(!src.client) @@ -613,7 +613,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp return 0 /mob/observer/dead/check_holy(var/turf/T) - if(check_rights(R_ADMIN|R_FUN|R_EVENT, 0, src)) + if(check_rights_for(src.client, R_ADMIN|R_FUN|R_EVENT)) return 0 return (T && T.holy) && (is_manifest || (mind in cult.current_antagonists)) @@ -923,7 +923,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp return 1 /mob/observer/dead/proc/can_admin_interact() - return check_rights(R_ADMIN|R_EVENT, 0, src) + return check_rights_for(src.client, R_ADMIN|R_EVENT|R_DEBUG) /mob/observer/dead/verb/toggle_ghostsee() set name = "Toggle Ghost Vision" diff --git a/code/modules/mob/living/carbon/alien/diona/progression.dm b/code/modules/mob/living/carbon/alien/diona/progression.dm index 45a33d4ebb..f701fd1992 100644 --- a/code/modules/mob/living/carbon/alien/diona/progression.dm +++ b/code/modules/mob/living/carbon/alien/diona/progression.dm @@ -6,7 +6,7 @@ /mob/living/carbon/alien/diona/confirm_evolution() - if(!is_alien_whitelisted(src, GLOB.all_species[SPECIES_DIONA])) + if(!is_alien_whitelisted(src.client, GLOB.all_species[SPECIES_DIONA])) tgui_alert(src, "You are currently not whitelisted to play as a full diona.") return null diff --git a/code/modules/mob/living/carbon/human/appearance.dm b/code/modules/mob/living/carbon/human/appearance.dm index fcd6781b63..085f72501b 100644 --- a/code/modules/mob/living/carbon/human/appearance.dm +++ b/code/modules/mob/living/carbon/human/appearance.dm @@ -180,14 +180,14 @@ for(var/current_species_name in GLOB.all_species) var/datum/species/current_species = GLOB.all_species[current_species_name] - if(check_whitelist && CONFIG_GET(flag/usealienwhitelist) && !check_rights(R_ADMIN|R_EVENT, 0, src)) //If we're using the whitelist, make sure to check it! + if(check_whitelist && CONFIG_GET(flag/usealienwhitelist) && !check_rights_for(src.client, R_ADMIN|R_EVENT)) //If we're using the whitelist, make sure to check it! if(!(current_species.spawn_flags & SPECIES_CAN_JOIN)) continue if(whitelist.len && !(current_species_name in whitelist)) continue if(blacklist.len && (current_species_name in blacklist)) continue - if((current_species.spawn_flags & SPECIES_IS_WHITELISTED) && !is_alien_whitelisted(src, current_species)) + if((current_species.spawn_flags & SPECIES_IS_WHITELISTED) && !is_alien_whitelisted(src.client, current_species)) continue valid_species += current_species_name diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index 29d26eefd6..495868918f 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -87,3 +87,4 @@ client.images += cloaked_selfimage client.init_verbs() SEND_SIGNAL(src, COMSIG_MOB_CLIENT_LOGIN, client) + SEND_SIGNAL(client, COMSIG_CLIENT_MOB_LOGIN, src) diff --git a/code/modules/mob/logout.dm b/code/modules/mob/logout.dm index 45b7ad1999..5dfd925d32 100644 --- a/code/modules/mob/logout.dm +++ b/code/modules/mob/logout.dm @@ -5,9 +5,9 @@ update_client_z(null) log_access_out(src) unset_machine() - if(admin_datums[src.ckey]) + if(GLOB.admin_datums[src.ckey]) message_admins("Staff logout: [key_name(src)]") // CHOMPEdit: Admin logout notice displays no matter what//Edit2: STAFF - if(ticker && ticker.current_state == GAME_STATE_PLAYING) //Only report this stuff if we are currently playing. + if (ticker && ticker.current_state == GAME_STATE_PLAYING) //Only report this stuff if we are currently playing. var/admins_number = GLOB.admins.len if(admins_number == 0) //Apparently the admin logging out is no longer an admin at this point, so we have to check this towards 0 and not towards 1. Awell. diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 5dd4c3488a..aede5c9d0a 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -467,7 +467,7 @@ set category = "OOC.Game" var/is_admin = 0 - if(client.holder && (client.holder.rights & R_ADMIN|R_EVENT)) + if(check_rights_for(client, R_ADMIN|R_EVENT)) is_admin = 1 else if(stat != DEAD || isnewplayer(src)) to_chat(src, span_filter_notice("[span_blue("You must be observing to use this!")]")) diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 107ecd582f..40b1496dce 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -83,7 +83,7 @@ /proc/is_admin(var/mob/user) - return check_rights(R_ADMIN|R_EVENT, 0, user) != 0 + return check_rights_for(user.client, R_ADMIN|R_EVENT) != 0 /** * Moved into its own file as part of port from CHOMP. @@ -406,7 +406,7 @@ var/list/intents = list(I_HELP,I_DISARM,I_GRAB,I_HURT) return // Can't talk in deadchat if you can't see it. for(var/mob/M in player_list) - if(M.client && ((!isnewplayer(M) && M.stat == DEAD) || (M.client.holder && M.client.holder.rights && M.client?.prefs?.read_preference(/datum/preference/toggle/holder/show_staff_dsay))) && M.client?.prefs?.read_preference(/datum/preference/toggle/show_dsay)) + if(M.client && ((!isnewplayer(M) && M.stat == DEAD) || (M.client.holder && check_rights_for(M.client, R_NONE) && M.client?.prefs?.read_preference(/datum/preference/toggle/holder/show_staff_dsay))) && M.client?.prefs?.read_preference(/datum/preference/toggle/show_dsay)) var/follow var/lname if(M.forbid_seeing_deadchat && !M.client.holder) @@ -436,7 +436,7 @@ var/list/intents = list(I_HELP,I_DISARM,I_GRAB,I_HURT) /proc/say_dead_object(var/message, var/obj/subject = null) for(var/mob/M in player_list) - if(M.client && ((!isnewplayer(M) && M.stat == DEAD) || (M.client.holder && M.client.holder.rights && M.client?.prefs?.read_preference(/datum/preference/toggle/holder/show_staff_dsay))) && M.client?.prefs?.read_preference(/datum/preference/toggle/show_dsay)) + if(M.client && ((!isnewplayer(M) && M.stat == DEAD) || (M.client.holder && check_rights_for(M.client, R_NONE) && M.client?.prefs?.read_preference(/datum/preference/toggle/holder/show_staff_dsay))) && M.client?.prefs?.read_preference(/datum/preference/toggle/show_dsay)) var/follow var/lname = "Game Master" if(M.forbid_seeing_deadchat && !M.client.holder) diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index c78a11c9ee..fa5c6f17c7 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -621,7 +621,7 @@ if(chosen_species && use_species_name) // Have to recheck admin due to no usr at roundstart. Latejoins are fine though. - if(is_alien_whitelisted(chosen_species)) + if(is_alien_whitelisted(src.client, chosen_species)) new_character = new(T, use_species_name) if(!new_character) @@ -697,9 +697,6 @@ src << browse(null, "window=News") //closes news window panel.close() -/mob/new_player/proc/has_admin_rights() - return check_rights(R_ADMIN, 0, src) - /mob/new_player/get_species() var/datum/species/chosen_species if(client.prefs.species) @@ -708,7 +705,7 @@ if(!chosen_species) return SPECIES_HUMAN - if(is_alien_whitelisted(chosen_species)) + if(is_alien_whitelisted(src.client, chosen_species)) return chosen_species.name return SPECIES_HUMAN diff --git a/code/modules/mob/new_player/new_player_vr.dm b/code/modules/mob/new_player/new_player_vr.dm index 09dd133ab0..c24b351c62 100644 --- a/code/modules/mob/new_player/new_player_vr.dm +++ b/code/modules/mob/new_player/new_player_vr.dm @@ -27,7 +27,7 @@ to_chat(src,span_warning("You have not set your scale yet. Do this on the VORE tab in character setup.")) //Can they play? - if(!is_alien_whitelisted(src,GLOB.all_species[client?.prefs?.species]) && !check_rights(R_ADMIN, 0)) + if(!is_alien_whitelisted(src.client,GLOB.all_species[client?.prefs?.species]) && !check_rights(R_ADMIN, 0)) pass = FALSE to_chat(src,span_warning("You are not allowed to spawn in as this species.")) diff --git a/code/modules/mob/new_player/poll.dm b/code/modules/mob/new_player/poll.dm index 6e073bc25f..0c8c11a5a2 100644 --- a/code/modules/mob/new_player/poll.dm +++ b/code/modules/mob/new_player/poll.dm @@ -398,7 +398,7 @@ var/adminrank = "Player" if(usr && usr.client && usr.client.holder) - adminrank = usr.client.holder.rank + adminrank = usr.client.holder.rank_names() var/datum/db_query/insert_query = SSdbcore.NewQuery("INSERT INTO erro_poll_vote (id ,datetime ,pollid ,optionid ,ckey ,ip ,adminrank) VALUES (null, Now(), [pollid], [optionid], '[usr.ckey]', '[usr.client.address]', '[adminrank]')") @@ -448,7 +448,7 @@ var/adminrank = "Player" if(usr && usr.client && usr.client.holder) - adminrank = usr.client.holder.rank + adminrank = usr.client.holder.rank_names() replytext = replacetext(replytext, "%BR%", "") @@ -520,7 +520,7 @@ var/adminrank = "Player" if(usr && usr.client && usr.client.holder) - adminrank = usr.client.holder.rank + adminrank = usr.client.holder.rank_names() var/datum/db_query/insert_query = SSdbcore.NewQuery("INSERT INTO erro_poll_vote (id ,datetime ,pollid ,optionid ,ckey ,ip ,adminrank, rating) VALUES (null, Now(), [pollid], [optionid], '[usr.ckey]', '[usr.client.address]', '[adminrank]', [(isnull(rating)) ? "null" : rating])") diff --git a/code/modules/modular_computers/computers/modular_computer/interaction.dm b/code/modules/modular_computers/computers/modular_computer/interaction.dm index 334ecfd8d8..9e9e56156f 100644 --- a/code/modules/modular_computers/computers/modular_computer/interaction.dm +++ b/code/modules/modular_computers/computers/modular_computer/interaction.dm @@ -100,7 +100,7 @@ /obj/item/modular_computer/attack_ghost(var/mob/observer/dead/user) if(enabled) tgui_interact(user) - else if(check_rights(R_ADMIN|R_EVENT, 0, user)) + else if(check_rights_for(user.client, R_ADMIN|R_EVENT|R_DEBUG)) var/response = tgui_alert(user, "This computer is turned off. Would you like to turn it on?", "Admin Override", list("Yes", "No")) if(response == "Yes") turn_on(user) diff --git a/code/modules/modular_computers/file_system/program.dm b/code/modules/modular_computers/file_system/program.dm index 9d0edc3991..9619d2728d 100644 --- a/code/modules/modular_computers/file_system/program.dm +++ b/code/modules/modular_computers/file_system/program.dm @@ -109,7 +109,7 @@ return 1 // Admin override - allows operation of any computer as aghosted admin, as if you had any required access. - if(isobserver(user) && check_rights(R_ADMIN|R_EVENT, 0, user)) + if(isobserver(user) && check_rights_for(user.client, R_ADMIN|R_EVENT|R_DEBUG)) return 1 if(!istype(user)) diff --git a/code/modules/paperwork/faxmachine.dm b/code/modules/paperwork/faxmachine.dm index 2b11acdf5c..683a073abf 100644 --- a/code/modules/paperwork/faxmachine.dm +++ b/code/modules/paperwork/faxmachine.dm @@ -425,7 +425,7 @@ Extracted to its own procedure for easier logic handling with paper bundles. msg = span_notice(msg) for(var/client/C in GLOB.admins) - if(check_rights((R_ADMIN|R_MOD|R_EVENT),0,C)) + if(check_rights_for(C, (R_ADMIN|R_MOD|R_EVENT))) to_chat(C,msg) C << 'sound/machines/printer.ogg' sender.client << 'sound/machines/printer.ogg' //CHOMPEdit - The pain must be felt diff --git a/code/modules/resleeving/autoresleever.dm b/code/modules/resleeving/autoresleever.dm index f23b84c9fd..d7be416618 100644 --- a/code/modules/resleeving/autoresleever.dm +++ b/code/modules/resleeving/autoresleever.dm @@ -75,7 +75,7 @@ var/client/ghost_client = ghost.client - if(!is_alien_whitelisted(ghost, GLOB.all_species[ghost_client?.prefs?.species]) && !check_rights(R_ADMIN, 0)) // Prevents a ghost ghosting in on a slot and spawning via a resleever with race they're not whitelisted for, getting around normal join restrictions. + if(!is_alien_whitelisted(ghost.client, GLOB.all_species[ghost_client?.prefs?.species]) && !check_rights(R_ADMIN, 0)) // Prevents a ghost ghosting in on a slot and spawning via a resleever with race they're not whitelisted for, getting around normal join restrictions. to_chat(ghost, span_warning("You are not whitelisted to spawn as this species!")) return diff --git a/code/modules/tgs/v5/chat_commands.dm b/code/modules/tgs/v5/chat_commands.dm index 697235ae53..b0769257a4 100644 --- a/code/modules/tgs/v5/chat_commands.dm +++ b/code/modules/tgs/v5/chat_commands.dm @@ -63,13 +63,13 @@ else keymsg += " (Ingame)" - if(R_ADMIN & C.holder.rights && R_BAN & C.holder.rights) // R_ADMIN and R_BAN apparently an admin makes + if(check_rights_for(C, R_ADMIN) && check_rights_for(C, R_BAN)) // R_ADMIN and R_BAN apparently an admin makes admin_keys += keymsg - else if(R_ADMIN & C.holder.rights && !(R_SERVER & C.holder.rights)) // R_ADMIN but not R_SERVER makes a moderator + else if(check_rights_for(C, R_ADMIN) && !(check_rights_for(C, R_SERVER))) // R_ADMIN but not R_SERVER makes a moderator mod_keys += keymsg - else if(R_SERVER & C.holder.rights) // R_SERVER makes a dev + else if(check_rights_for(C, R_SERVER)) // R_SERVER makes a dev dev_keys += keymsg else // No R_ADMIN&&R_BAN, R_ADMIN!R_BAN, R_SERVER, must be a GM or something diff --git a/code/modules/tgui/modules/admin/player_notes.dm b/code/modules/tgui/modules/admin/player_notes.dm index 5730eb56d6..ebc69925c4 100644 --- a/code/modules/tgui/modules/admin/player_notes.dm +++ b/code/modules/tgui/modules/admin/player_notes.dm @@ -48,7 +48,7 @@ current_filter = filter /datum/tgui_module/player_notes/proc/open_legacy() - var/datum/admins/A = admin_datums[usr.ckey] + var/datum/admins/A = GLOB.admin_datums[usr.ckey] A.PlayerNotesLegacy() /datum/tgui_module/player_notes/tgui_state(mob/user) @@ -113,7 +113,7 @@ if(..()) return TRUE - var/datum/admins/A = admin_datums[usr.ckey] + var/datum/admins/A = GLOB.admin_datums[usr.ckey] A.show_player_info_legacy(key) /datum/tgui_module/player_notes_info/tgui_act(action, params, datum/tgui/ui) diff --git a/code/modules/tgui/modules/late_choices.dm b/code/modules/tgui/modules/late_choices.dm index e693f0c4f4..67df7c3b4a 100644 --- a/code/modules/tgui/modules/late_choices.dm +++ b/code/modules/tgui/modules/late_choices.dm @@ -131,7 +131,7 @@ return var/datum/species/S = GLOB.all_species[new_user.client.prefs.species] - if(!is_alien_whitelisted(new_user, S)) + if(!is_alien_whitelisted(new_user.client, S)) tgui_alert(new_user, "You are currently not whitelisted to play [new_user.client.prefs.species].") return 0 diff --git a/code/modules/tgui/states.dm b/code/modules/tgui/states.dm index ab80a41bd5..28c217326b 100644 --- a/code/modules/tgui/states.dm +++ b/code/modules/tgui/states.dm @@ -24,7 +24,7 @@ if(isobserver(user)) // Admins can always interact. - if(check_rights(R_ADMIN|R_EVENT, 0, src)) + if(check_rights_for(user.client, R_ADMIN|R_EVENT|R_DEBUG)) . = max(., STATUS_INTERACTIVE) // Regular ghosts can always at least view if in range. diff --git a/code/modules/tgui/states/admin.dm b/code/modules/tgui/states/admin.dm index bfa877bc09..28c395f078 100644 --- a/code/modules/tgui/states/admin.dm +++ b/code/modules/tgui/states/admin.dm @@ -12,6 +12,6 @@ GLOBAL_DATUM_INIT(tgui_admin_state, /datum/tgui_state/admin_state, new) /datum/tgui_state/admin_state/can_use_topic(src_object, mob/user) - if(check_rights_for(user.client, R_ADMIN|R_EVENT)) + if(check_rights_for(user.client, R_ADMIN|R_EVENT|R_DEBUG)) return STATUS_INTERACTIVE return STATUS_CLOSE diff --git a/code/modules/tgui/states/default.dm b/code/modules/tgui/states/default.dm index 2fe058f3e0..5b16728e25 100644 --- a/code/modules/tgui/states/default.dm +++ b/code/modules/tgui/states/default.dm @@ -79,6 +79,6 @@ GLOBAL_DATUM_INIT(tgui_default_state, /datum/tgui_state/default, new) return ..() /mob/observer/dead/default_can_use_tgui_topic() - if(check_rights(R_ADMIN|R_EVENT, 0, src)) + if(check_rights_for(src.client, R_ADMIN|R_EVENT|R_DEBUG)) return STATUS_INTERACTIVE // Admins are more equal return STATUS_UPDATE // Ghosts can view updates diff --git a/code/modules/tgui/states/observer.dm b/code/modules/tgui/states/observer.dm index f20bb1b71f..79e6f51b6b 100644 --- a/code/modules/tgui/states/observer.dm +++ b/code/modules/tgui/states/observer.dm @@ -14,6 +14,6 @@ GLOBAL_DATUM_INIT(tgui_observer_state, /datum/tgui_state/observer_state, new) /datum/tgui_state/observer_state/can_use_topic(src_object, mob/user) if(isobserver(user)) return STATUS_INTERACTIVE - if(check_rights(R_ADMIN|R_EVENT, 0, src)) + if(check_rights_for(user.client, R_ADMIN|R_EVENT)) return STATUS_INTERACTIVE return STATUS_CLOSE diff --git a/code/modules/vote/vote_datum.dm b/code/modules/vote/vote_datum.dm index ebdaf6e596..61cf7156f4 100644 --- a/code/modules/vote/vote_datum.dm +++ b/code/modules/vote/vote_datum.dm @@ -169,7 +169,7 @@ data["question"] = question data["choices"] = choices - if(show_counts || check_rights(R_ADMIN, FALSE, user)) + if(show_counts || check_rights_for(user.client, R_ADMIN)) data["show_counts"] = TRUE var/list/counts = list() diff --git a/code/modules/whitelist/whitelist.dm b/code/modules/whitelist/whitelist.dm index 1352c5b976..772872eb3e 100644 --- a/code/modules/whitelist/whitelist.dm +++ b/code/modules/whitelist/whitelist.dm @@ -97,4 +97,4 @@ /proc/whitelist_overrides(mob/M) - return !CONFIG_GET(flag/usealienwhitelist) || check_rights(R_ADMIN|R_EVENT, 0, M) // CHOMPEdit + return !CONFIG_GET(flag/usealienwhitelist) || check_rights_for(M.client, R_ADMIN|R_EVENT) // CHOMPEdit diff --git a/config/example/admin_ranks.txt b/config/example/admin_ranks.txt index 817da0b541..5255f7b3f6 100644 --- a/config/example/admin_ranks.txt +++ b/config/example/admin_ranks.txt @@ -1,46 +1,89 @@ -######################################################################################## -# ADMIN RANK DEFINES # -# The format of this is very simple. Rank name goes first. # -# Rank is CASE-SENSITIVE, all punctuation will be stripped so spaces don't matter. # -# Each rank is then followed by keywords with the prefix "+". # -# These keywords represent groups of verbs and abilities which are given to that rank. # -# +@ (or +prev) is a special shorthand which adds all the rights of the rank above it. # -# Ranks with no keywords will just be given the most basic verbs and abilities ~Carn # -######################################################################################## -# PLEASE NOTE: depending on config options, some abilities will be unavailable regardless if you have permission to use them! -# ALSO NOTE: this is a WorkInProgress at the moment. Most of this is just arbitrarily thrown in whatever group because LoadsaWork2Do+LittleTime. -# I'll be doing more moving around as feedback comes in. So be sure to check the notes after updates. +#Admin Rank format is as follows: +# +#Name = Game Admin +#Include = @ ADMIN BAN SOUND +#Exclude = FUN +#Edit = +# +#Name will match anything after '=' and must be identical to an admin's rank in admins.txt to be linked but otherwise has no formatting restrictions. +#A rank's permissions are defined with keywords that control access to groups of verbs and abilities, they are case-sensitive and separated by a space with no prefix. +#To define no permissions for a type, leave it empty. +#There are three types of permissions: +#Include will give a keyword to a rank. +#Exclude removes a keyword and takes precedence over Include. +#Edit will allow an admin to edit these permissions on other ranks or change an admin's rank to another if they can edit all the permissions it has. +#Edit is only used when SQL-based admin loading is enabled. +#If SQL-based admin loading is enabled, ranks and their keywords listed here will be loaded first and override any with the same name loaded from the database. +# +#The following are valid permission keywords: +#ADMIN = general admin tools, verbs etc. +#FUN = events, other event-orientated actions. Access to the fun secrets in the secrets panel. +#BAN = the ability to ban and unban. +#STEALTH = the ability to stealthmin (make yourself appear with a fake name to everyone but other admins). +#POSSESS = the ability to possess objects. +#REJUVINATE = the ability to heal, respawn, modify damage and use godmode +#BUILDMODE = the ability to use buildmode. +#SERVER = the ability to restart the server, change the game mode or force a round to start/end. +#DEBUG = debug tools used for diagnosing and fixing problems. It's useful to give this to coders so they can investigate problems on a live server. +#VAREDIT = everyone may view viewvars/debugvars/whatever you call it. This keyword allows you to actually EDIT those variables. +#PERMISSIONS = allows you to promote and/or demote people. +#SOUNDS = 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. +#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. -# KEYWORDS: -# +ADMIN = general admin tools, verbs etc -# +FUN = events, other event-orientated actions. Access to the fun secrets in the secrets panel. -# +BAN = the ability to ban, jobban and fullban -# +STEALTH = the ability to stealthmin (make yourself appear with a fake name to everyone but other admins -# +POSSESS = the ability to possess objects -# +REJUV (or +REJUVINATE) = the ability to heal, respawn, modify damage and use godmode -# +BUILD (or +BUILDMODE) = the ability to use buildmode -# +SERVER = higher-risk admin verbs and abilities, such as those which affect the server configuration. -# +DEBUG = debug tools used for diagnosing and fixing problems. It's useful to give this to coders so they can investigate problems on a live server. -# +VAREDIT = everyone may view viewvars/debugvars/whatever you call it. This keyword allows you to actually EDIT those variables. -# +RIGHTS (or +PERMISSIONS) = allows you to promote and/or demote people. -# +SOUND (or +SOUNDS) = allows you to upload and play sounds -# +SPAWN (or +CREATE) = 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. -# +EVERYTHING (or +HOST or +ALL) = Simply gives you everything without having to type every flag +Name = Admin Observer +Include = +Exclude = +Edit = -Admin Observer -Moderator +MOD -Mentor +MENTOR +Name = Moderator +Include = MOD +Exclude = +Edit = -Admin Candidate +ADMIN -Trial Admin +@ +STEALTH +SPAWN +REJUV +VAREDIT +BAN +FUN -Admin +@ +POSSESS +BUILDMODE +SERVER -Admin Supervisor +@ +SOUNDS +DEBUG +PERMISSIONS -Game Master +ADMIN +FUN +POSSESS +REJUV +BUILD +SERVER +DEBUG +VAREDIT +SOUND +SPAWN -Head Admin +EVERYTHING -Retired Admin +ADMIN +STEALTH +Name = Admin Candidate +Include = ADMIN +Exclude = +Edit = -Host +EVERYTHING +Name = Trial Admin +Include = @ SPAWN REJUVINATE VAREDIT BAN +Exclude = +Edit = -Developer +DEBUG +VAREDIT +SERVER +SPAWN +REJUV +POSSESS +BUILDMODE -Dev Mod +@ +MOD +Name = Badmin +Include = @ POSSESS BUILDMODE SERVER FUN +Exclude = +Edit = + +Name = Game Admin +Include = @ STEALTH SOUNDS DEBUG PERMISSIONS +Exclude = +Edit = + +Name = Game Master +Include = EVERYTHING +Exclude = +Edit = EVERYTHING + +Name = Head Admin +Include = EVERYTHING +Exclude = +Edit = EVERYTHING + +Name = Retired Admin +Include = ADMIN STEALTH +Exclude = +Edit = + +Name = Host +Include = EVERYTHING +Exclude = +Edit = EVERYTHING + +Name = Developer +Include = DEBUG VAREDIT SERVER SPAWN REJUVINATE POSSESS BUILDMODE +Exclude = +Edit = diff --git a/config/example/admins.txt b/config/example/admins.txt index 810fc79320..84f7aa36e6 100644 --- a/config/example/admins.txt +++ b/config/example/admins.txt @@ -1,8 +1,11 @@ -###################################################################### -# Basically, ckey goes first. Rank goes after the "-" # -# Case is not important for ckey. # -# Case IS important for the rank. However punctuation/spaces are not # -# Ranks can be anything defined in admin_ranks.txt ~Carn # -###################################################################### +############################################################################################### +# Basically, ckey goes first. Rank goes after the "=" # +# Case is not important for ckey. # +# Case IS important for the rank. # +# All punctuation (spaces etc) EXCEPT '-', '_' and '@' will be stripped from rank names. # +# Ranks can be anything defined in admin_ranks.txt # +# NOTE: if the rank-name cannot be found in admin_ranks.txt, they will not be adminned! ~Carn # +# NOTE: syntax was changed to allow hyphenation of ranknames, since spaces are stripped. # +############################################################################################### -# not_a_user - Admin \ No newline at end of file +# not_a_user = Admin diff --git a/config/example/config.txt b/config/example/config.txt index 7e3e0a87b7..624730ff4c 100644 --- a/config/example/config.txt +++ b/config/example/config.txt @@ -17,9 +17,21 @@ $include sqlite.txt ## Server name: This appears at the top of the screen in-game. In this case it will read "tgstation: station_name" where station_name is the randomly generated name of the station for the round. Remove the # infront of SERVERNAME and replace 'tgstation' with the name of your choice # SERVERNAME spacestation13 -## Add a # infront of this if you want to use the SQL based admin system, the legacy system uses admins.txt. You need to set up your database to use the SQL based system. +## Comment this out if you want to use the SQL based admin system, the legacy system uses admins.txt. +## You need to set up your database to use the SQL based system. +## This flag is automatically enabled if SQL_ENABLED isn't ADMIN_LEGACY_SYSTEM +##Uncomment this to stop any admins loaded by the legacy system from having their rank edited by the permissions panel +#PROTECT_LEGACY_ADMINS + +##Uncomment this to stop any ranks loaded by the legacy system from having their flags edited by the permissions panel +#PROTECT_LEGACY_RANKS + +##Uncomment this to have admin ranks only loaded from the legacy admin_ranks.txt +##If enabled, each time admins are loaded ranks the database will be updated with the current ranks and their flags +#LOAD_LEGACY_RANKS_ONLY + ## Add a # infront of this if you want to use the SQL based banning system. The legacy systems use the files in the data folder. You need to set up your database to use the SQL based system. BAN_LEGACY_SYSTEM @@ -596,3 +608,6 @@ JUKEBOX_TRACK_FILES config/jukebox.json # The endpoint for the chat to fetch the chatlogs from (for example, the last 2500 messages on init for the history) # REQUIRES chatlog_database_backend to be enabled #CHATLOG_DATABASE_API_ENDPOINT https://example.com + +## Uncomment to block granting profiling privileges to users with R_DEBUG, for performance purposes +#FORBID_ADMIN_PROFILING diff --git a/html/panels.css b/html/admin/panels.css similarity index 79% rename from html/panels.css rename to html/admin/panels.css index ac44cf5487..22373ee0ca 100644 --- a/html/panels.css +++ b/html/admin/panels.css @@ -1,10 +1,10 @@ body {padding:0px;margin:0px;} #top {position:fixed;top:5px;left:10%;width:80%;text-align:center;background-color:#fff;border:2px solid #ccc;} -#main {position:relative;top:50px;left:3%;width:96%;text-align:center;z-index:0;} +#main {position:relative;top:10px;left:3%;width:96%;text-align:center;z-index:0;} #searchable {table-layout:fixed;width:100%;text-align:center;"#f4f4f4";} tr.norm {background-color:#f4f4f4;} tr.title {background-color:#ccc;} tr.alt {background-color:#e7e7e7;} .small {font-size:80%;} -a {text-decoration:none;color:#a0a;} +a {text-decoration:none;} a:hover {color:#d3d;} diff --git a/html/search.js b/html/admin/search.js similarity index 91% rename from html/search.js rename to html/admin/search.js index ded0b92844..639a3729fe 100644 --- a/html/search.js +++ b/html/admin/search.js @@ -23,11 +23,11 @@ function updateSearch(){ } if(found == 0) row.style.display='none'; else{ - row.style.display='block'; + row.style.display='table-row'; /* DON'T make tables with block property */ row.className = alt_style; if(alt_style == 'alt') alt_style = 'norm'; else alt_style = 'alt'; } }catch(err) { } } -} \ No newline at end of file +} diff --git a/icons/ui/common/padlock.png b/icons/ui/common/padlock.png new file mode 100644 index 0000000000..719a12e9c9 Binary files /dev/null and b/icons/ui/common/padlock.png differ diff --git a/modular_chomp/code/modules/tickets/tickets.dm b/modular_chomp/code/modules/tickets/tickets.dm index 55793a87f2..477158af60 100644 --- a/modular_chomp/code/modules/tickets/tickets.dm +++ b/modular_chomp/code/modules/tickets/tickets.dm @@ -701,7 +701,7 @@ GLOBAL_DATUM_INIT(tickets, /datum/tickets, new) . = list("total" = list(), "noflags" = list(), "afk" = list(), "stealth" = list(), "present" = list()) for(var/client/X in GLOB.admins) .["total"] += X - if(requiredflags != 0 && !check_rights(rights_required = requiredflags, show_msg = FALSE, C = X)) + if(requiredflags != 0 && !check_rights(X, requiredflags)) .["noflags"] += X else if(X.is_afk()) .["afk"] += X diff --git a/tools/admin_rank_bitflag_calculator.py b/tools/admin_rank_bitflag_calculator.py new file mode 100644 index 0000000000..758031cfd0 --- /dev/null +++ b/tools/admin_rank_bitflag_calculator.py @@ -0,0 +1,66 @@ +import tkinter as tk +from tkinter import ttk + +bitflags = { + "R_BUILDMODE": 1<<0, + "R_ADMIN": 1<<1, + "R_BAN": 1<<2, + "R_FUN": 1<<3, + "R_SERVER": 1<<4, + "R_DEBUG": 1<<5, + "R_POSSESS": 1<<6, + "R_PERMISSIONS": 1<<7, + "R_STEALTH": 1<<8, + "R_REJUVINATE": 1<<9, + "R_VAREDIT": 1<<10, + "R_SOUNDS": 1<<11, + "R_SPAWN": 1<<12, + "R_MOD": 1<<13, + "R_EVENT": 1<<14, + "R_HOST": 1<<15, + "-------------": 0, + "EVERYTHING": (1<<16)-1 +} + +class BitflagCalculator: + def __init__(self, master): + self.master = master + master.title("Bitflag Calculator") + master.geometry("300x625") + + self.checkboxes = {} + self.vars = {} + + for i, (flag, value) in enumerate(bitflags.items()): + var = tk.IntVar() + cb = ttk.Checkbutton(master, text=flag, variable=var, command=self.update_result) + cb.grid(row=i, column=0, sticky="w", padx=10, pady=2) + self.checkboxes[flag] = cb + self.vars[flag] = var + + self.result_label = ttk.Label(master, text="Result: 0") + self.result_label.grid(row=len(bitflags), column=0, pady=10) + + self.copy_button = ttk.Button(master, text="Copy Result", command=self.copy_result) + self.copy_button.grid(row=len(bitflags)+1, column=0, pady=5) + + self.clear_button = ttk.Button(master, text="Clear All", command=self.clear_all) + self.clear_button.grid(row=len(bitflags)+2, column=0, pady=5) + + def update_result(self): + result = sum(value for flag, value in bitflags.items() if self.vars[flag].get()) + self.result_label.config(text=f"Result: {result}") + + def copy_result(self): + result = sum(value for flag, value in bitflags.items() if self.vars[flag].get()) + self.master.clipboard_clear() + self.master.clipboard_append(str(result)) + + def clear_all(self): + for var in self.vars.values(): + var.set(0) + self.update_result() + +root = tk.Tk() +calculator = BitflagCalculator(root) +root.mainloop() diff --git a/vorestation.dme b/vorestation.dme index 6c34d77289..801faf0cb9 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -21,6 +21,7 @@ #include "code\names.dm" #include "code\world.dm" #include "code\__defines\__globals.dm" +#include "code\__defines\_bitfields.dm" #include "code\__defines\_click.dm" #include "code\__defines\_compile_options.dm" #include "code\__defines\_fruits.dm" @@ -35,6 +36,7 @@ #include "code\__defines\action.dm" #include "code\__defines\admin.dm" #include "code\__defines\admin_ch.dm" +#include "code\__defines\admin_verb.dm" #include "code\__defines\admin_vr.dm" #include "code\__defines\airlock_control.dm" #include "code\__defines\ammunition.dm" @@ -185,6 +187,7 @@ #include "code\__defines\dcs\helpers.dm" #include "code\__defines\dcs\signals.dm" #include "code\__defines\dcs\signals_ch.dm" +#include "code\__defines\dcs\signals\signals_client.dm" #include "code\__defines\dcs\signals\signals_subsystem.dm" #include "code\__defines\dcs\signals\signals_mobs\signals_mob_main.dm" #include "code\__defines\dcs\signals\signals_turf.dm" @@ -210,6 +213,7 @@ #include "code\_helpers\_global_objects.dm" #include "code\_helpers\_global_objects_vr.dm" #include "code\_helpers\_lists.dm" +#include "code\_helpers\admin.dm" #include "code\_helpers\announcements.dm" #include "code\_helpers\atmospherics.dm" #include "code\_helpers\atom_movables.dm" @@ -1966,8 +1970,6 @@ #include "code\modules\admin\admin_tools.dm" #include "code\modules\admin\admin_verb_lists_vr.dm" #include "code\modules\admin\admin_verbs.dm" -#include "code\modules\admin\admin_verbs_vr.dm" -#include "code\modules\admin\admin_vr.dm" #include "code\modules\admin\banjob.dm" #include "code\modules\admin\ckey_vr.dm" #include "code\modules\admin\create_mob.dm" @@ -1980,6 +1982,7 @@ #include "code\modules\admin\modify_robot.dm" #include "code\modules\admin\NewBan.dm" #include "code\modules\admin\news.dm" +#include "code\modules\admin\permissionedit.dm" #include "code\modules\admin\persistence.dm" #include "code\modules\admin\player_effects.dm" #include "code\modules\admin\player_notes.dm" @@ -1988,7 +1991,6 @@ #include "code\modules\admin\ToRban.dm" #include "code\modules\admin\callproc\callproc.dm" #include "code\modules\admin\DB ban\functions.dm" -#include "code\modules\admin\permissionverbs\permissionedit.dm" #include "code\modules\admin\secrets\admin_secrets\admin_logs.dm" #include "code\modules\admin\secrets\admin_secrets\alter_narsie.dm" #include "code\modules\admin\secrets\admin_secrets\bombing_list.dm" @@ -2026,6 +2028,7 @@ #include "code\modules\admin\secrets\random_events\gravity.dm" #include "code\modules\admin\secrets\random_events\trigger_cordical_borer_infestation.dm" #include "code\modules\admin\secrets\random_events\trigger_xenomorph_infestation.dm" +#include "code\modules\admin\verb_datums\_admin_verb_datum.dm" #include "code\modules\admin\verbs\admin_ch.dm" #include "code\modules\admin\verbs\adminjump.dm" #include "code\modules\admin\verbs\adminpm.dm" @@ -2061,6 +2064,7 @@ #include "code\modules\admin\verbs\randomverbs_vr.dm" #include "code\modules\admin\verbs\resize.dm" #include "code\modules\admin\verbs\smite.dm" +#include "code\modules\admin\verbs\special_verbs.dm" #include "code\modules\admin\verbs\striketeam.dm" #include "code\modules\admin\verbs\tripAI.dm" #include "code\modules\admin\verbs\SDQL2\SDQL_2.dm" @@ -2124,10 +2128,12 @@ #include "code\modules\asset_cache\assets\browser.dm" #include "code\modules\asset_cache\assets\circuits.dm" #include "code\modules\asset_cache\assets\cloning.dm" +#include "code\modules\asset_cache\assets\common.dm" #include "code\modules\asset_cache\assets\fontawesome.dm" #include "code\modules\asset_cache\assets\icon_ref_map.dm" #include "code\modules\asset_cache\assets\jquery.dm" #include "code\modules\asset_cache\assets\ntos.dm" +#include "code\modules\asset_cache\assets\permissions.dm" #include "code\modules\asset_cache\assets\preferences.dm" #include "code\modules\asset_cache\assets\tgfont.dm" #include "code\modules\asset_cache\assets\tgui.dm"