diff --git a/SQL/paradise_schema.sql b/SQL/paradise_schema.sql
index da78d6a3ca8..3c16d6ae650 100644
--- a/SQL/paradise_schema.sql
+++ b/SQL/paradise_schema.sql
@@ -171,14 +171,32 @@ DROP TABLE IF EXISTS `admin`;
CREATE TABLE `admin` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ckey` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
- `admin_rank` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'Administrator',
- `level` int(2) NOT NULL DEFAULT '0',
- `flags` int(16) NOT NULL DEFAULT '0',
+ `display_rank` varchar(32) COLLATE utf8mb4_unicode_ci,
+ `permissions_rank` int(11) COMMENT 'Foreign key for admin_ranks.id',
+ `extra_permissions` int(16) NOT NULL DEFAULT '0',
+ `removed_permissions` int(16) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `ckey` (`ckey`)
) ENGINE=InnoDB AUTO_INCREMENT=99 DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `admin_ranks`
+--
+
+DROP TABLE IF EXISTS `admin_ranks`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `admin_ranks` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `default_permissions` int(16) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `name` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=99 DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
--
-- Table structure for table `admin_log`
--
@@ -273,7 +291,6 @@ CREATE TABLE `player` (
`lastseen` datetime NOT NULL,
`ip` varchar(18) COLLATE utf8mb4_unicode_ci NOT NULL,
`computerid` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
- `lastadminrank` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'Player',
`ooccolor` varchar(7) COLLATE utf8mb4_unicode_ci DEFAULT '#b82e00',
`UI_style` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT 'Midnight',
`UI_style_color` varchar(7) COLLATE utf8mb4_unicode_ci DEFAULT '#ffffff',
diff --git a/SQL/updates/69-70.sql b/SQL/updates/69-70.sql
new file mode 100644
index 00000000000..38fb9b5a344
--- /dev/null
+++ b/SQL/updates/69-70.sql
@@ -0,0 +1,39 @@
+# Updating DB from 69-70 -FunnyMan3595
+# Adds the admin_ranks table
+
+# Create the new table.
+CREATE TABLE `admin_ranks` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `default_permissions` int(16) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`),
+ KEY `name` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=99 DEFAULT CHARSET=utf8mb4;
+
+# Create official ranks for every existing display rank, with the minimum common permissions.
+INSERT INTO admin_ranks (name, default_permissions)
+SELECT admin_rank, BIT_AND(flags) FROM admin GROUP BY admin_rank;
+
+# Add new columns to admin table.
+ALTER TABLE admin
+ ADD COLUMN `display_rank` varchar(32) COLLATE utf8mb4_unicode_ci,
+ ADD COLUMN `permissions_rank` int(11) COMMENT 'Foreign key for admin_ranks.id',
+ ADD COLUMN `extra_permissions` int(16) NOT NULL DEFAULT '0',
+ ADD COLUMN `removed_permissions` int(16) NOT NULL DEFAULT '0';
+
+# Set the rank and any extra_permissions for each admin.
+# There won't be any removed_permissions because we took the minimum common permissions earlier.
+# We also leave display_rank blank, since admin_ranks.name matches their old admin_rank.
+UPDATE admin JOIN admin_ranks ON admin.admin_rank = admin_ranks.name
+SET
+ admin.permissions_rank = admin_ranks.id,
+ admin.extra_permissions = admin.flags & ~admin_ranks.default_permissions;
+
+# Drop the old columns from the admin table.
+ALTER TABLE admin
+ DROP COLUMN admin_rank,
+ DROP COLUMN level,
+ DROP COLUMN flags;
+
+# Drop the lastadminrank column from the player table.
+ALTER TABLE player DROP COLUMN lastadminrank;
diff --git a/code/__DEFINES/admin_defines.dm b/code/__DEFINES/admin_defines.dm
index a9539d20135..e9e80b91110 100644
--- a/code/__DEFINES/admin_defines.dm
+++ b/code/__DEFINES/admin_defines.dm
@@ -41,7 +41,7 @@
#define R_MAINTAINER (1<<17)
#define R_DEV_TEAM (1<<18)
#define R_VIEWLOGS (1<<19)
-// Update the following two defines if you add more
+// Update the following two defines and GLOB.admin_permission_names if you add more
#define R_MAXPERMISSION (1<<19) // This holds the maximum value for a permission. It is used in iteration, so keep it updated.
diff --git a/code/__DEFINES/misc_defines.dm b/code/__DEFINES/misc_defines.dm
index a46d6ccd65a..5db8793c000 100644
--- a/code/__DEFINES/misc_defines.dm
+++ b/code/__DEFINES/misc_defines.dm
@@ -438,7 +438,7 @@
#define INVESTIGATE_DEATHS "deaths"
// The SQL version required by this version of the code
-#define SQL_VERSION 69
+#define SQL_VERSION 70
// Vending machine stuff
#define CAT_NORMAL (1<<0)
diff --git a/code/__HELPERS/type2type.dm b/code/__HELPERS/type2type.dm
index 243bc0e6205..4135db31786 100644
--- a/code/__HELPERS/type2type.dm
+++ b/code/__HELPERS/type2type.dm
@@ -34,7 +34,7 @@
num_list += text2num(x)
return num_list
-//Splits the text of a file at seperator and returns them in a list.
+//Splits the text of a file at separator and returns them in a list.
/proc/file2list(filename, separator = "\n", no_empty = TRUE)
var/list/result = list()
for(var/line in splittext(return_file_text(filename), separator))
@@ -132,28 +132,23 @@
if(BLEND_SUBTRACT) return ICON_SUBTRACT
else return ICON_OVERLAY
-//Converts a rights bitfield into a string
-/proc/rights2text(rights,seperator="")
- if(rights & R_BUILDMODE) . += "[seperator]+BUILDMODE"
- if(rights & R_ADMIN) . += "[seperator]+ADMIN"
- if(rights & R_BAN) . += "[seperator]+BAN"
- if(rights & R_EVENT) . += "[seperator]+EVENT"
- if(rights & R_SERVER) . += "[seperator]+SERVER"
- if(rights & R_DEBUG) . += "[seperator]+DEBUG"
- if(rights & R_POSSESS) . += "[seperator]+POSSESS"
- if(rights & R_PERMISSIONS) . += "[seperator]+PERMISSIONS"
- if(rights & R_STEALTH) . += "[seperator]+STEALTH"
- if(rights & R_REJUVINATE) . += "[seperator]+REJUVINATE"
- if(rights & R_VAREDIT) . += "[seperator]+VAREDIT"
- if(rights & R_SOUNDS) . += "[seperator]+SOUND"
- if(rights & R_SPAWN) . += "[seperator]+SPAWN"
- if(rights & R_PROCCALL) . += "[seperator]+PROCCALL"
- if(rights & R_MOD) . += "[seperator]+MODERATOR"
- if(rights & R_MENTOR) . += "[seperator]+MENTOR"
- if(rights & R_VIEWRUNTIMES) . += "[seperator]+VIEWRUNTIMES"
- if(rights & R_MAINTAINER) . += "[seperator]+MAINTAINER"
- if(rights & R_DEV_TEAM) . += "[seperator]+DEV_TEAM"
- if(rights & R_VIEWLOGS) . += "[seperator]+VIEWLOGS"
+///Converts a rights bitfield into a string
+/proc/rights2text(rights,separator="")
+ . = ""
+ for(var/bit in GLOB.admin_permission_names)
+ if(rights & bit)
+ . += "[separator]+[GLOB.admin_permission_names[bit]]"
+
+///Converts the full permissions details of a DB-ranked admin to a HTML-colored string
+/proc/ranked_rights2text(rank_rights, extra_rights, removed_rights, separator="")
+ . = ""
+ for(var/bit in GLOB.admin_permission_names)
+ if(removed_rights & bit)
+ . += "[separator]-[GLOB.admin_permission_names[bit]]"
+ else if(extra_rights & bit)
+ . += "[separator]+[GLOB.admin_permission_names[bit]]"
+ else if(rank_rights & bit)
+ . += "[separator]+[GLOB.admin_permission_names[bit]]"
/proc/ui_style2icon(ui_style)
switch(ui_style)
diff --git a/code/_globalvars/lists/misc_lists.dm b/code/_globalvars/lists/misc_lists.dm
index 7ea33c03f53..208dfd7043c 100644
--- a/code/_globalvars/lists/misc_lists.dm
+++ b/code/_globalvars/lists/misc_lists.dm
@@ -73,3 +73,28 @@ GLOBAL_LIST_EMPTY(rnd_tech_id_to_name)
/// List of things that restrain teslas.
GLOBAL_LIST_EMPTY(tesla_containment)
+
+/// Assoc list of admin permission names
+GLOBAL_LIST_INIT(admin_permission_names, alist(
+ R_BUILDMODE = "BUILDMODE",
+ R_ADMIN = "ADMIN",
+ R_BAN = "BAN",
+ R_EVENT = "EVENT",
+ R_SERVER = "SERVER",
+ R_DEBUG = "DEBUG",
+ R_POSSESS = "POSSESS",
+ R_PERMISSIONS = "PERMISSIONS",
+ R_STEALTH = "STEALTH",
+ R_REJUVINATE = "REJUVINATE",
+ R_VAREDIT = "VAREDIT",
+ R_SOUNDS = "SOUNDS",
+ R_SPAWN = "SPAWN",
+ R_MOD = "MOD",
+ R_MENTOR = "MENTOR",
+ R_PROCCALL = "PROCCALL",
+ R_VIEWRUNTIMES = "VIEWRUNTIMES",
+ R_MAINTAINER = "MAINTAINER",
+ R_DEV_TEAM = "DEV_TEAM",
+ R_VIEWLOGS = "VIEWLOGS",
+))
+GLOBAL_PROTECT(admin_permission_names)
diff --git a/code/controllers/configuration/sections/admin_configuration.dm b/code/controllers/configuration/sections/admin_configuration.dm
index 9050bd79a5d..5a2f9bdccee 100644
--- a/code/controllers/configuration/sections/admin_configuration.dm
+++ b/code/controllers/configuration/sections/admin_configuration.dm
@@ -32,7 +32,7 @@
if(islist(data["admin_assignments"]))
ckey_rank_map.Cut()
for(var/list/kvset in data["admin_assignments"])
- ckey_rank_map[kvset["ckey"]] = kvset["rank"]
+ ckey_rank_map[ckey(kvset["ckey"])] = kvset["rank"]
// Load admin colours
if(islist(data["admin_rank_colour_map"]))
diff --git a/code/controllers/subsystem/tickets/SStickets.dm b/code/controllers/subsystem/tickets/SStickets.dm
index 2b90e822867..0471e684587 100644
--- a/code/controllers/subsystem/tickets/SStickets.dm
+++ b/code/controllers/subsystem/tickets/SStickets.dm
@@ -585,7 +585,7 @@ UI STUFF
for(var/client/X in GLOB.admins)
if(ckey(X.ckey) == ckey(T.client_ckey))
continue
- if(!check_rights_for(X, rights_needed))
+ if(!check_rights_client(rights_needed, FALSE, X))
continue
for(var/key in X.pm_tracker.pms)
if(ckey(key) != ckey(T.client_ckey))
diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm
index 83ce0497dcc..681aa24b619 100644
--- a/code/game/machinery/computer/communications.dm
+++ b/code/game/machinery/computer/communications.dm
@@ -12,7 +12,7 @@
#define COMM_MSGLEN_MINIMUM 6
#define COMM_CCMSGLEN_MINIMUM 20
-#define ADMIN_CHECK(user) (check_rights_all(R_ADMIN|R_EVENT, FALSE, user) && (authenticated >= COMM_AUTHENTICATION_CENTCOM || user.can_admin_interact()))
+#define ADMIN_CHECK(user) (check_rights(R_ADMIN|R_EVENT, FALSE, user, all = TRUE) && (authenticated >= COMM_AUTHENTICATION_CENTCOM || user.can_admin_interact()))
// The communications computer
/obj/machinery/computer/communications
@@ -56,7 +56,7 @@
. = ..()
/obj/machinery/computer/communications/proc/is_authenticated(mob/user, message = 1)
- if(check_rights_all(R_ADMIN|R_EVENT, FALSE, user))
+ if(check_rights(R_ADMIN|R_EVENT, FALSE, user, all = TRUE))
if(user.can_admin_interact())
return COMM_AUTHENTICATION_AGHOST
if(authenticated == COMM_AUTHENTICATION_CENTCOM)
@@ -109,7 +109,7 @@
if(ACCESS_CAPTAIN in access)
authenticated = COMM_AUTHENTICATION_CAPT
if(ACCESS_CENT_COMMANDER in access)
- if(!check_rights_all(R_ADMIN|R_EVENT, FALSE, ui.user))
+ if(!check_rights(R_ADMIN|R_EVENT, FALSE, ui.user, all = TRUE))
to_chat(ui.user, "[src] buzzes, invalid central command clearance.")
return
authenticated = COMM_AUTHENTICATION_CENTCOM
diff --git a/code/modules/admin/admin_ranks.dm b/code/modules/admin/admin_ranks.dm
index 596dc3251eb..8ce0d74bda3 100644
--- a/code/modules/admin/admin_ranks.dm
+++ b/code/modules/admin/admin_ranks.dm
@@ -48,41 +48,119 @@ GLOBAL_PROTECT(admin_ranks) // this shit is being protected for obvious reasons
testing(msg)
#endif
+/proc/reload_one_admin(admin_ckey, silent = FALSE)
+ if(IsAdminAdvancedProcCall())
+ to_chat(usr, "Admin reload blocked: Advanced ProcCall detected.")
+ message_admins("[key_name(usr)] attempted to reload an admin via advanced proc-call")
+ log_admin("[key_name(usr)] attempted to reload an admin via advanced proc-call")
+ return
+
+ // Make sure it's actually in ckey format.
+ admin_ckey = ckey(admin_ckey)
+
+ // Remove any existing permissions.
+ var/datum/admins/admin_datum = GLOB.admin_datums[admin_ckey]
+ if(admin_datum)
+ qdel(admin_datum)
+
+ if(admin_ckey in (GLOB.de_admins + GLOB.de_mentors))
+ if(!silent)
+ message_admins("Admin permissions for [admin_ckey] will reload when they re-admin.")
+ return
+
+ if(admin_ckey in GLOB.directory)
+ var/client/admin = GLOB.directory[admin_ckey]
+ var/localhosted = admin.try_localhost_autoadmin()
+ if(localhosted)
+ if(!silent)
+ message_admins("Admin permissions for [admin_ckey] reloaded. Full permissions granted as they are currently connected from localhost.")
+ return
+
+ if(GLOB.configuration.admin.use_database_admins)
+ var/datum/db_query/get_admin = SSdbcore.NewQuery({"
+ SELECT
+ -- Use the display_rank if set, otherwise the name of their permissions_rank.
+ IFNULL(admin.display_rank, admin_ranks.name),
+ -- Permissions start with their admin rank permissions (if any)
+ (IFNULL(admin_ranks.default_permissions, 0)
+ -- Then add in any extra permissions they've been granted.
+ | admin.extra_permissions)
+ -- And exclude any permissions they've had removed.
+ & ~admin.removed_permissions
+ FROM admin
+ -- We want all admins, and admin_ranks where available.
+ LEFT OUTER JOIN admin_ranks
+ ON admin.permissions_rank = admin_ranks.id
+ WHERE admin.ckey=:admin_ckey"}, list(
+ "admin_ckey" = admin_ckey
+ ))
+ if(!get_admin.warn_execute())
+ qdel(get_admin)
+ return
+
+ if(get_admin.NextRow())
+ var/rank = get_admin.item[1]
+ var/rights = get_admin.item[2]
+ if(rights == 0)
+ // If you have no rights, you don't get an admin datum.
+ qdel(get_admin)
+ if(!silent)
+ message_admins("Admin permissions for [admin_ckey] have been reloaded.")
+ return
+ admin_datum = new(rank, rights, admin_ckey)
+ qdel(get_admin)
+ else
+ var/rank = GLOB.configuration.admin.ckey_rank_map[admin_ckey]
+
+ // Load permissions associated with this rank
+ var/rights = GLOB.admin_ranks[rank]
+
+ // Create their admin datum.
+ admin_datum = new /datum/admins(rank, rights, admin_ckey)
+
+ // Set up their permissions.
+ if(admin_datum)
+ admin_datum.associate(GLOB.directory[admin_ckey])
+
+ if(admin_ckey in GLOB.directory)
+ var/client/admin = GLOB.directory[admin_ckey]
+ to_chat(admin, "Holder? [admin.holder] Permissions? [admin.holder?.rights]")
+ if(!silent)
+ message_admins("Admin permissions for [admin_ckey] have been reloaded.")
+
/proc/load_admins(run_async = FALSE)
if(IsAdminAdvancedProcCall())
to_chat(usr, "Admin reload blocked: Advanced ProcCall detected.")
message_admins("[key_name(usr)] attempted to reload admins via advanced proc-call")
log_admin("[key_name(usr)] attempted to reload admins via advanced proc-call")
return
- //clear the datums references
+
+ // Revoke all permissions.
+ var/list/localhost_admins = list()
+ for(var/datum/admins/admin_datum in GLOB.admin_datums)
+ if(admin_datum.is_localhost_autoadmin && admin_datum.owner)
+ localhost_admins += admin_datum.owner
+ qdel(admin_datum)
GLOB.admin_datums.Cut()
- for(var/client/C in GLOB.admins)
- C.hide_verbs()
- C.holder = null
GLOB.admins.Cut()
- // Remove all profiler access
+ // Just to be double-sure, revoke all profiler access
for(var/A in world.GetConfig("admin"))
world.SetConfig("APP/admin", A, null)
if(!GLOB.configuration.admin.use_database_admins)
load_admin_ranks()
- //process each line seperately
- for(var/iterator_key in GLOB.configuration.admin.ckey_rank_map)
- var/ckey = ckey(iterator_key) // Snip out formatting
- var/rank = GLOB.configuration.admin.ckey_rank_map[iterator_key]
+ for(var/ckey in GLOB.configuration.admin.ckey_rank_map)
+ var/rank = GLOB.configuration.admin.ckey_rank_map[ckey]
- //load permissions associated with this rank
+ // Load permissions associated with this rank
var/rights = GLOB.admin_ranks[rank]
- //create the admin datum and store it for later use
+ // Create their admin datum.
var/datum/admins/D = new /datum/admins(rank, rights, ckey)
- if(D.rights & R_DEBUG || D.rights & R_VIEWRUNTIMES) // Grants profiler access to anyone with R_DEBUG or R_VIEWRUNTIMES
- 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
+ // Set up their admin permissions.
D.associate(GLOB.directory[ckey])
else
@@ -93,34 +171,47 @@ GLOBAL_PROTECT(admin_ranks) // this shit is being protected for obvious reasons
load_admins()
return
- var/datum/db_query/query = SSdbcore.NewQuery("SELECT ckey, admin_rank, level, flags FROM admin")
- if(!query.warn_execute(async=run_async))
- qdel(query)
+ var/datum/db_query/get_admins = SSdbcore.NewQuery({"
+ SELECT
+ admin.ckey,
+ -- Use the display_rank if set, otherwise the name of their permissions_rank.
+ IFNULL(admin.display_rank, admin_ranks.name),
+ -- Permissions start with their admin rank permissions (if any)
+ (IFNULL(admin_ranks.default_permissions, 0)
+ -- Then add in any extra permissions they've been granted.
+ | admin.extra_permissions)
+ -- And exclude any permissions they've had removed.
+ & ~admin.removed_permissions
+ FROM admin
+ -- We want all admins, and admin_ranks where available.
+ LEFT OUTER JOIN admin_ranks
+ ON admin.permissions_rank = admin_ranks.id"})
+ if(!get_admins.warn_execute(async=run_async))
+ qdel(get_admins)
return
- 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)
+ while(get_admins.NextRow())
+ var/ckey = get_admins.item[1]
+ var/rank = get_admins.item[2]
+ var/rights = get_admins.item[3]
+ if(rights == 0)
+ // If you have no rights, you don't get an admin datum.
+ continue
var/datum/admins/D = new /datum/admins(rank, rights, ckey)
- if(D.rights & R_DEBUG || D.rights & R_VIEWRUNTIMES) // Grants profiler access to anyone with R_DEBUG or R_VIEWRUNTIMES
- 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
+ // Set up their admin permissions.
D.associate(GLOB.directory[ckey])
+ qdel(get_admins)
- qdel(query)
-
- if(!GLOB.admin_datums)
+ if(!length(GLOB.admin_datums))
log_world("The database query in load_admins() resulted in no admins being added to the list. Reverting to legacy system.")
GLOB.configuration.admin.use_database_admins = FALSE
load_admins()
return
+ for(var/client/localhost_admin in localhost_admins)
+ localhost_admin.try_localhost_autoadmin()
+
#ifdef TESTING
var/msg = "Admins Built:\n"
for(var/ckey in GLOB.admin_datums)
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index e86f94333a6..01cf86e37b6 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -879,119 +879,10 @@ GLOBAL_LIST_INIT(view_logs_verbs, list(
log_admin("[key_name(usr)] deadmined themself.")
message_admins("[key_name_admin(usr)] deadmined themself.")
- if(check_rights(R_ADMIN, FALSE))
- GLOB.de_admins += ckey
- else
- GLOB.de_mentors += ckey
deadmin()
- add_verb(src, /client/proc/readmin)
- update_active_keybindings()
to_chat(src, "You are now a normal player.")
SSblackbox.record_feedback("tally", "admin_verb", 1, "De-admin") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-/client/proc/readmin()
- set name = "Re-admin self"
- set category = "Admin"
- set desc = "Regain your admin powers."
-
- var/datum/admins/D = GLOB.admin_datums[ckey]
- var/rank = null
- if(!GLOB.configuration.admin.use_database_admins)
- for(var/iterator_key in GLOB.configuration.admin.ckey_rank_map)
- var/_ckey = ckey(iterator_key) // Snip out formatting
- if(ckey != _ckey)
- continue
- rank = GLOB.configuration.admin.ckey_rank_map[iterator_key]
- break
- else
- if(!SSdbcore.IsConnected())
- to_chat(src, "Warning, MYSQL database is not connected.")
- return
-
- var/datum/db_query/rank_read = SSdbcore.NewQuery(
- "SELECT admin_rank FROM admin WHERE ckey=:ckey",
- list("ckey" = ckey)
- )
-
- if(!rank_read.warn_execute())
- qdel(rank_read)
- return FALSE
-
- while(rank_read.NextRow())
- rank = ckeyEx(rank_read.item[1])
-
- qdel(rank_read)
- if(!D)
- D = try_localhost_autoadmin()
- if(!D)
- if(!GLOB.configuration.admin.use_database_admins)
- if(GLOB.admin_ranks[rank] == null)
- error("Error while re-adminning [src], admin rank ([rank]) does not exist.")
- to_chat(src, "Error while re-adminning, admin rank ([rank]) does not exist.")
- return
-
- // Do a little check here
- if(GLOB.configuration.system.is_production && (GLOB.admin_ranks[rank] & R_ADMIN) && prefs._2fa_status == _2FA_DISABLED) // If they are an admin and their 2FA is disabled
- to_chat(src,"You do not have 2FA enabled. Admin verbs will be unavailable until you have enabled 2FA.") // Very fucking obvious
- return
- D = new(rank, GLOB.admin_ranks[rank], ckey)
- else
- if(!SSdbcore.IsConnected())
- to_chat(src, "Warning, MYSQL database is not connected.")
- return
-
- var/datum/db_query/admin_read = SSdbcore.NewQuery(
- "SELECT ckey, admin_rank, flags FROM admin WHERE ckey=:ckey",
- list("ckey" = ckey)
- )
-
- if(!admin_read.warn_execute())
- qdel(admin_read)
- return FALSE
-
- while(admin_read.NextRow())
- var/admin_ckey = admin_read.item[1]
- var/admin_rank = admin_read.item[2]
- var/flags = admin_read.item[3]
- if(!admin_ckey)
- to_chat(src, "Error while re-adminning, ckey [admin_ckey] was not found in the admin database.")
- qdel(admin_read)
- return
- if(admin_rank == "Removed") //This person was de-adminned. They are only in the admin list for archive purposes.
- to_chat(src, "Error while re-adminning, ckey [admin_ckey] is not an admin.")
- qdel(admin_read)
- return
-
- if(istext(flags))
- flags = text2num(flags)
- var/client/check_client = GLOB.directory[ckey]
- // Do a little check here
- if(GLOB.configuration.system.is_production && (flags & R_ADMIN) && check_client.prefs._2fa_status == _2FA_DISABLED) // If they are an admin and their 2FA is disabled
- to_chat(src,"You do not have 2FA enabled. Admin verbs will be unavailable until you have enabled 2FA.") // Very fucking obvious
- qdel(admin_read)
- return
- D = new(admin_rank, flags, ckey)
- qdel(admin_read)
-
- var/client/C = GLOB.directory[ckey]
- D.associate(C)
- update_active_keybindings()
- message_admins("[key_name_admin(usr)] re-adminned themselves.")
- log_admin("[key_name(usr)] re-adminned themselves.")
- GLOB.de_admins -= ckey
- GLOB.de_mentors -= ckey
- if(istype(mob, /mob/dead/observer))
- var/mob/dead/observer/O = mob
- O.update_admin_actions()
- SSblackbox.record_feedback("tally", "admin_verb", 1, "Re-admin")
- return
- else
- to_chat(src, "You are already an admin.")
- remove_verb(src, /client/proc/readmin)
- GLOB.de_admins -= ckey
- GLOB.de_mentors -= ckey
- return
-
/client/proc/toggle_log_hrefs()
set name = "Toggle href logging"
set category = "Server"
diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm
index 4ecb375ecec..9f1ee097481 100644
--- a/code/modules/admin/holder2.dm
+++ b/code/modules/admin/holder2.dm
@@ -10,10 +10,15 @@ GLOBAL_PROTECT(href_token)
. += "[rand(10)]"
/datum/admins
- var/rank = "Temporary Admin"
+ var/rank = "No Rank"
+ var/ckey
var/client/owner
/// Bitflag containing the current rights this admin holder is assigned to
var/rights = 0
+ /// Tracks if their permissions have been reduced due to a lack of 2fa.
+ var/restricted_by_2fa = FALSE
+ /// Was this auto-created for someone connecting from localhost?
+ var/is_localhost_autoadmin = FALSE
var/fakekey
var/big_brother = FALSE
var/current_tab = 0
@@ -27,7 +32,7 @@ GLOBAL_PROTECT(href_token)
/// Our index into GLOB.antagonist_teams, so that admins can have pretty tabs in the Check Teams menu.
var/team_switch_tab_index = 1
-/datum/admins/New(initial_rank = "Temporary Admin", initial_rights = 0, ckey)
+/datum/admins/New(initial_rank, initial_rights, ckey)
if(IsAdminAdvancedProcCall())
to_chat(usr, "Admin rank creation blocked: Advanced ProcCall detected.")
message_admins("[key_name(usr)] attempted to create a new admin rank via advanced proc-call")
@@ -37,8 +42,11 @@ GLOBAL_PROTECT(href_token)
error("Admin datum created without a ckey argument. Datum has been deleted")
qdel(src)
return
- rank = initial_rank
- rights = initial_rights
+ src.ckey = ckey
+ if(initial_rank)
+ rank = initial_rank
+ if(initial_rights)
+ rights = initial_rights
href_token = GenerateToken()
GLOB.admin_datums[ckey] = src
@@ -48,22 +56,68 @@ GLOBAL_PROTECT(href_token)
message_admins("[key_name(usr)] attempted to delete an admin rank via advanced proc-call")
log_admin("[key_name(usr)] attempted to delete an admin rank via advanced proc-call")
return
+ disassociate()
..()
return QDEL_HINT_HARDDEL_NOW
-/datum/admins/proc/associate(client/C)
+/datum/admins/proc/associate(client/C, delay_2fa_complaint = FALSE)
if(IsAdminAdvancedProcCall())
to_chat(usr, "Rank association blocked: Advanced ProcCall detected.")
message_admins("[key_name(usr)] attempted to associate an admin rank to a new client via advanced proc-call")
log_admin("[key_name(usr)] attempted to associate an admin rank to a new client via advanced proc-call")
return
- if(istype(C))
- owner = C
- owner.holder = src
- owner.add_admin_verbs() //TODO
- remove_verb(owner, /client/proc/readmin)
- owner.init_verbs() //re-initialize the verb list
- GLOB.admins |= C
+
+ if(istype(C) && C.ckey != ckey)
+ CRASH("Admin datum belonging to [ckey] was asked to associate to client belonging to [C.ckey].")
+
+ owner = C
+
+ // Check for 2fa, if required.
+ if(needs_2fa() && istype(owner) && !owner.has_2fa())
+ // Disable most permissions.
+ rights = rights & (R_MENTOR | R_VIEWRUNTIMES)
+ restricted_by_2fa = TRUE
+ if(!delay_2fa_complaint)
+ // And tell them about it.
+ to_chat(owner,"You do not have 2FA enabled. Admin verbs will be unavailable until you have enabled 2FA.") // Very fucking obvious
+
+ // Regardless of client, tell BYOND if they should have profiler access.
+ if(rights & (R_DEBUG | R_VIEWRUNTIMES))
+ world.SetConfig("APP/admin", ckey, "role=admin")
+
+ if(!istype(owner))
+ return
+
+ owner.holder = src
+ owner.add_admin_verbs()
+ if(istype(owner.mob, /mob/dead/observer))
+ var/mob/dead/observer/ghost = owner.mob
+ ghost.update_admin_actions()
+ remove_verb(owner, /client/proc/readmin)
+ owner.init_verbs() //re-initialize the verb list
+ owner.update_active_keybindings()
+ if(rights & (R_DEBUG | R_VIEWRUNTIMES))
+ // Enable the buttons they should have.
+ winset(owner, "debugmcbutton", "is-disabled=true")
+ winset(owner, "profilecode", "is-disabled=true")
+ GLOB.admins |= owner
+ GLOB.de_admins -= ckey
+ GLOB.de_mentors -= ckey
+
+/datum/admins/proc/needs_2fa()
+ if((rights & (R_MENTOR | R_VIEWRUNTIMES)) == rights)
+ // No significant permissions.
+ return FALSE
+
+ if(GLOB.configuration.admin.enable_localhost_autoadmin && owner && owner.is_connecting_from_localhost())
+ // Localhost connections are immune.
+ return FALSE
+
+ if(!GLOB.configuration.system.is_production)
+ // 2fa isn't required for anyone.
+ return FALSE
+
+ return TRUE
/datum/admins/proc/disassociate()
if(IsAdminAdvancedProcCall())
@@ -71,72 +125,74 @@ GLOBAL_PROTECT(href_token)
message_admins("[key_name(usr)] attempted to disassociate an admin rank from a client via advanced proc-call")
log_admin("[key_name(usr)] attempted to disassociate an admin rank from a client via advanced proc-call")
return
+
+ GLOB.admin_datums -= ckey
+
+ // Regardless of client, tell BYOND they shouldn't have access to the profiler.
+ world.SetConfig("APP/admin", ckey, null)
+
if(owner)
GLOB.admins -= owner
owner.hide_verbs()
- owner.init_verbs()
owner.holder = null
+ owner.init_verbs()
+ if(istype(owner.mob, /mob/dead/observer))
+ var/mob/dead/observer/ghost = owner.mob
+ ghost.update_admin_actions()
+ owner.update_active_keybindings()
+ // Disable buttons they should no longer have.
+ winset(owner, "debugmcbutton", "is-disabled=true")
+ winset(owner, "profilecode", "is-disabled=true")
owner = null
/*
-checks if usr is an admin with at least ONE of the flags in rights_required. (Note, they don't need all the flags)
-if rights_required == 0, then it simply checks if they are an admin.
-if it doesn't return 1 and show_msg=1 it will prints a message explaining why the check has failed
-generally it would be used like so:
+Checks if usr is an admin.
+If rights_required == 0, then it simply checks if they are an admin.
+If all is set, requires all the rights listed. Otherwise, any matching right is sufficient.
+With show_msg, if the check fails, prints a message explaining why.
-proc/admin_proc()
- if(!check_rights(R_ADMIN)) return
+Usage:
+/some/proc()
+ if(!check_rights(R_ADMIN))
+ return
to_chat(world, "you have enough rights!")
-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.holder.rights & R_ADMIN) yourself.
+NOTE: checks usr by default, not src
*/
-/proc/check_rights(rights_required, show_msg = TRUE, mob/user = usr)
+/proc/check_rights(rights_required, show_msg = TRUE, mob/user = usr, all = FALSE)
if(user && user.client)
- if(rights_required)
- if(user.client.holder)
- if(rights_required & user.client.holder.rights)
- return 1
- else
- if(show_msg)
- to_chat(user, "Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")].")
- else
- if(user.client.holder)
- return 1
- else
- if(show_msg)
- to_chat(user, "Error: You are not an admin.")
- return 0
-
-// Basically the above proc but checks at a /client level
-/proc/check_rights_client(rights_required, show_msg = TRUE, client/C)
- if(C)
- if(rights_required)
- if(C.holder)
- if(rights_required & C.holder.rights)
- return TRUE
- else
- if(show_msg)
- to_chat(C, "Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")].")
- else
- if(C.holder)
- return TRUE
- else
- if(show_msg)
- to_chat(C, "Error: You are not an admin.")
+ return check_rights_ckey(rights_required, show_msg, user.ckey, all)
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.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, "Error: Cannot proceed. They have more or equal rights to us.")
- return 0
+// As above, for a /client
+/proc/check_rights_client(rights_required, show_msg, client/C, all = FALSE)
+ if(C)
+ return check_rights_ckey(rights_required, show_msg, C.ckey, all)
+ return FALSE
+
+// As above, for a ckey
+/proc/check_rights_ckey(rights_required, show_msg, ckey, all)
+ var/datum/admins/holder = GLOB.admin_datums[ckey]
+ if(!holder)
+ if(show_msg)
+ to_chat(GLOB.directory[ckey], "Error: You are not an admin.")
+ return FALSE
+
+ if(!rights_required)
+ return TRUE
+
+ if((rights_required & holder.rights) == rights_required)
+ return TRUE
+ else if(!all && (rights_required & holder.rights))
+ return TRUE
+
+ if(show_msg)
+ if(all)
+ to_chat(GLOB.directory[ckey], "Error: You do not have sufficient rights to do that. You require all of the following flags:[rights2text(rights_required," ")].")
+ else
+ to_chat(GLOB.directory[ckey], "Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")].")
+
+ return FALSE
/client/proc/deadmin()
if(IsAdminAdvancedProcCall())
@@ -144,45 +200,39 @@ you will have to do something like if(client.holder.rights & R_ADMIN) yourself.
message_admins("[key_name(usr)] attempted to de-admin a client via advanced proc-call")
log_admin("[key_name(usr)] attempted to de-admin a client via advanced proc-call")
return
- GLOB.admin_datums -= ckey
if(holder)
- holder.disassociate()
+ if(check_rights(R_ADMIN, show_msg = FALSE))
+ GLOB.de_admins += ckey
+ else
+ GLOB.de_mentors += ckey
qdel(holder)
- if(istype(mob, /mob/dead/observer))
- var/mob/dead/observer/O = mob
- O.update_admin_actions()
- return TRUE
+ add_verb(src, /client/proc/readmin)
+ else
+ to_chat(src, "Error: You were already not an admin.")
-//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
+/client/proc/readmin()
+ set name = "Re-admin self"
+ set category = "Admin"
+ set desc = "Regain your admin powers."
-/**
- * Requires the holder to have all the rights specified
- *
- * rights_required = R_ADMIN|R_EVENT means they must have both flags, or it will return false
- */
-/proc/check_rights_all(rights_required, show_msg = TRUE, mob/user = usr)
- if(!user?.client)
- return FALSE
- if(!rights_required)
- if(user.client.holder)
- return TRUE
- if(show_msg)
- to_chat(user, "Error: You are not an admin.")
- return FALSE
+ if(IsAdminAdvancedProcCall())
+ to_chat(usr, "Readmin blocked: Advanced ProcCall detected.")
+ message_admins("[key_name(usr)] attempted to re-admin a client via advanced proc-call")
+ log_admin("[key_name(usr)] attempted to re-admin a client via advanced proc-call")
- if(!user.client.holder)
- return FALSE
- if((user.client.holder.rights & rights_required) == rights_required)
- return TRUE
- if(show_msg)
- to_chat(user, "Error: You do not have sufficient rights to do that. You require all of the following flags:[rights2text(rights_required, " ")].")
- return FALSE
+ if(holder)
+ to_chat(usr, "You are already an admin.")
+ else if(ckey in (GLOB.de_admins + GLOB.de_mentors))
+ GLOB.de_admins -= ckey
+ GLOB.de_mentors -= ckey
+ reload_one_admin(ckey, silent = TRUE)
+ log_admin("[key_name(usr)] re-adminned themselves.")
+ message_admins("[key_name_admin(usr)] re-adminned themselves.")
+ SSblackbox.record_feedback("tally", "admin_verb", 1, "Re-admin") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+ else
+ to_chat(usr, "Readmin blocked: You were not an admin or mentor.")
+ message_admins("[key_name(usr)] attempted to re-admin without being an admin or mentor")
+ log_admin("[key_name(usr)] attempted to re-admin without being an admin or mentor")
/datum/admins/vv_edit_var(var_name, var_value)
return FALSE // no admin abuse
diff --git a/code/modules/admin/misc_admin_procs.dm b/code/modules/admin/misc_admin_procs.dm
index 46d03f32d04..f1251238bd5 100644
--- a/code/modules/admin/misc_admin_procs.dm
+++ b/code/modules/admin/misc_admin_procs.dm
@@ -1004,7 +1004,7 @@ GLOBAL_VAR_INIT(gamma_ship_location, 1) // 0 = station , 1 = space
/proc/staff_countup(rank_mask = R_BAN)
var/list/result = list(0, 0, 0)
for(var/client/X in GLOB.admins)
- if(rank_mask && !check_rights_for(X, rank_mask))
+ if(rank_mask && !check_rights_client(rank_mask, FALSE, X))
result[2]++
continue
if(X.holder.fakekey)
diff --git a/code/modules/admin/permissionverbs/permissionedit.dm b/code/modules/admin/permissionverbs/permissionedit.dm
index 4007266e95a..6be15acc9c5 100644
--- a/code/modules/admin/permissionverbs/permissionedit.dm
+++ b/code/modules/admin/permissionverbs/permissionedit.dm
@@ -13,6 +13,13 @@
var/datum/asset/permissions_asset = get_asset_datum(/datum/asset/simple/permissions)
permissions_asset.send(usr)
+ var/db_section = ""
+ if(db_available())
+ db_section = {"
+
Admin database active. All data is pulled from the database, and may require an admin reload to match actual permissions. Automatically-granted localhost admin permissions are not listed.