[ADMIN] Chganges auth backend to use the forums instead of the DB (#15195)

* [ADMIN] Chganges auth backend to use the forums instead of the DB

* Remove dbranks flag

* I'm dumb

* re-promotes myself

* Re-use datums, rather than continually re-making them

* Delete the datum instead, easier to manage

* Moved to an inhertiance based system for permissions management

* Proccall protection and logging

* Linter

* Update config

* Fixes pp I hope

* Two letters made it do a bad, I am sad

* Clears forums admins when reloading

* Adds db support

* Update config

* Re-enables forum integration

* No editing the funny datum

* Allow me to do the funny during the test merge

* Didn't commit the changes

* Copying and pasting hard

* Sanitize ckey

* Var is unnecessary

* Small debug log to debug dono chat

* Fixes wrong proc call

* Move log, will be a bit much, but is fine because its temporary

* Made better log message

* Fixed reload admins, added debug log to json_decode that was erroring

* Expanded forums integration error handling

* Fully protects funny lists
This commit is contained in:
adamsong
2022-08-28 09:26:46 -04:00
committed by GitHub
parent b3cfdabad7
commit 44cd06ecc6
63 changed files with 1552 additions and 1225 deletions

View File

@@ -37,8 +37,8 @@
#define R_SOUNDS (1<<11)
#define R_SPAWN (1<<12)
#define R_AUTOLOGIN (1<<13)
#define R_DBRANKS (1<<14)
#define R_DEV (1<<15) // Stuff NOONE should be touching except for head-dev/maints, I guess council too..
#define R_DEV (1<<14) // Stuff NOONE should be touching except for head-dev/maints, I guess council too..
#define R_PERSIST_PERMS (1<<15) // Allow modification of persistent perms
#define R_EVERYTHING (1<<16)-1 //the sum of all other rank permissions, used for +EVERYTHING
#define R_DEFAULT R_AUTOLOGIN

View File

@@ -294,7 +294,7 @@ GLOBAL_LIST_INIT(pda_styles, list(MONO, VT, ORBITRON, SHARE))
#define debug_usr(msg) if (GLOB.Debug2&&usr) to_chat(usr, \
type = MESSAGE_TYPE_DEBUG, \
text = "DEBUG: [msg]")
#define debug_admins(msg) if (GLOB.Debug2) to_chat(GLOB.admins, \
#define debug_admins(msg) if (GLOB.Debug2) to_chat(GLOB.permissions.admins, \
type = MESSAGE_TYPE_DEBUG, \
text = "DEBUG: [msg]")
#define debug_world_log(msg) if (GLOB.Debug2) log_world("DEBUG: [msg]")

View File

@@ -622,76 +622,6 @@
count++
return objective_parts.Join("<br>")
/datum/controller/subsystem/ticker/proc/save_admin_data()
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='admin prefix'>Admin rank DB Sync blocked: Advanced ProcCall detected.</span>")
return
if(CONFIG_GET(flag/admin_legacy_system)) //we're already using legacy system so there's nothing to save
return
else if(load_admins(TRUE)) //returns true if there was a database failure and the backup was loaded from
return
sync_ranks_with_db()
var/list/sql_admins = list()
for(var/i in GLOB.protected_admins)
var/datum/admins/A = GLOB.protected_admins[i]
sql_admins += list(list("ckey" = A.target, "rank" = A.rank.name))
SSdbcore.MassInsert(format_table_name("admin"), sql_admins, duplicate_key = TRUE)
var/datum/DBQuery/query_admin_rank_update = SSdbcore.NewQuery("UPDATE [format_table_name("player")] p INNER JOIN [format_table_name("admin")] a ON p.ckey = a.ckey SET p.lastadminrank = a.rank")
query_admin_rank_update.Execute()
qdel(query_admin_rank_update)
//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 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/i in GLOB.admin_datums+GLOB.deadmins)
var/datum/admins/A = GLOB.admin_datums[i]
if(!A)
A = GLOB.deadmins[i]
if (!A)
continue
file_data["admins"]["[i]"] = list()
file_data["admins"]["[i]"]["rank"] = A.rank.name
file_data["admins"]["[i]"]["ip_cache"] = A.ip_cache
file_data["admins"]["[i]"]["cid_cache"] = A.cid_cache
fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data))
/datum/controller/subsystem/ticker/proc/update_everything_flag_in_db()
for(var/datum/admin_rank/R 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/DBQuery/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/DBQuery/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)
/datum/controller/subsystem/ticker/proc/cargoking()
var/datum/achievement/cargoking/CK = SSachievements.get_achievement(/datum/achievement/cargoking)
var/cargoking = FALSE

View File

@@ -195,38 +195,38 @@
//Converts a rights bitfield into a string
/proc/rights2text(rights, seperator="", prefix = "+")
seperator += prefix
if(rights & R_BUILDMODE)
. += "[seperator]BUILDMODE"
if(rights & R_ADMIN)
. += "[seperator]ADMIN"
if(rights & R_AUTOLOGIN)
. += "[seperator]AUTOLOGIN"
if(rights & R_BAN)
. += "[seperator]BAN"
if(rights & R_FUN)
. += "[seperator]FUN"
if(rights & R_SERVER)
. += "[seperator]SERVER"
if(rights & R_BUILDMODE)
. += "[seperator]BUILDMODE"
if(rights & R_DEBUG)
. += "[seperator]DEBUG"
if(rights & R_POSSESS)
. += "[seperator]POSSESS"
if(rights & R_DEV)
. += "[seperator]DEV"
if(rights & R_FUN)
. += "[seperator]FUN"
if(rights & R_PERMISSIONS)
. += "[seperator]PERMISSIONS"
if(rights & R_STEALTH)
. += "[seperator]STEALTH"
if(rights & R_PERSIST_PERMS)
. += "[seperator]PERSISTPERMS"
if(rights & R_POLL)
. += "[seperator]POLL"
if(rights & R_VAREDIT)
. += "[seperator]VAREDIT"
if(rights & R_POSSESS)
. += "[seperator]POSSESS"
if(rights & R_SERVER)
. += "[seperator]SERVER"
if(rights & R_SOUNDS)
. += "[seperator]SOUND"
if(rights & R_SPAWN)
. += "[seperator]SPAWN"
if(rights & R_AUTOLOGIN)
. += "[seperator]AUTOLOGIN"
if(rights & R_DBRANKS)
. += "[seperator]DBRANKS"
if(rights & R_DEV)
. += "[seperator]DEV"
if(rights & R_STEALTH)
. += "[seperator]STEALTH"
if(rights & R_VAREDIT)
. += "[seperator]VAREDIT"
if(!.)
. = "NONE"
return .

View File

@@ -68,8 +68,8 @@ GLOBAL_LIST_INIT(bitfields, list(
"SOUNDS" = R_SOUNDS,
"SPAWN" = R_SPAWN,
"AUTOLOGIN" = R_AUTOLOGIN,
"DBRANKS" = R_DBRANKS,
"DEV" = R_DEV
"DEV" = R_DEV,
"PERSISTPERMS" = R_PERSIST_PERMS
),
"interaction_flags_atom" = list(
"INTERACT_ATOM_REQUIRES_ANCHORED" = INTERACT_ATOM_REQUIRES_ANCHORED,

View File

@@ -1,7 +1,4 @@
GLOBAL_LIST_EMPTY(clients) //all clients
GLOBAL_LIST_EMPTY(admins) //all clients whom are admins
GLOBAL_PROTECT(admins)
GLOBAL_LIST_EMPTY(deadmins) //all ckeys who have used the de-admin verb.
GLOBAL_LIST_EMPTY(directory) //all ckeys with associated client
GLOBAL_LIST_EMPTY(stealthminID) //reference list with IDs that store ckeys, for stealthmins

View File

@@ -161,9 +161,6 @@
min_val = 0 //oranges warned us
integer = FALSE
/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
@@ -173,7 +170,7 @@
/datum/config_entry/flag/enable_localhost_rank //Gives the !localhost! rank to any client connecting from 127.0.0.1 or ::1
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
/datum/config_entry/string/permissions_backend // Sets the permissions backend to use
protection = CONFIG_ENTRY_LOCKED
/datum/config_entry/string/hostedby

View File

@@ -59,20 +59,20 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
message_admins(span_adminnotice("Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks."))
--defcon
if(2)
to_chat(GLOB.admins, span_boldannounce("Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks."))
to_chat(GLOB.permissions.admins, span_boldannounce("Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks."))
--defcon
if(1)
to_chat(GLOB.admins, span_boldannounce("Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting..."))
to_chat(GLOB.permissions.admins, span_boldannounce("Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting..."))
--defcon
var/rtn = Recreate_MC()
if(rtn > 0)
defcon = 4
master_iteration = 0
to_chat(GLOB.admins, span_adminnotice("MC restarted successfully"))
to_chat(GLOB.permissions.admins, span_adminnotice("MC restarted successfully"))
else if(rtn < 0)
log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0")
to_chat(GLOB.admins, span_boldannounce("ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying."))
to_chat(GLOB.permissions.admins, span_boldannounce("ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying."))
//if the return number was 0, it just means the mc was restarted too recently, and it just needs some time before we try again
//no need to handle that specially when defcon 0 can handle it
if(0) //DEFCON 0! (mc failed to restart)
@@ -80,7 +80,7 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
if(rtn > 0)
defcon = 4
master_iteration = 0
to_chat(GLOB.admins, span_adminnotice("MC restarted successfully"))
to_chat(GLOB.permissions.admins, span_adminnotice("MC restarted successfully"))
else
defcon = min(defcon + 1,5)
master_iteration = Master.iteration

View File

@@ -152,7 +152,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
msg = "The [BadBoy.name] subsystem seems to be destabilizing the MC and will be offlined."
BadBoy.flags |= SS_NO_FIRE
if(msg)
to_chat(GLOB.admins, span_boldannounce("[msg]"))
to_chat(GLOB.permissions.admins, span_boldannounce("[msg]"))
log_world(msg)
if (istype(Master.subsystems))

View File

@@ -172,7 +172,7 @@
SEND_SIGNAL(src, COMSIG_SUBSYSTEM_POST_INITIALIZE, start_timeofday)
var/time = (REALTIMEOFDAY - start_timeofday)/10
var/msg = "Initialized [name] subsystem within [time] second[time == 1 ? "" : "s"]!" // Yogs -- quieter subsystem initialization
to_chat(GLOB.admins,
to_chat(GLOB.permissions.admins,
type = MESSAGE_TYPE_DEBUG,
html = span_notice(msg),
confidential = FALSE)

View File

@@ -395,7 +395,7 @@ SUBSYSTEM_DEF(air)
//Yogs start -- prettier atmos notices
var/msg = "HEY! LISTEN! [(world.timeofday - timer)/10] seconds were wasted processing [starting_ats] turf(s) (connected to [ending_ats] other turfs) with atmos differences at round start."
to_chat(GLOB.admins,
to_chat(GLOB.permissions.admins,
type = MESSAGE_TYPE_DEBUG,
html = span_notice(msg),
confidential = FALSE)

View File

@@ -738,5 +738,3 @@ SUBSYSTEM_DEF(ticker)
/datum/controller/subsystem/ticker/Shutdown()
gather_newscaster() //called here so we ensure the log is created even upon admin reboot
save_admin_data()
update_everything_flag_in_db()

View File

@@ -134,7 +134,7 @@ SUBSYSTEM_DEF(vote)
SSmapping.map_voted = TRUE
if(restart)
var/active_admins = FALSE
for(var/client/C in GLOB.admins + GLOB.deadmins)
for(var/client/C in GLOB.permissions.admins + GLOB.permissions.deadmins)
if(!C.is_afk() && check_rights_for(C, R_SERVER))
active_admins = TRUE
break
@@ -170,7 +170,7 @@ SUBSYSTEM_DEF(vote)
return FALSE
var/lower_admin = FALSE
var/ckey = ckey(initiator_key)
if(GLOB.admin_datums[ckey])
if(GLOB.permissions.admin_datums[ckey])
lower_admin = TRUE
if(!mode)

View File

@@ -610,7 +610,7 @@
continue //Ghosted while alive
for (var/C in GLOB.admins)
for (var/C in GLOB.permissions.admins)
to_chat(C, msg.Join())
log_admin(msg.Join())

View File

@@ -70,7 +70,7 @@
// Autoapproves after a certain time
rename_callback = CALLBACK(src, .proc/rename_station, new_name, user.name, user.real_name, key_name(user))
response_timer_id = addtimer(rename_callback, approval_time, TIMER_STOPPABLE)
to_chat(GLOB.admins,
to_chat(GLOB.permissions.admins,
span_adminnotice("<b><font color=orange>CUSTOM STATION RENAME:</font></b>[ADMIN_LOOKUPFLW(user)] proposes to rename the [name_type] to [new_name] (will auto-approve in [DisplayTimeText(approval_time)]).\
(<a HREF='?_src_=holder;[HrefToken(TRUE)];accept_custom_name=[REF(src)]'>ACCEPT</a> or <a HREF='?_src_=holder;[HrefToken(TRUE)];reject_custom_name=[REF(src)]'>REJECT</a>) [ADMIN_SMITE(user)] [ADMIN_CENTCOM_REPLY(user)]"))

View File

@@ -36,7 +36,9 @@ GLOBAL_VAR(restart_counter)
config.Load(params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER])
load_admins()
init_permissions()
GLOB.permissions.start()
//SetupLogs depends on the RoundID, so lets check
//DB schema and set RoundID if we can

View File

@@ -34,7 +34,7 @@
//magic voodo to check for a key in a list while also adding that key to the list without having to do two associated lookups
var/message = !checkedckeys[ckey]++
if(GLOB.admin_datums[ckey] || GLOB.deadmins[ckey])
if(GLOB.permissions.admin_datums[ckey] || GLOB.permissions.deadmins[ckey])
admin = TRUE
var/client/C = GLOB.directory[ckey]

View File

@@ -2,14 +2,14 @@
////////////////////////////////
/proc/message_admins(msg)
msg = span_admin("<span class=\"prefix\">ADMIN LOG:</span> <span class=\"message\">[msg]</span>")
to_chat(GLOB.admins,
to_chat(GLOB.permissions.admins,
type = MESSAGE_TYPE_ADMINLOG,
html = msg,
confidential = TRUE)
/proc/relay_msg_admins(msg)
msg = span_admin("<span class=\"prefix\">RELAY:</span> <span class=\"message\">[msg]</span>")
to_chat(GLOB.admins,
to_chat(GLOB.permissions.admins,
type = MESSAGE_TYPE_ADMINLOG,
html = msg,
confidential = TRUE)
@@ -42,7 +42,7 @@
body += "<body>Options panel for <b>[M]</b>"
if(M.client)
body += " played by <b>[M.client]</b> "
body += "\[<A href='?_src_=holder;[HrefToken()];editrights=[(GLOB.admin_datums[M.client.ckey] || GLOB.deadmins[M.client.ckey]) ? "rank" : "add"];key=[M.key]'>[M.client.holder ? M.client.holder.rank : "Player"]</A>\]"
body += "\[<A href='?_src_=holder;[HrefToken()];editrights=[(GLOB.permissions.admin_datums[M.client.ckey] || GLOB.permissions.deadmins[M.client.ckey]) ? "rank" : "add"];key=[M.key]'>[M.client.holder ? M.client.holder.rank_name() : "Player"]</A>\]"
if(CONFIG_GET(flag/use_exp_tracking))
body += "\[<A href='?_src_=holder;[HrefToken()];getplaytimewindow=[REF(M)]'>" + M.client.get_exp_living() + "</a> | "
body += " <A href='?_src_=holder;[HrefToken()];toggleexempt=[REF(M)]'>Toggle Exempt</a>\]"

View File

@@ -1,305 +0,0 @@
GLOBAL_LIST_EMPTY(admin_ranks) //list of all admin_rank datums
GLOBAL_PROTECT(admin_ranks)
GLOBAL_LIST_EMPTY(protected_ranks) //admin ranks loaded from txt
GLOBAL_PROTECT(protected_ranks)
/datum/admin_rank
var/name = "NoRank"
var/rights = R_DEFAULT
var/exclude_rights = 0
var/include_rights = 0
var/can_edit_rights = 0
/datum/admin_rank/New(init_name, init_rights, init_exclude_rights, init_edit_rights)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
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
/datum/admin_rank/Destroy()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return QDEL_HINT_LETMELIVE
. = ..()
/datum/admin_rank/vv_edit_var(var_name, var_value)
return FALSE
/proc/admin_keyword_to_flag(word, previous_rights=0)
var/flag = 0
switch(ckey(word))
if("buildmode","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","rights")
flag = R_PERMISSIONS
if("possess")
flag = R_POSSESS
if("stealth")
flag = R_STEALTH
if("poll")
flag = R_POLL
if("varedit")
flag = R_VAREDIT
if("everything","host","all")
flag = R_EVERYTHING
if("sound","sounds")
flag = R_SOUNDS
if("spawn","create")
flag = R_SPAWN
if("autologin", "autoadmin")
flag = R_AUTOLOGIN
if("dbranks")
flag = R_DBRANKS
if("dev")
flag = R_DEV
if("@","prev")
flag = previous_rights
return flag
// Adds/removes rights to this admin_rank
/datum/admin_rank/proc/process_keyword(word, previous_rights=0)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
var/flag = admin_keyword_to_flag(word, previous_rights)
if(flag)
switch(text2ascii(word,1))
if(43)
rights |= flag //+
include_rights |= flag
if(45)
rights &= ~flag //-
exclude_rights |= flag
if(42)
can_edit_rights |= flag //*
// Checks for (keyword-formatted) rights on this admin
/datum/admins/proc/check_keyword(word)
var/flag = admin_keyword_to_flag(word)
if(flag)
return ((rank.rights & flag) == flag) //true only if right has everything in flag
/proc/sync_ranks_with_db()
set waitfor = FALSE
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='admin prefix'>Admin rank DB Sync blocked: Advanced ProcCall detected.</span>", confidential=TRUE)
return
var/list/sql_ranks = list()
for(var/datum/admin_rank/R 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)
//load our rank - > rights associations
/proc/load_admin_ranks(dbfail, no_update)
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='admin prefix'>Admin Reload blocked: Advanced ProcCall detected.</span>", confidential=TRUE)
return
GLOB.admin_ranks.Cut()
GLOB.protected_ranks.Cut()
var/previous_rights = 0
//load text from file and process each line separately
for(var/line in world.file2list("[global.config.directory]/admin_ranks.txt"))
if(!line || findtextEx(line,"#",1,2) || line == " ") //YOGS - added our DB support
continue
var/next = findtext(line, "=")
var/datum/admin_rank/R = new(trim(copytext(line, 1, next)))
if(!R)
continue
GLOB.admin_ranks += R
GLOB.protected_ranks += R
var/prev = findchar(line, "+-*", next, 0)
while(prev)
next = findchar(line, "+-*", prev + 1, 0)
R.process_keyword(copytext(line, prev, next), previous_rights)
prev = next
previous_rights = R.rights
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/DBQuery/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 = 1
else
while(query_load_admin_ranks.NextRow())
var/skip
var/rank_name = trim(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/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
/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 = 1
//clear the datums references
GLOB.admin_datums.Cut()
for(var/client/C in GLOB.admins)
C.remove_admin_verbs()
C.holder = null
GLOB.admins.Cut()
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/list/lines = world.file2list("[global.config.directory]/admins.txt")
for(var/line in lines)
if(!length(line) || findtextEx(line, "#", 1, 2) || line == " ") //yogs - added our DB support
continue
var/list/entry = splittext(line, "=")
if(entry.len < 2)
continue
var/ckey = ckey(entry[1])
var/rank = trim(entry[2])
if(!ckey || !rank)
continue
new /datum/admins(rank_names[rank], ckey, 0, 1)
if(!CONFIG_GET(flag/admin_legacy_system) || dbfail)
var/datum/DBQuery/query_load_admins = SSdbcore.NewQuery("SELECT ckey, rank 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 = trim(query_load_admins.item[2])
var/skip
if(rank_names[admin_rank] == null)
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)
new /datum/admins(rank_names[admin_rank], admin_ckey)
qdel(query_load_admins)
//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/J in backup_file_json["admins"])
var/skip
for(var/A in GLOB.admin_datums + GLOB.deadmins)
if(A == "[J]") //this admin was already loaded from txt override
skip = TRUE
if(skip)
continue
var/datum/admins/A = new /datum/admins(trim(rank_names[backup_file_json["admins"]["[J]"]["rank"]]), ckey("[J]"))
A.ip_cache = backup_file_json["admins"]["[J]"]["ip_cache"]
A.cid_cache = backup_file_json["admins"]["[J]"]["cid_cache"]
#ifdef TESTING
var/msg = "Admins Built:\n"
for(var/ckey in GLOB.admin_datums)
var/datum/admins/D = GLOB.admin_datums[ckey]
msg += "\t[ckey] - [D.rank.name]\n"
testing(msg)
#endif
return dbfail
#ifdef TESTING
/client/verb/changerank(newrank in GLOB.admin_ranks)
if(holder)
holder.rank = newrank
else
holder = new /datum/admins(newrank, ckey)
remove_admin_verbs()
holder.associate(src)
/client/verb/changerights(newrights as num)
if(holder)
holder.rank.rights = newrights
else
holder = new /datum/admins("testing", newrights, ckey)
remove_admin_verbs()
holder.associate(src)
#endif

View File

@@ -262,7 +262,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
if(holder)
control_freak = CONTROL_FREAK_SKIN | CONTROL_FREAK_MACROS
var/rights = holder.rank.rights
var/rights = GLOB.permissions.get_rights_for(src)
add_verb(src, GLOB.admin_verbs_default)
add_verb(src, GLOB.mentor_verbs) // yogs - give admins mentor verbs
if(rights & R_BUILDMODE)
@@ -650,7 +650,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
/client/proc/togglebuildmodeself()
set name = "Toggle Build Mode Self"
set category = "Admin.Round Interaction"
if (!(holder.rank.rights & R_BUILDMODE))
if (!(check_rights(R_BUILDMODE)))
return
if(src.mob)
togglebuildmode(src.mob)
@@ -703,10 +703,10 @@ GLOBAL_PROTECT(admin_verbs_hideable)
set category = "Admin"
set desc = "Regain your admin powers."
var/datum/admins/A = GLOB.deadmins[ckey]
var/datum/admins/A = GLOB.permissions.deadmins[ckey]
if(!A)
A = GLOB.admin_datums[ckey]
A = GLOB.permissions.admin_datums[ckey]
if (!A)
var/msg = " is trying to readmin but they have no deadmin entry"
message_admins("[key_name_admin(src)][msg]")

View File

@@ -116,8 +116,7 @@ GLOBAL_LIST(round_end_notifiees)
/datum/tgs_chat_command/reload_admins/proc/ReloadAsync()
set waitfor = FALSE
refresh_admin_files() //yogs - DB support
load_admins()
GLOB.permissions.start()
/datum/tgs_chat_command/reload_mentors
name = "reload_mentors"
@@ -131,4 +130,4 @@ GLOBAL_LIST(round_end_notifiees)
/datum/tgs_chat_command/reload_mentors/proc/ReloadAsync()
set waitfor = FALSE
load_mentors()
load_mentors()

View File

@@ -1,13 +1,7 @@
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)
/datum/admins
var/datum/admin_rank/rank
var/target
var/name = "nobody's admin datum (no rank)" //Makes for better runtimes
@@ -33,7 +27,7 @@ GLOBAL_PROTECT(href_token)
var/cid_cache
/datum/admins/New(datum/admin_rank/R, ckey, force_active = FALSE, protected)
/datum/admins/New(ckey, rights, force_active = FALSE)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
@@ -45,20 +39,13 @@ GLOBAL_PROTECT(href_token)
if(!ckey)
QDEL_IN(src, 0)
CRASH("Admin datum created without a ckey")
if(!istype(R))
QDEL_IN(src, 0)
CRASH("Admin datum created without a rank")
target = ckey
name = "[ckey]'s admin datum ([R])"
rank = R
name = "[ckey]'s admin datum"
admin_signature = "Nanotrasen Officer #[rand(0,9)][rand(0,9)][rand(0,9)]"
href_token = GenerateToken()
if(R.rights & R_DEBUG) //grant profile access
if(rights & R_DEBUG) //grant profile access
world.SetConfig("APP/admin", ckey, "role=admin")
//only admins with +ADMIN start admined
if(protected)
GLOB.protected_admins[target] = src
if (force_active || (R.rights & R_AUTOLOGIN))
if (force_active || (rights & R_AUTOLOGIN))
activate()
else
deactivate()
@@ -69,6 +56,12 @@ GLOBAL_PROTECT(href_token)
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return QDEL_HINT_LETMELIVE
var/client/C = owner
deactivate()
if(GLOB.permissions.deadmins[target] == src)
GLOB.permissions.deadmins -= target
if(C)
remove_verb(C, /client/proc/readmin)
. = ..()
/datum/admins/proc/activate()
@@ -77,8 +70,8 @@ GLOBAL_PROTECT(href_token)
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
GLOB.deadmins -= target
GLOB.admin_datums[target] = src
GLOB.permissions.deadmins -= target
GLOB.permissions.admin_datums[target] = src
deadmined = FALSE
if (GLOB.directory[target])
associate(GLOB.directory[target]) //find the client for a ckey if they are connected and associate them with us
@@ -90,8 +83,8 @@ GLOBAL_PROTECT(href_token)
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
GLOB.deadmins[target] = src
GLOB.admin_datums -= target
GLOB.permissions.deadmins[target] = src
GLOB.permissions.admin_datums -= target
deadmined = TRUE
var/client/C
if ((C = owner) || (C = GLOB.directory[target]))
@@ -126,7 +119,7 @@ GLOBAL_PROTECT(href_token)
owner.add_admin_verbs() //TODO <--- todo what? the proc clearly exists and works since its the backbone to our entire admin system
remove_verb(owner, /client/proc/readmin)
owner.init_verbs() //re-initialize the verb list
GLOB.admins |= C
GLOB.permissions.admins |= C
return TRUE
/datum/admins/proc/disassociate()
@@ -136,71 +129,20 @@ GLOBAL_PROTECT(href_token)
log_admin("[key_name(usr)][msg]")
return
if(owner)
GLOB.admins -= owner
GLOB.permissions.admins -= owner
owner.remove_admin_verbs()
owner.init_verbs()
owner.holder = null
owner = null
/datum/admins/proc/check_for_rights(rights_required)
if(rights_required && !(rights_required & rank.rights))
return 0
return 1
/datum/admins/proc/check_if_greater_rights_than_holder(datum/admins/other)
if(!other)
return 1 //they have no rights
if(rank.rights == R_EVERYTHING)
return 1 //we have all the rights
if(src == other)
return 1 //you always have more rights than yourself
if(rank.rights != other.rank.rights)
if( (rank.rights & other.rank.rights) == other.rank.rights )
return 1 //we have all the rights they have and more
return 0
/datum/admins/proc/rank_name()
if(owner)
return GLOB.permissions.get_rank_name(owner)
return "Unknown"
/datum/admins/vv_edit_var(var_name, var_value)
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)
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:
/proc/admin_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.rights & R_ADMIN) yourself.
*/
/proc/check_rights(rights_required, show_msg=1)
if(usr && usr.client)
if (check_rights_for(usr.client, rights_required))
return 1
else
if(show_msg)
to_chat(usr, "<font color='red'>Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")].</font>", confidential=TRUE)
return 0
//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
return usr.client.holder.check_if_greater_rights_than_holder(other.holder)
return 0
//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)
return subject.holder.check_for_rights(rights_required)
return 0
/proc/GenerateToken()
. = ""
for(var/I in 1 to 32)

View File

@@ -21,7 +21,7 @@
/client/proc/mfa_check_cache()
CHECK_MFA_ENABLED
var/datum/admins/tmp_holder = GLOB.admin_datums[ckey] || GLOB.deadmins[ckey]
var/datum/admins/tmp_holder = GLOB.permissions.admin_datums[ckey] || GLOB.permissions.deadmins[ckey]
if(tmp_holder && tmp_holder.cid_cache == computer_id && tmp_holder.ip_cache == address)
return TRUE
@@ -282,7 +282,7 @@
qdel(mfa_addverify)
var/datum/admins/tmp_holder = GLOB.admin_datums[ckey] || GLOB.deadmins[ckey]
var/datum/admins/tmp_holder = GLOB.permissions.admin_datums[ckey] || GLOB.permissions.deadmins[ckey]
if(tmp_holder)
// These values are cached even if the user says not to remember the session, but are only used if the DB is down during admin loading
tmp_holder.cid_cache = computer_id

View File

@@ -4,296 +4,251 @@
set desc = "Edit admin permissions"
if(!check_rights(R_PERMISSIONS))
return
usr.client.holder.edit_admin_permissions()
new /datum/permissions_panel(usr)
/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("<link rel='stylesheet' type='text/css' href='[SSassets.transport.get_asset_url("panels.css")]'><a href='?_src_=holder;[HrefToken()];editrightsbrowser=1'>\[Permissions\]</a>")
if(action)
output += " | <a href='?_src_=holder;[HrefToken()];editrightsbrowserlog=1;editrightspage=0'>\[Log\]</a> | <a href='?_src_=holder;[HrefToken()];editrightsbrowsermanage=1'>\[Management\]</a><hr style='background:#000000; border:0; height:3px'>"
else
output += "<br><a href='?_src_=holder;[HrefToken()];editrightsbrowserlog=1;editrightspage=0'>\[Log\]</a><br><a href='?_src_=holder;[HrefToken()];editrightsbrowsermanage=1'>\[Management\]</a>"
if(action == 1)
var/logcount = 0
var/logssperpage = 20
var/pagecount = 0
page = text2num(page)
var/datum/DBQuery/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 += "<br><b>Page: </b>"
while(logcount > 0)
output += "|<a href='?_src_=holder;[HrefToken()];editrightsbrowserlog=1;editrightstarget=[target];editrightsoperation=[operation];editrightspage=[pagecount]'>[pagecount == page ? "<b>\[[pagecount]\]</b>" : "\[[pagecount]\]"]</a>"
logcount -= logssperpage
pagecount++
output += "|"
var/datum/DBQuery/query_search_admin_logs = SSdbcore.NewQuery({"
SELECT
datetime,
round_id,
IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey),
operation,
IF(ckey IS NULL, target, byond_key),
log
FROM [format_table_name("admin_log")]
LEFT JOIN [format_table_name("player")] ON target = ckey
WHERE (:target 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 += "<p style='margin:0px'><b>[datetime] | Round ID [round_id] | Admin [admin_key] | Operation [operation] on [target]</b><br>[log]</p><hr style='background:#000000; border:0; height:3px'>"
qdel(query_search_admin_logs)
if(action == 2)
output += "<h3>Admin ckeys with invalid ranks</h3>"
var/datum/DBQuery/query_check_admin_errors = SSdbcore.NewQuery("SELECT (SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("admin")].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] | <a href='?_src_=holder;[HrefToken()];editrightsbrowsermanage=1;editrightschange=[admin_key]'>\[Change Rank\]</a> | <a href='?_src_=holder;[HrefToken()];editrightsbrowsermanage=1;editrightsremove=[admin_key]'>\[Remove\]</a>"
output += "<hr style='background:#000000; border:0; height:1px'>"
qdel(query_check_admin_errors)
output += "<h3>Unused ranks</h3>"
var/datum/DBQuery/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 | <a href='?_src_=holder;[HrefToken()];editrightsbrowsermanage=1;editrightsremoverank=[admin_rank]'>\[Remove\]</a>
<br>Permissions: [rights2text(text2num(query_check_unused_rank.item[2])," ")]
<br>Denied: [rights2text(text2num(query_check_unused_rank.item[3])," ", "-")]
<br>Allowed to edit: [rights2text(text2num(query_check_unused_rank.item[4])," ", "*")]
<hr style='background:#000000; border:0; height:1px'>"}
qdel(query_check_unused_rank)
else if(!action)
output += {"<head>
<meta charset='UTF-8'>
<title>Permissions Panel</title>
<script type='text/javascript' src='[SSassets.transport.get_asset_url("search.js")]'></script>
</head>
<body onload='selectTextField();updateSearch();'>
<div id='main'><table id='searchable' cellspacing='0'>
<tr class='title'>
<th style='width:150px;'>CKEY <a class='small' href='?src=[REF(src)];[HrefToken()];editrights=add'>\[+\]</a></th>
<th style='width:125px;'>RANK</th>
<th style='width:40%;'>PERMISSIONS</th>
<th style='width:20%;'>DENIED</th>
<th style='width:40%;'>ALLOWED TO EDIT</th>
</tr>
"}
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 = " <a class='small' href='?src=[REF(src)];[HrefToken()];editrights=activate;key=[adm_ckey]'>\[RA\]</a>"
else
deadminlink = " <a class='small' href='?src=[REF(src)];[HrefToken()];editrights=deactivate;key=[adm_ckey]'>\[DA\]</a>"
output += "<tr>"
output += "<td style='text-align:center;'>[adm_ckey]<br>[deadminlink]<a class='small' href='?src=[REF(src)];[HrefToken()];editrights=remove;key=[adm_ckey]'>\[-\]</a><a class='small' href='?src=[REF(src)];[HrefToken()];editrights=mfareset;key=[adm_ckey]'>\[2FA\]</a><br><a class='small' href='?src=[REF(src)];[HrefToken()];editrights=sync;key=[adm_ckey]'>\[SYNC TGDB\]</a></td>"
output += "<td><a href='?src=[REF(src)];[HrefToken()];editrights=rank;key=[adm_ckey]'>[D.rank.name]</a></td>"
output += "<td><a class='small' href='?src=[REF(src)];[HrefToken()];editrights=permissions;key=[adm_ckey]'>[rights2text(D.rank.include_rights," ")]</a></td>"
output += "<td><a class='small' href='?src=[REF(src)];[HrefToken()];editrights=permissions;key=[adm_ckey]'>[rights2text(D.rank.exclude_rights," ", "-")]</a></td>"
output += "<td><a class='small' href='?src=[REF(src)];[HrefToken()];editrights=permissions;key=[adm_ckey]'>[rights2text(D.rank.can_edit_rights," ", "*")]</a></td>"
output += "</tr>"
output += "</table></div><div id='top'><b>Search:</b> <input type='text' id='filter' value='' style='width:70%;' onkeyup='updateSearch();'></div></body>"
if(QDELETED(usr))
return
usr << browse("<!DOCTYPE html><html>[jointext(output, "")]</html>","window=editrights;size=1000x650")
/datum/permissions_panel/New(mob/user)
ui_interact(user)
/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.")
/datum/permissions_panel/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
// Open UI
ui = new(user, src, "PermissionsPanel")
ui.open()
/datum/permissions_panel/ui_data(mob/user)
. = GLOB.permissions.pp_data(user)
/datum/permissions_panel/ui_state(mob/user)
return GLOB.permissions_state
/datum/permissions_panel/ui_act(action, list/params)
if(..())
return
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='admin prefix'>Admin Edit blocked: Advanced ProcCall detected.</span>", confidential=TRUE)
return
if(!owner.mfa_query())
return
var/datum/asset/permissions_assets = get_asset_datum(/datum/asset/simple/namespaced/common)
permissions_assets.send(src)
var/admin_key = href_list["key"]
var/admin_ckey = ckey(admin_key)
var/datum/admins/D = GLOB.admin_datums[admin_ckey]
var/use_db
var/task = href_list["editrights"]
var/skip
var/legacy_only
if(task == "activate" || task == "deactivate" || task == "sync" || task == "mfareset")
skip = TRUE
if(!CONFIG_GET(flag/admin_legacy_system) && CONFIG_GET(flag/protect_legacy_admins) && task == "rank")
if(admin_ckey in GLOB.protected_admins)
to_chat(usr, "<span class='admin prefix'>Editing the rank of this admin is blocked by server configuration.</span>", confidential=TRUE)
return
if(!CONFIG_GET(flag/admin_legacy_system) && CONFIG_GET(flag/protect_legacy_ranks) && task == "permissions")
if(D.rank in GLOB.protected_ranks)
to_chat(usr, "<span class='admin prefix'>Editing the flags of this rank is blocked by server configuration.</span>", confidential=TRUE)
return
if(CONFIG_GET(flag/load_legacy_ranks_only) && (task == "add" || task == "rank" || task == "permissions"))
to_chat(usr, "<span class='admin prefix'>Database rank loading is disabled, only temporary changes can be made to a rank's permissions and permanently creating a new rank is blocked.</span>", 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 = alert("Permanent changes are saved to the database for future rounds, temporary changes will affect only the current round", "Permanent or Temporary?", "Permanent", "Temporary", "Cancel")
if(use_db == "Cancel")
return
if(use_db == "Permanent")
use_db = TRUE
else
use_db = FALSE
if(QDELETED(usr))
switch(action)
if("addNewAdmin")
GLOB.permissions.add_admin()
return TRUE
if("forceSwapAdmin")
var/ckey = params["ckey"]
var/adminned_datum = GLOB.permissions.admin_datums[ckey]
var/deadminned_datum = GLOB.permissions.deadmins[ckey]
if(adminned_datum)
force_deadmin(ckey, adminned_datum)
if(deadminned_datum)
force_readmin(ckey, deadminned_datum)
return TRUE
if("resetMFA")
var/admin_ckey = params["ckey"]
if(alert("WARNING! This will reset the 2FA code and backup for [admin_ckey], possibly comprimising the security of the server. Are you sure you wish to continue?", "Confirmation", "Cancel", "Continue") != "Continue")
return
if(task != "add")
D = GLOB.admin_datums[admin_ckey]
if(!D)
D = GLOB.deadmins[admin_ckey]
if(!D)
return
if((task != "sync") && !check_if_greater_rights_than_holder(D))
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
change_admin_rank(admin_ckey, admin_key, use_db, null, legacy_only)
if("remove")
remove_admin(admin_ckey, admin_key, use_db, D)
if("rank")
change_admin_rank(admin_ckey, admin_key, use_db, D, legacy_only)
if("permissions")
change_admin_flags(admin_ckey, admin_key, use_db, D, legacy_only)
if("activate")
force_readmin(admin_key, D)
if("deactivate")
force_deadmin(admin_key, D)
if("sync")
sync_lastadminrank(admin_ckey, admin_key, D)
if("mfareset")
if(alert("WARNING! This will reset the 2FA code and backup for [admin_key], possibly comprimising the security of the server. Are you sure you wish to continue?", "Confirmation", "Cancel", "Continue") != "Continue")
return
if(alert("If you have been requested to reset the MFA credentials for someone, please confirm that you have verified their identity. Resetting MFA for an unverified person can result in a break of server security.", "Confirmation", "I Understand", "Cancel") != "I Understand")
if(alert("If you have been requested to reset the MFA credentials for someone, please confirm that you have verified their identity. Resetting MFA for an unverified person can result in a breach of server security.", "Confirmation", "I Understand", "Cancel") != "I Understand")
return
message_admins("MFA for [admin_ckey] has been reset by [usr]!")
log_admin("MFA Reset for [admin_ckey] by [usr]!")
mfa_reset(admin_ckey)
edit_admin_permissions()
return TRUE
if("editRank")
GLOB.permissions.edit_rank(params["ckey"])
return TRUE
if("editPerms")
GLOB.permissions.edit_perms(params["ckey"])
return TRUE
if("removeAdmin")
GLOB.permissions.remove_admin(params["ckey"])
return TRUE
/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/DBQuery/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/DBQuery/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/DBQuery/query_add_admin_log = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log)
VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'add admin', :target, CONCAT('New admin added: ', :target))
"}, list("time" = SQLtime(), "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/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("<link rel='stylesheet' type='text/css' href='[SSassets.transport.get_asset_url("panels.css")]'><a href='?_src_=holder;[HrefToken()];editrightsbrowser=1'>\[Permissions\]</a>")
// if(action)
// output += " | <a href='?_src_=holder;[HrefToken()];editrightsbrowserlog=1;editrightspage=0'>\[Log\]</a><hr style='background:#000000; border:0; height:3px'>"
// else
// output += "<br><a href='?_src_=holder;[HrefToken()];editrightsbrowserlog=1;editrightspage=0'>\[Log\]</a>"
// if(action == 1)
// var/logcount = 0
// var/logssperpage = 20
// var/pagecount = 0
// page = text2num(page)
// var/datum/DBQuery/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 += "<br><b>Page: </b>"
// while(logcount > 0)
// output += "|<a href='?_src_=holder;[HrefToken()];editrightsbrowserlog=1;editrightstarget=[target];editrightsoperation=[operation];editrightspage=[pagecount]'>[pagecount == page ? "<b>\[[pagecount]\]</b>" : "\[[pagecount]\]"]</a>"
// logcount -= logssperpage
// pagecount++
// output += "|"
// var/datum/DBQuery/query_search_admin_logs = SSdbcore.NewQuery({"
// SELECT
// datetime,
// round_id,
// IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey),
// operation,
// IF(ckey IS NULL, target, byond_key),
// log
// FROM [format_table_name("admin_log")]
// LEFT JOIN [format_table_name("player")] ON target = ckey
// WHERE (:target 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 += "<p style='margin:0px'><b>[datetime] | Round ID [round_id] | Admin [admin_key] | Operation [operation] on [target]</b><br>[log]</p><hr style='background:#000000; border:0; height:3px'>"
// qdel(query_search_admin_logs)
// else if(!action)
// output += {"<head>
// <meta charset='UTF-8'>
// <title>Permissions Panel</title>
// <script type='text/javascript' src='[SSassets.transport.get_asset_url("search.js")]'></script>
// </head>
// <body onload='selectTextField();updateSearch();'>
// <div id='main'><table id='searchable' cellspacing='0'>
// <tr class='title'>
// <th style='width:150px;'>CKEY <a class='small' href='?src=[REF(src)];[HrefToken()];editrights=add'>\[+\]</a></th>
// <th style='width:125px;'>RANK</th>
// <th style='width:40%;'>PERMISSIONS</th>
// </tr>
// "}
// for(var/adm_ckey in GLOB.permissions.admin_datums+GLOB.permissions.deadmins)
// var/datum/admins/D = GLOB.permissions.admin_datums[adm_ckey]
// if(!D)
// D = GLOB.permissions.deadmins[adm_ckey]
// if (!D)
// continue
// var/deadminlink = ""
// if(D.owner)
// adm_ckey = D.owner.key
// if (D.deadmined)
// deadminlink = " <a class='small' href='?src=[REF(src)];[HrefToken()];editrights=activate;key=[adm_ckey]'>\[RA\]</a>"
// else
// deadminlink = " <a class='small' href='?src=[REF(src)];[HrefToken()];editrights=deactivate;key=[adm_ckey]'>\[DA\]</a>"
// output += "<tr>"
// output += "<td style='text-align:center;'>[adm_ckey]<br>[deadminlink]<a class='small' href='?src=[REF(src)];[HrefToken()];editrights=remove;key=[adm_ckey]'>\[-\]</a><a class='small' href='?src=[REF(src)];[HrefToken()];editrights=mfareset;key=[adm_ckey]'>\[2FA\]</a></td>"
// output += "<td><a href='?src=[REF(src)];[HrefToken()];editrights=rank;key=[adm_ckey]'>[D.rank_name()]</a></td>"
// output += "<td><a class='small' href='?src=[REF(src)];[HrefToken()];editrights=permissions;key=[adm_ckey]'>[rights2text(D.rights," ")]</a></td>"
// output += "</tr>"
// output += "</table></div><div id='top'><b>Search:</b> <input type='text' id='filter' value='' style='width:70%;' onkeyup='updateSearch();'></div></body>"
// if(QDELETED(usr))
// return
// usr << browse("<!DOCTYPE html><html>[jointext(output, "")]</html>","window=editrights;size=1000x650")
/datum/admins/proc/remove_admin(admin_ckey, admin_key, use_db, datum/admins/D)
if(alert("Are you sure you want to remove [admin_ckey]?","Confirm Removal","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/DBQuery/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/DBQuery/query_add_rank_log = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log)
VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'remove admin', :admin_ckey, CONCAT('Admin removed: ', :admin_ckey))
"}, list("time" = SQLtime(), "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/edit_rights_topic(list/href_list)
// if(!check_rights(R_PERMISSIONS))
// message_admins("[key_name_admin(usr)] attempted to edit admin permissions without sufficient rights.")
// log_admin("[key_name(usr)] attempted to edit admin permissions without sufficient rights.")
// return
// if(IsAdminAdvancedProcCall())
// to_chat(usr, "<span class='admin prefix'>Admin Edit blocked: Advanced ProcCall detected.</span>", confidential=TRUE)
// return
// if(!owner.mfa_query())
// return
// var/datum/asset/permissions_assets = get_asset_datum(/datum/asset/simple/namespaced/common)
// permissions_assets.send(owner)
// var/admin_key = href_list["key"]
// var/admin_ckey = ckey(admin_key)
// var/datum/admins/D = GLOB.permissions.admin_datums[admin_ckey]
// var/task = href_list["editrights"]
// if(CONFIG_GET(flag/protect_legacy_admins) && task == "rank")
// if(admin_ckey in GLOB.protected_admins)
// to_chat(usr, "<span class='admin prefix'>Editing the rank of this admin is blocked by server configuration.</span>", confidential=TRUE)
// return
// if(task != "add")
// D = GLOB.permissions.admin_datums[admin_ckey]
// if(!D)
// D = GLOB.permissions.deadmins[admin_ckey]
// if(!D)
// return
// switch(task)
// if("add")
// admin_key = input("New admin's key","Admin key") as text|null
// admin_ckey = ckey(admin_key)
// if(admin_ckey in GLOB.permissions.admin_datums+GLOB.permissions.deadmins)
// to_chat(usr, span_danger("[admin_key] is already an admin."), confidential=TRUE)
// return
// change_admin_rank(admin_ckey, admin_key, null)
// if("remove")
// remove_admin(admin_ckey, admin_key, D)
// if("rank")
// change_admin_rank(admin_ckey, admin_key, D)
// if("permissions")
// change_admin_flags(admin_ckey, admin_key, D)
// if("activate")
// force_readmin(admin_key, D)
// if("deactivate")
// force_deadmin(admin_key, D)
// if("mfareset")
// if(alert("WARNING! This will reset the 2FA code and backup for [admin_key], possibly comprimising the security of the server. Are you sure you wish to continue?", "Confirmation", "Cancel", "Continue") != "Continue")
// return
// if(alert("If you have been requested to reset the MFA credentials for someone, please confirm that you have verified their identity. Resetting MFA for an unverified person can result in a break of server security.", "Confirmation", "I Understand", "Cancel") != "I Understand")
// return
// message_admins("MFA for [admin_ckey] has been reset by [usr]!")
// log_admin("MFA Reset for [admin_ckey] by [usr]!")
// mfa_reset(admin_ckey)
// edit_admin_permissions()
/datum/admins/proc/force_readmin(admin_key, datum/admins/D)
// /datum/admins/proc/add_admin(admin_ckey, admin_key)
// 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.permissions.admin_datums+GLOB.permissions.deadmins))
// to_chat(usr, span_danger("[admin_key] is already an admin."), confidential=TRUE)
// return FALSE
// /datum/admins/proc/remove_admin(admin_ckey, admin_key, datum/admins/D)
// if(alert("Are you sure you want to remove [admin_ckey]?","Confirm Removal","Do it","Cancel") == "Do it")
// GLOB.permissions.admin_datums -= admin_ckey
// GLOB.permissions.deadmins -= admin_ckey
// if(D)
// D.disassociate()
// var/m1 = "[key_name_admin(usr)] removed [admin_key] from the admins list temporarily"
// var/m2 = "[key_name(usr)] removed [admin_key] from the admins list temporarily"
// message_admins(m1)
// log_admin(m2)
/datum/permissions_panel/proc/force_readmin(admin_key, datum/admins/D)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
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)
/datum/permissions_panel/proc/force_deadmin(admin_key, datum/admins/D)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(!D || D.deadmined)
return
message_admins("[key_name_admin(usr)] forcefully deadmined [admin_key]")
@@ -301,7 +256,12 @@
D.deactivate() //after logs so the deadmined admin can see the message.
/datum/admins/proc/auto_deadmin()
if(GLOB.admins.len < CONFIG_GET(number/auto_deadmin_threshold))
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(GLOB.permissions.admins.len < CONFIG_GET(number/auto_deadmin_threshold))
log_admin("[owner] auto-deadmin failed due to low admin count.")
to_chat(owner, span_userdanger("You have not be auto-deadminned due to lack of admins on the server, you can still deadmin manually."))
return FALSE
@@ -312,238 +272,38 @@
log_admin("[old_owner] deadmined via auto-deadmin config.")
return TRUE
/datum/admins/proc/change_admin_rank(admin_ckey, admin_key, use_db, datum/admins/D, legacy_only)
if(!check_rights(R_PERMISSIONS))
return
var/datum/admin_rank/R
var/list/rank_names = list()
if(!use_db || (use_db && !legacy_only))
rank_names += "*New Rank*"
for(R in GLOB.admin_ranks)
if((R.rights & usr.client.holder.rank.can_edit_rights) == R.rights)
rank_names[R.name] = R
var/new_rank = input("Please select a rank", "New rank") as null|anything in rank_names
if(new_rank == "*New Rank*")
new_rank = trim(input("Please input a new rank", "New custom rank") as text|null)
if(!new_rank)
if(!D)
remove_admin(admin_ckey, admin_key, use_db, null)
return
R = rank_names[new_rank]
if(!R) //rank with that name doesn't exist yet - make it
if(D)
R = new(new_rank, D.rank.rights) //duplicate our previous admin_rank but with a new name
else
R = new(new_rank) //blank new admin_rank
GLOB.admin_ranks += R
var/m1 = "[key_name_admin(usr)] edited the admin rank of [admin_key] to [new_rank] [use_db ? "permanently" : "temporarily"]"
var/m2 = "[key_name(usr)] edited the admin rank of [admin_key] to [new_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/DBQuery/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)
//similarly if a temp rank is created it won't be in the db if someone is permanently changed to it
// /datum/admins/proc/change_admin_rank(admin_ckey, admin_key, datum/admins/D)
// if(!check_rights(R_PERMISSIONS))
// return
// var/list/rank_names = list()
// rank_names += "*New Rank*"
// for(var/R in GLOB.legacy_ranks)
// rank_names += R
// var/new_rank = input("Please select a rank", "New rank") as null|anything in rank_names
// if(new_rank == "*New Rank*")
// new_rank = trim(input("Please input a new rank", "New custom rank") as text|null)
// if(!new_rank)
// return
// D.rank_name = new_rank
// if(new_rank in GLOB.legacy_ranks)
// D.rights = GLOB.legacy_ranks[new_rank]
// var/client/C = D.owner
// D.disassociate()
// D.associate(C)
// var/m1 = "[key_name_admin(usr)] edited the admin rank of [admin_key] to [new_rank] temporarily"
// var/m2 = "[key_name(usr)] edited the admin rank of [admin_key] to [new_rank] temporarily"
// message_admins(m1)
// log_admin(m2)
var/datum/DBQuery/query_rank_in_db = SSdbcore.NewQuery(
"SELECT 1 FROM [format_table_name("admin_ranks")] WHERE `rank` = :new_rank",
list("new_rank" = new_rank)
)
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/DBQuery/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" = new_rank))
if(!query_add_rank.warn_execute())
qdel(query_add_rank)
return
qdel(query_add_rank)
var/datum/DBQuery/query_add_rank_log = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log)
VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'add rank', :new_rank, CONCAT('New rank added: ', :new_rank))
"}, list("time" = SQLtime(), "round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "new_rank" = new_rank))
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/DBQuery/query_change_rank = SSdbcore.NewQuery(
"UPDATE [format_table_name("admin")] SET `rank` = :new_rank WHERE ckey = :admin_ckey",
list("new_rank" = new_rank, "admin_ckey" = admin_ckey)
)
if(!query_change_rank.warn_execute())
qdel(query_change_rank)
return
qdel(query_change_rank)
var/datum/DBQuery/query_change_rank_log = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log)
VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'change admin rank', :target, CONCAT('Rank of ', :target, ' changed from ', :old_rank, ' to ', :new_rank))
"}, list("time" = SQLtime(), "round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "target" = admin_ckey, "old_rank" = old_rank, "new_rank" = new_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.rank = R //set the admin_rank as our rank
var/client/C = GLOB.directory[admin_ckey]
D.associate(C)
else
D = new(R, admin_ckey, TRUE) //new admin
message_admins(m1)
log_admin(m2)
/datum/admins/proc/change_admin_flags(admin_ckey, admin_key, use_db, datum/admins/D, legacy_only)
var/new_flags = input_bitfield(usr, "Include permission flags<br>[use_db ? "This will affect ALL admins with this rank." : "This will affect only the current admin [admin_key]"]", "admin_flags", D.rank.include_rights, 350, 590, allowed_edit_list = usr.client.holder.rank.can_edit_rights)
if(isnull(new_flags))
return
var/new_exclude_flags = input_bitfield(usr, "Exclude permission flags<br>Flags enabled here will be removed from a rank.<br>Note these take precedence over included flags.<br>[use_db ? "This will affect ALL admins with this rank." : "This will affect only the current admin [admin_key]"]", "admin_flags", D.rank.exclude_rights, 350, 670, "red", usr.client.holder.rank.can_edit_rights)
if(isnull(new_exclude_flags))
return
var/new_can_edit_flags = input_bitfield(usr, "Editable permission flags<br>These are the flags this rank is allowed to edit if they have access to the permissions panel.<br>They will be unable to modify admins to a rank that has a flag not included here.<br>[use_db ? "This will affect ALL admins with this rank." : "This will affect only the current admin [admin_key]"]", "admin_flags", D.rank.can_edit_rights, 350, 710, allowed_edit_list = usr.client.holder.rank.can_edit_rights)
if(isnull(new_can_edit_flags))
return
var/m1 = "[key_name_admin(usr)] edited the permissions of [use_db ? " rank [D.rank.name] permanently" : "[admin_key] temporarily"]"
var/m2 = "[key_name(usr)] edited the permissions of [use_db ? " rank [D.rank.name] permanently" : "[admin_key] temporarily"]"
if(use_db || legacy_only)
var/rank_name = D.rank.name
var/old_flags
var/old_exclude_flags
var/old_can_edit_flags
var/datum/DBQuery/query_get_rank_flags = SSdbcore.NewQuery(
"SELECT flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] WHERE `rank` = :rank_name",
list("rank_name" = rank_name)
)
if(!query_get_rank_flags.warn_execute())
qdel(query_get_rank_flags)
return
if(query_get_rank_flags.NextRow())
old_flags = text2num(query_get_rank_flags.item[1])
old_exclude_flags = text2num(query_get_rank_flags.item[2])
old_can_edit_flags = text2num(query_get_rank_flags.item[3])
qdel(query_get_rank_flags)
var/datum/DBQuery/query_change_rank_flags = SSdbcore.NewQuery(
"UPDATE [format_table_name("admin_ranks")] SET flags = :new_flags, exclude_flags = :new_exclude_flags, can_edit_flags = :new_can_edit_flags WHERE `rank` = :rank_name",
list("new_flags" = new_flags, "new_exclude_flags" = new_exclude_flags, "new_can_edit_flags" = new_can_edit_flags, "rank_name" = rank_name)
)
if(!query_change_rank_flags.warn_execute())
qdel(query_change_rank_flags)
return
qdel(query_change_rank_flags)
var/log_message = "Permissions of [rank_name] changed from[rights2text(old_flags," ")][rights2text(old_exclude_flags," ", "-")][rights2text(old_can_edit_flags," ", "*")] to[rights2text(new_flags," ")][rights2text(new_exclude_flags," ", "-")][rights2text(new_can_edit_flags," ", "*")]"
var/datum/DBQuery/query_change_rank_flags_log = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log)
VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'change rank flags', :rank_name, :log)
"}, list("time" = SQLtime(), "round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "rank_name" = rank_name, "log" = log_message))
if(!query_change_rank_flags_log.warn_execute())
qdel(query_change_rank_flags_log)
return
qdel(query_change_rank_flags_log)
for(var/datum/admin_rank/R in GLOB.admin_ranks)
if(R.name != D.rank.name)
continue
R.rights = new_flags &= ~new_exclude_flags
R.exclude_rights = new_exclude_flags
R.include_rights = new_flags
R.can_edit_rights = new_can_edit_flags
for(var/i in GLOB.admin_datums+GLOB.deadmins)
var/datum/admins/A = GLOB.admin_datums[i]
if(!A)
A = GLOB.deadmins[i]
if (!A)
continue
if(A.rank.name != D.rank.name)
continue
var/client/C = GLOB.directory[A.target]
A.disassociate()
A.associate(C)
else
D.disassociate()
if(!findtext(D.rank.name, "([admin_ckey])")) //not a modified subrank, need to duplicate the admin_rank datum to prevent modifying others too
D.rank = new("[D.rank.name]([admin_ckey])", new_flags, new_exclude_flags, new_can_edit_flags) //duplicate our previous admin_rank but with a new name
//we don't add this clone to the admin_ranks list, as it is unique to that ckey
else
D.rank.rights = new_flags &= ~new_exclude_flags
D.rank.include_rights = new_flags
D.rank.exclude_rights = new_exclude_flags
D.rank.can_edit_rights = new_can_edit_flags
var/client/C = GLOB.directory[admin_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(m1)
log_admin(m2)
/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.rank.can_edit_rights) == R.rights))
to_chat(usr, "<span class='admin prefix'>You don't have edit rights to all the rights this rank has, rank deletion not permitted.</span>", confidential=TRUE)
return
if(!CONFIG_GET(flag/admin_legacy_system) && CONFIG_GET(flag/protect_legacy_ranks) && (admin_rank in GLOB.protected_ranks))
to_chat(usr, "<span class='admin prefix'>Deletion of protected ranks is not permitted, it must be removed from admin_ranks.txt.</span>", confidential=TRUE)
return
if(CONFIG_GET(flag/load_legacy_ranks_only))
to_chat(usr, "<span class='admin prefix'>Rank deletion not permitted while database rank loading is disabled.</span>", confidential=TRUE)
return
var/datum/DBQuery/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(alert("Are you sure you want to remove [admin_rank]?","Confirm Removal","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/DBQuery/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/DBQuery/query_add_rank_log = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log)
VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'remove rank', :admin_rank, CONCAT('Rank removed: ', :admin_rank))
"}, list("time" = SQLtime(), "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.name
var/datum/DBQuery/query_sync_lastadminrank = SSdbcore.NewQuery(
"UPDATE [format_table_name("player")] SET lastadminrank = :rank WHERE ckey = :ckey",
list("rank" = sqlrank, "ckey" = admin_ckey)
)
if(!query_sync_lastadminrank.warn_execute())
qdel(query_sync_lastadminrank)
return
qdel(query_sync_lastadminrank)
to_chat(usr, span_admin("Sync of [admin_key] successful."), confidential=TRUE)
// /datum/admins/proc/change_admin_flags(admin_ckey, admin_key, datum/admins/D)
// var/new_flags = input_bitfield(usr, "Permission flags<br>This will affect only the current admin [admin_key]", "admin_flags", D.rights, 350, 590)
// if(isnull(new_flags))
// return
// var/m1 = "[key_name_admin(usr)] edited the permissions of [admin_key] temporarily"
// var/m2 = "[key_name(usr)] edited the permissions of [admin_key] temporarily"
// D.rights = new_flags
// var/client/C = D.owner
// D.disassociate()
// D.associate(C)
// message_admins(m1)
// log_admin(m2)

View File

@@ -0,0 +1,375 @@
/datum/permissions_controller/db
var/list/dbranks = list()
var/list/dbadmins = list()
/datum/permissions_controller/db/clear_admins()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
..()
dbranks.Cut()
dbadmins.Cut()
/datum/permissions_controller/db/load_admins()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
. = ..()
if(!SSdbcore.Connect())
message_admins("Failed to connect to database to load admins.")
log_sql("Failed to connect to database to load admins")
return
var/datum/DBQuery/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.")
log_sql("Error loading admin ranks from database.")
else
while(query_load_admin_ranks.NextRow())
var/rank_name = trim(query_load_admin_ranks.item[1])
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])
dbranks[rank_name] = list("flags" = rank_flags & (~rank_exclude_flags), "can_edit_flags" = rank_can_edit_flags)
qdel(query_load_admin_ranks)
var/datum/DBQuery/query_load_admins = SSdbcore.NewQuery("SELECT ckey, rank 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.")
else
while(query_load_admins.NextRow())
var/admin_ckey = ckey(query_load_admins.item[1])
var/admin_rank = trim(query_load_admins.item[2])
if(dbranks[admin_rank] == null)
message_admins("[admin_ckey] loaded with invalid admin rank [admin_rank].")
continue
if(admin_ckey in legacy_admins)
continue
dbadmins[admin_ckey] = admin_rank
qdel(query_load_admins)
/datum/permissions_controller/db/_load_permissions_for(var/client/C)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(..())
return TRUE
var/rank = dbadmins[C.ckey]
if(!rank)
return FALSE
var/rights = dbranks[rank]
if(!rights)
return FALSE
new /datum/admins(C.ckey, rights["flags"])
return TRUE
/datum/permissions_controller/db/get_rights_for_ckey(ckey)
. = ..()
if(.)
return
var/rank = dbadmins[ckey]
if(!rank)
return
var/rights = dbranks[rank]
if(!rights)
return
else
return rights["flags"]
/datum/permissions_controller/db/get_rank_name(client/subject)
. = ..()
if(.)
return
if(subject.ckey in dbadmins)
return dbadmins[subject.ckey]
/datum/permissions_controller/db/pp_data(mob/user)
. = ..()
var/perm_rights = check_rights_for(user.client, R_PERSIST_PERMS)
var/grant_flags = 0
if(user.ckey in legacy_admins)
grant_flags = legacy_ranks[legacy_admins[user.ckey]] || 0
else if(user.ckey in dbadmins)
var/dbrank = dbadmins[user.ckey]
if(dbranks[dbrank])
grant_flags = dbranks[dbrank]["can_edit_flags"]
for(var/admin in dbadmins)
var/data = list()
data["ckey"] = admin
data["rank"] = dbadmins[admin]
var/rights_text = "Rights: [rights2text(dbranks[data["rank"]]["flags"], " ")]\nGrant Rights: [rights2text(dbranks[data["rank"]]["can_edit_flags"], " ", "*")]"
data["rights"] = rights_text
data["deadminned"] = (admin in deadmins)
data["protected_admin"] = (dbranks[data["rank"]]["flags"] & grant_flags) != dbranks[data["rank"]]["flags"]
data["protected_rank"] = !perm_rights || data["protected_admin"]
.["admins"] |= list(data)
/datum/permissions_controller/db/should_add_admin(ckey)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(!..())
return FALSE
return !(ckey in dbadmins)
/// Sets a rank in the DB
/// Returns true of the rank was set
/datum/permissions_controller/db/proc/set_db_rank(ckey)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
var/grant_flags = 0
if(usr.ckey in legacy_admins)
grant_flags = legacy_ranks[legacy_admins[usr.ckey]] || 0
else if(usr.ckey in dbadmins)
var/dbrank = dbadmins[usr.ckey]
if(dbranks[dbrank])
grant_flags = dbranks[dbrank]["can_edit_flags"]
if((ckey in dbadmins) && (dbadmins[ckey] in dbranks) && (dbranks[dbadmins[ckey]]["flags"] & grant_flags) != dbranks[dbadmins[ckey]]["flags"])
return FALSE
var/list/ranks = list()
ranks += "*New Rank*"
for(var/rank in dbranks)
var/flags = dbranks[rank]["flags"]
if((flags & grant_flags) == flags)
ranks += rank
var/new_rank = input("Please select a rank", "New rank") as null|anything in ranks
if(new_rank == "*New Rank*")
new_rank = trim(input("Please input a new rank", "New custom rank") as text|null)
if(!new_rank)
return FALSE
if(!(new_rank in dbranks))
var/current_flags = list("flags" = 0, "can_edit_flags" = 0)
if((ckey in dbadmins) && (dbadmins[ckey] in dbranks))
current_flags = dbranks[dbadmins[ckey]]
dbranks[new_rank] = current_flags
var/datum/DBQuery/query_add_rank = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("admin_ranks")] (`rank`, flags, exclude_flags, can_edit_flags)
VALUES (:new_rank, :flags, '0', :edit_flags) ON DUPLICATE KEY UPDATE
"}, list("new_rank" = new_rank, "flags" = current_flags["flags"], "edit_flags" = current_flags["can_edit_flags"]))
if(!query_add_rank.warn_execute())
qdel(query_add_rank)
return
qdel(query_add_rank)
var/datum/DBQuery/query_add_rank_log = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log)
VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'add rank', :new_rank, CONCAT('New rank added: ', :new_rank))
"}, list("time" = SQLtime(), "round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "new_rank" = new_rank))
if(!query_add_rank_log.warn_execute())
qdel(query_add_rank_log)
return
qdel(query_add_rank_log)
var/old_rank = dbadmins[ckey] || "NEW ADMIN"
dbadmins[ckey] = new_rank
var/datum/DBQuery/query_change_rank = SSdbcore.NewQuery(
"INSERT INTO [format_table_name("admin")] (`rank`, ckey) VALUES(:new_rank, :admin_ckey)",
list("new_rank" = new_rank, "admin_ckey" = ckey)
)
if(!query_change_rank.warn_execute())
qdel(query_change_rank)
return
qdel(query_change_rank)
var/datum/DBQuery/query_change_rank_log = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log)
VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'change admin rank', :target, CONCAT('Rank of ', :target, ' changed from ', :old_rank, ' to ', :new_rank))
"}, list("time" = SQLtime(), "round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "target" = ckey, "old_rank" = old_rank, "new_rank" = new_rank))
if(!query_change_rank_log.warn_execute())
qdel(query_change_rank_log)
return
qdel(query_change_rank_log)
var/datum/admins/holder = admin_datums[ckey]
if(holder)
holder.deactivate()
holder.activate()
return TRUE
/datum/permissions_controller/db/make_admin(ckey)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
var/permanent = (SSdbcore.Connect() && check_rights(R_PERSIST_PERMS, FALSE) && (alert("Permanent changes are saved to the database for future rounds, temporary changes will affect only the current round", "Permanent or Temporary?", "Permanent", "Temporary", "Cancel") == "Permanent"))
if(!permanent)
return ..()
set_db_rank(ckey)
return TRUE
/datum/permissions_controller/db/edit_rank(ckey)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(..())
return TRUE
if(!(ckey in dbadmins))
return FALSE
var/permanent = ((ckey in dbadmins) && SSdbcore.Connect() && check_rights(R_PERSIST_PERMS, FALSE) && (alert("Permanent changes are saved to the database for future rounds, temporary changes will affect only the current round", "Permanent or Temporary?", "Permanent", "Temporary", "Cancel") == "Permanent"))
if(!permanent)
if(set_legacy_rank(ckey))
dbadmins -= ckey
return TRUE
set_db_rank(ckey)
return TRUE
/datum/permissions_controller/db/edit_perms(ckey)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(..())
return TRUE
if(!(ckey in dbadmins))
return FALSE
if(!SSdbcore.Connect())
to_chat(usr, "Cannot connect to database to edit rank [dbadmins[ckey]]", confidential = TRUE)
return TRUE
if(!check_rights(R_PERSIST_PERMS, FALSE))
to_chat(usr, "You do not have access to modify this rank.")
return TRUE
var/grant_flags = 0
if(usr.ckey in legacy_admins)
grant_flags = legacy_ranks[legacy_admins[usr.ckey]] || 0
else if(usr.ckey in dbadmins)
var/dbrank = dbadmins[usr.ckey]
if(dbranks[dbrank])
grant_flags = dbranks[dbrank]["can_edit_flags"]
if(!((dbadmins[ckey] in dbranks) && (dbranks[dbadmins[ckey]]["flags"] & grant_flags) == dbranks[dbadmins[ckey]]["flags"]))
to_chat(usr, "You do not have access to modify this rank.")
return TRUE
if(alert(usr, "This rank cannot be modified temporarily, and modifying it will change all admins using this rank. Do you wish to continue?", "Confirmation", "Yes", "No") != "Yes")
return TRUE
var/rank = dbranks[dbadmins[ckey]]
var/new_flags = input_bitfield(usr, "Permission flags<br>This will affect ALL admins with this rank.", "admin_flags", rank["flags"], 350, 590, allowed_edit_list = grant_flags)
if(isnull(new_flags))
return
var/new_can_edit_flags = input_bitfield(usr, "Editable permission flags<br>These are the flags this rank is allowed to edit if they have access to the permissions panel.<br>They will be unable to modify admins to a rank that has a flag not included here.<br>This will affect ALL admins with this rank.", "admin_flags", rank["can_edit_flags"], 350, 710, grant_flags)
if(isnull(new_can_edit_flags))
return
var/rank_name = dbadmins[ckey]
var/m = "edited the permissions of rank [rank_name] permanently"
var/datum/DBQuery/query_change_rank_flags = SSdbcore.NewQuery(
"UPDATE [format_table_name("admin_ranks")] SET flags = :new_flags, exclude_flags = :new_exclude_flags, can_edit_flags = :new_can_edit_flags WHERE `rank` = :rank_name",
list("new_flags" = new_flags, "new_exclude_flags" = 0, "new_can_edit_flags" = new_can_edit_flags, "rank_name" = rank_name)
)
if(!query_change_rank_flags.warn_execute())
qdel(query_change_rank_flags)
return
qdel(query_change_rank_flags)
var/log_message = "Permissions of [rank_name] changed from[rights2text(rank["flags"]," ")][rights2text(rank["can_edit_flags"]," ", "*")] to[rights2text(new_flags," ")][rights2text(new_can_edit_flags," ", "*")]"
var/datum/DBQuery/query_change_rank_flags_log = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log)
VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'change rank flags', :rank_name, :log)
"}, list("time" = SQLtime(), "round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "rank_name" = rank_name, "log" = log_message))
if(!query_change_rank_flags_log.warn_execute())
qdel(query_change_rank_flags_log)
return
qdel(query_change_rank_flags_log)
rank["flags"] = new_flags
rank["can_edit_flags"] = new_can_edit_flags
for(var/admin in admin_datums)
if(dbadmins[admin] == rank_name)
var/datum/admins/holder = admin_datums[admin]
holder.deactivate()
holder.activate()
message_admins("[key_name_admin(usr)] [m]")
log_admin("[key_name(usr)] [m]")
return TRUE
/datum/permissions_controller/db/remove_admin(ckey)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(..())
return TRUE
if(!(ckey in dbadmins))
return FALSE
var/grant_flags = 0
if(usr.ckey in legacy_admins)
grant_flags = legacy_ranks[legacy_admins[usr.ckey]] || 0
else if(usr.ckey in dbadmins)
var/dbrank = dbadmins[usr.ckey]
if(dbranks[dbrank])
grant_flags = dbranks[dbrank]["can_edit_flags"]
if(!((dbadmins[ckey] in dbranks) && (dbranks[dbadmins[ckey]]["flags"] & grant_flags) == dbranks[dbadmins[ckey]]["flags"]))
to_chat(usr, "You do not have access to delete this admin")
return TRUE
var/permanent = ((ckey in dbadmins) && SSdbcore.Connect() && check_rights(R_PERSIST_PERMS, FALSE) && (alert("Permanent changes are saved to the database for future rounds, temporary changes will affect only the current round", "Permanent or Temporary?", "Permanent", "Temporary", "Cancel") == "Permanent"))
if(permanent)
if(alert(usr, "Are you sure you wish to permanently remove [ckey] from the admins list?", "Confirmation", "Yes", "No") != "Yes")
return TRUE
var/datum/DBQuery/query_add_rank = SSdbcore.NewQuery(
"DELETE FROM [format_table_name("admin")] WHERE ckey = :ckey",
list("ckey" = ckey)
)
if(!query_add_rank.warn_execute())
qdel(query_add_rank)
return
qdel(query_add_rank)
var/datum/DBQuery/query_add_rank_log = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log)
VALUES (:time, :round_id, :adminckey, INET_ATON(:adminip), 'remove admin', :admin_ckey, CONCAT('Admin removed: ', :admin_ckey))
"}, list("time" = SQLtime(), "round_id" = "[GLOB.round_id]", "adminckey" = usr.ckey, "adminip" = usr.client.address, "admin_ckey" = ckey))
if(!query_add_rank_log.warn_execute())
qdel(query_add_rank_log)
return
qdel(query_add_rank_log)
else
if(alert(usr, "Are you sure you wish to temporarily remove [ckey] from the admins list?", "Confirmation", "Yes", "No") != "Yes")
return TRUE
var/datum/admins/holder = admin_datums[ckey] || deadmins[ckey]
if(holder)
qdel(holder)
dbadmins -= ckey
return TRUE

View File

@@ -0,0 +1,171 @@
/datum/permissions_controller/forums
/// Admins from the forums
var/list/forums_admins = list()
/// Admins who are overriden for this round
var/list/overrides = list()
/datum/permissions_controller/forums/clear_admins()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
..()
forums_admins.Cut()
overrides.Cut()
/datum/permissions_controller/forums/_load_permissions_for(client/C)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(C.ckey in overrides)
return ..()
forums_admins -= C.ckey // In case they have been demoted since last login
var/permissions = query_permissions_for(C.ckey)
if(permissions)
forums_admins[C.ckey] = permissions
new /datum/admins(C.ckey, permissions["rights"])
return TRUE
return ..()
/// Queries forums to find permissions
/datum/permissions_controller/forums/proc/query_permissions_for(ckey)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(!CONFIG_GET(string/xenforo_key))
CRASH("Trying to load forums permisisons without xenforo key")
var/datum/http_request/req = new()
req.prepare(RUSTG_HTTP_METHOD_GET, "[CONFIG_GET(string/apiurl)]/linking/byond/[ckey(ckey)]", "", list("XF-Api-Key" = CONFIG_GET(string/xenforo_key)))
req.begin_async()
UNTIL(req.is_complete())
var/datum/http_response/response = req.into_response()
if(response.errored)
CRASH("Errored loading forums rank: [response.error]")
var/list/body
try
body = json_decode(response.body)
catch
CRASH("Malformed JSON from forums permssions endpoint. [response.body]")
if(!body["success"])
return FALSE
var/flags = 0
var/list/permissions = body["user"]["permissions"]
for(var/permission in permissions)
var/list/parts = splittext(permission, ".")
if(parts[1] != "ingame")
continue
var/newflag = admin_keyword_to_flag(parts[2])
if(!newflag)
stack_trace("WARNING: Permission \"[parts[2]]\" not found.")
flags |= newflag
if(flags)
return list("rank" = body["display_group"]["title"], "rights" = flags)
else
return FALSE
/datum/permissions_controller/forums/get_rights_for_ckey(ckey)
. = ..()
if(.)
return
if(!(ckey in overrides) && (ckey in forums_admins))
return forums_admins[ckey]["rights"]
/datum/permissions_controller/forums/get_rank_name(client/subject)
. = ..()
if(.)
return
if(!(subject.ckey in overrides) && (subject.ckey in forums_admins))
return forums_admins[subject.ckey]["rank"]
/datum/permissions_controller/forums/pp_data(mob/user)
. = ..()
for(var/admin in forums_admins)
if(admin in overrides)
continue
var/data = list()
data["ckey"] = admin
data["rank"] = forums_admins[admin]["rank"]
data["rights"] = rights2text(forums_admins[admin]["rights"], seperator = " ")
data["deadminned"] = (admin in deadmins)
data["protected_rank"] = TRUE
.["admins"] |= list(data)
/datum/permissions_controller/forums/should_add_admin(ckey)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(!..())
return FALSE
if(ckey in overrides)
return TRUE
return !query_permissions_for(ckey)
/datum/permissions_controller/forums/edit_rank(ckey)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(..())
return TRUE
if((ckey in forums_admins) && !(ckey in overrides))
if(alert("Forums permissions cannot be edited from this panel. Would you like to add an override for this round?", "Confirmation", "Yes", "No") != "Yes")
return TRUE
if(set_legacy_rank(ckey))
overrides += ckey
return TRUE
return FALSE
/datum/permissions_controller/forums/edit_perms(ckey)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(..())
return TRUE
if((ckey in forums_admins) && !(ckey in overrides))
to_chat(usr, "Permissions for forums ranks cannot be edited in game. To temporarily modify a users permissions first give them a temporary rank.", confidential = TRUE)
return TRUE
return FALSE
/datum/permissions_controller/forums/remove_admin(ckey)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(..())
return TRUE
if((ckey in forums_admins) && !(ckey in overrides))
if(alert("Forums permissions cannot be edited from this panel. Would you like to demote for this round?", "Confirmation", "Yes", "No") != "Yes")
return TRUE
overrides += ckey
forums_admins -= ckey
if(ckey in admin_datums)
qdel(admin_datums[ckey])
if(ckey in deadmins)
qdel(deadmins[ckey])
return TRUE
return FALSE

View File

@@ -0,0 +1,454 @@
GLOBAL_DATUM(permissions, /datum/permissions_controller)
GENERAL_PROTECT_DATUM(/datum/permissions_controller)
// Creates permissions controller based on the config
/proc/init_permissions()
if(GLOB.permissions != null)
CRASH("Permissions controller loaded twice")
var/controller_type = /datum/permissions_controller
switch(ckey(CONFIG_GET(string/permissions_backend)))
if("database")
if(CONFIG_GET(flag/sql_enabled))
controller_type = /datum/permissions_controller/db
else
stack_trace("Attempted to load database permissions with sql disabled.")
if("forums")
if(CONFIG_GET(string/xenforo_key))
controller_type = /datum/permissions_controller/forums
else
stack_trace("Attempted to load forums permissions without a xenforo api key")
GLOB.permissions = new controller_type()
// Handles admin permissions management, overriden to support external backends
// Base datum supports legacy file based admin loading
/datum/permissions_controller
/// Admins that should not be allowed to be modified by the permissions panel
var/list/protected_admins = list()
/// Ranks that should not be allowed to be modified by the permissions panel
var/list/protected_ranks = list()
/// Admins loaded with the legacy system
var/list/legacy_admins = list()
/// Ranks loaded with the legacy system
var/list/legacy_ranks = list()
// These lists are mostly handled by the datums themselves
/// Associated list of ckey -> admin datums
var/list/admin_datums = list()
/// List of all admin clients
var/list/admins = list()
/// List of all deadmins
var/list/deadmins = list()
/// Clears any existing stored admins and (re) loads the data from the backend
/datum/permissions_controller/proc/start()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
clear_admins()
load_admins()
for(var/client/C in GLOB.clients)
load_permissions_for(C)
/// Removes all admin status from everyone
/datum/permissions_controller/proc/clear_admins()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
//clear the datums references
admin_datums.Cut()
for(var/client/C in GLOB.permissions.admins)
C.remove_admin_verbs()
C.holder = null
admins.Cut()
protected_admins.Cut()
protected_ranks.Cut()
legacy_admins.Cut()
legacy_ranks.Cut()
deadmins.Cut()
//Clear profile access
for(var/A in world.GetConfig("admin"))
world.SetConfig("APP/admin", A, null)
/// Pulls in admin data, for if the backend caches the admin data
/datum/permissions_controller/proc/load_admins()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
var/previous_rights = 0
for(var/line in world.file2list("[global.config.directory]/admin_ranks.txt"))
if(!line || findtextEx(line,"#",1,2) || line == " ") //YOGS - added our DB support
continue
var/next = findtext(line, "=")
var/prev = findchar(line, "+-*", next, 0)
var/rank_name = trim(copytext(line, 1, next))
if(!rank_name) continue
var/rights = 0
while(prev)
next = findchar(line, "+-*", prev + 1, 0)
rights |= admin_keyword_to_flag(copytext(line, prev, next), previous_rights)
prev = next
previous_rights = rights
legacy_ranks[rank_name] = rights
if(CONFIG_GET(flag/protect_legacy_ranks))
protected_ranks |= rank_name
//ckeys listed in admins.txt are always made admins before sql loading is attempted
var/list/lines = world.file2list("[global.config.directory]/admins.txt")
for(var/line in lines)
if(!length(line) || findtextEx(line, "#", 1, 2) || line == " ") //yogs - added our DB support
continue
var/list/entry = splittext(line, "=")
if(entry.len < 2)
continue
var/ckey = ckey(entry[1])
var/rank = trim(entry[2])
if(!ckey || !rank)
continue
if(rank in legacy_ranks)
legacy_admins[ckey] = rank
if(CONFIG_GET(flag/protect_legacy_admins))
protected_admins |= ckey
/// Queries the backend permissions system then creates their datum if they should have one
/// Returns true if a datum was created
/datum/permissions_controller/proc/load_permissions_for(client/C)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(C.ckey in legacy_admins)
new /datum/admins(C.ckey, legacy_ranks[legacy_admins[C.ckey]])
return TRUE
if(_load_permissions_for(C))
return TRUE
if(CONFIG_GET(flag/autoadmin))
var/auto_rank = CONFIG_GET(string/autoadmin_rank)
if(auto_rank in legacy_ranks)
legacy_admins[C.ckey] = auto_rank
new /datum/admins(C.ckey, legacy_ranks[C.ckey])
return TRUE
if(CONFIG_GET(flag/enable_localhost_rank))
var/localhost_addresses = list("127.0.0.1", "::1")
if(isnull(C.address) || (C.address in localhost_addresses))
legacy_ranks["!localhost!"] = R_EVERYTHING - R_PERSIST_PERMS
legacy_admins[C.ckey] = "!localhost!"
new /datum/admins(C.ckey, R_EVERYTHING - R_PERSIST_PERMS)
return TRUE
return FALSE
/// Most backends probably want to override this one
/// Loads after legacy but before autoadmin/localhost
/// If you woud like your backend to load before legacy, or after autoadmin/localhost
/// Override the above function instead
/datum/permissions_controller/proc/_load_permissions_for(client/C)
PROTECTED_PROC(TRUE)
return FALSE
/datum/permissions_controller/proc/check_for_rights(client/subject, rights_required)
if(!subject || !subject.holder) // Null and deadmins have no rights
return FALSE
if(rights_required)
return !!(get_rights_for(subject) & rights_required)
return TRUE
/datum/permissions_controller/proc/get_rights_for(client/subject)
. = 0
if(!subject || !subject.holder)
return
return get_rights_for_ckey(subject.ckey)
/datum/permissions_controller/proc/get_rights_for_ckey(ckey)
if(ckey in legacy_admins)
if(legacy_admins[ckey] in legacy_ranks)
return legacy_ranks[legacy_admins[ckey]]
/// Returns -1 if fewer, 0 if same, 1 if more
/datum/permissions_controller/proc/compare_rights(client/A, client/B)
if(!A && !B) // If both null, same
return 0
if(!A)
return -1
if(!B)
return 1
if(!A.holder && !B.holder)
return 0
if(!A.holder)
return -1
if(!B.holder)
return 1
var/A_rights = get_rights_for(A)
var/B_rights = get_rights_for(B)
if(A_rights == B_rights)
return 0
if(A_rights > B_rights)
return 1
return -1
/datum/permissions_controller/proc/get_rank_name(client/subject)
var/ckey = subject.ckey
if(ckey in legacy_admins)
return legacy_admins[ckey]
/datum/permissions_controller/proc/pp_data(mob/user)
var/user_rights = get_rights_for(user.client)
. = list()
.["admins"] = list()
for(var/legmin in legacy_admins)
var/data = list()
data["ckey"] = legmin
data["rank"] = legacy_admins[legmin]
var/rights = legacy_ranks[data["rank"]]
data["rights"] = rights2text(rights, seperator = " ")
var/can_edit = (rights & user_rights) == rights
data["protected_admin"] = (legmin in protected_admins) || !can_edit
data["protected_rank"] = (data["rank"] in protected_ranks) || !can_edit
data["deadminned"] = (legmin in deadmins)
.["admins"] |= list(data)
// Functions used to modify permissions
// Returns true if permissions modification was handled
/// This proc prompts the user for the name that want to add
/// Then uses should_add_admin(ckey) to determine if there is a reason
/// that ckey shouldn't be added (usually due to already being adminned)
/// make_admin(ckey) is then called to handle the actual adding
/datum/permissions_controller/proc/add_admin()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
var/ckey = ckey(input("New admin's key","Admin key") as text|null)
if(!ckey)
return FALSE
if(!should_add_admin(ckey))
to_chat(usr, span_warning("Unable to admin [ckey]. Do they already hold a rank?"), confidential = TRUE)
return FALSE
return make_admin(ckey)
/datum/permissions_controller/proc/should_add_admin(ckey)
return !(ckey in legacy_admins)
/// Returns true if the rank was changed
/datum/permissions_controller/proc/set_legacy_rank(ckey)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
var/list/rank_names = list()
var/usr_rights = get_rights_for(usr.client)
rank_names += "*New Rank*"
for(var/R in legacy_ranks)
if((usr_rights & legacy_ranks[R]) == legacy_ranks[R]) // Cannot grant permissions you do not have
rank_names += R
var/new_rank = input("Please select a rank", "New rank") as null|anything in rank_names
if(new_rank == "*New Rank*")
new_rank = trim(input("Please input a new rank", "New custom rank") as text|null)
if(!new_rank)
return FALSE
var/old_rights = 0
if(ckey in legacy_admins)
if(legacy_admins[ckey] in legacy_ranks)
old_rights = legacy_ranks[legacy_admins[ckey]]
if(!(new_rank in legacy_ranks))
legacy_ranks[new_rank] = old_rights
legacy_admins[ckey] = new_rank
var/m = "edited the admin rank of [ckey] to [new_rank] temporarily"
message_admins("[key_name_admin(usr)] [m]")
log_admin("[key_name(usr)] [m]")
if(ckey in admin_datums)
var/datum/admins/holder = admin_datums[ckey]
holder.deactivate()
holder.activate()
return TRUE
if(ckey in deadmins)
return TRUE
if(ckey in GLOB.directory)
new /datum/admins(ckey, legacy_ranks[new_rank])
return TRUE
return TRUE
/datum/permissions_controller/proc/make_admin(ckey)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
set_legacy_rank(ckey)
return TRUE
/datum/permissions_controller/proc/edit_rank(ckey)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(ckey in protected_admins)
to_chat(usr, span_warning("Editing this admin blocked by config"), confidential = TRUE)
return TRUE // Nothing was changed, but nothing should be changed
if(!(ckey in legacy_admins))
return FALSE
set_legacy_rank(ckey)
return TRUE
/datum/permissions_controller/proc/edit_perms(ckey)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(!(ckey in legacy_admins))
return FALSE
var/rank = legacy_admins[ckey]
if(rank in protected_ranks)
to_chat(usr, span_warning("Editing this rank blocked by config"), confidential = TRUE)
return TRUE // Nothing was changed, but nothing should be changed
if(alert("This will modify all admins with the same rank, are you sure you wish to continue?", "Confirmation", "Yes", "No") != "Yes")
return TRUE
var/new_flags = input_bitfield(usr, "Permission flags<br>This will affect all admins with rank [rank]", "admin_flags", legacy_ranks[rank], 350, 590)
if(isnull(new_flags))
return
legacy_ranks[rank] = new_flags
var/m = "edited the admin rank of [rank] temporarily"
message_admins("[key_name_admin(usr)] [m]")
log_admin("[key_name(usr)] [m]")
for(var/admin in admin_datums)
if((admin in legacy_admins) && legacy_admins[admin] == rank)
var/datum/admins/holder = admin_datums[ckey]
holder.deactivate()
holder.activate()
/datum/permissions_controller/proc/remove_admin(ckey)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(!(ckey in legacy_admins))
return FALSE
if(ckey in protected_admins)
to_chat(usr, span_warning("Editing this admin blocked by config"), confidential = TRUE)
return TRUE // Nothing was changed, but nothing should be changed
if(alert("This will remove all admin access for the rest of the round. Are you sure?", "Confirmation", "Yes", "No", "Cancel") != "Yes")
return TRUE
var/m = "removed [ckey] from the admin list temporarily"
message_admins("[key_name_admin(usr)] [m]")
log_admin("[key_name(usr)] [m]")
legacy_admins[ckey] = null
if(ckey in admin_datums)
qdel(admin_datums[ckey])
if(ckey in deadmins)
qdel(deadmins[ckey])
/*
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:
/proc/admin_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
use check_rights_for
*/
/proc/check_rights(rights_required, show_msg=TRUE)
if(usr && usr.client)
if (check_rights_for(usr.client, rights_required))
return TRUE
else
if(show_msg)
to_chat(usr, "<font color='red'>Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")].</font>", confidential=TRUE)
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)
return GLOB.permissions.check_for_rights(subject, rights_required)
//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 TRUE
return GLOB.permissions.compare_rights(usr.client, other) > 0
return FALSE
/proc/admin_keyword_to_flag(word, previous_rights=0)
var/flag = 0
switch(ckey(word))
if("buildmode","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","rights")
flag = R_PERMISSIONS
if("possess")
flag = R_POSSESS
if("stealth")
flag = R_STEALTH
if("poll")
flag = R_POLL
if("varedit")
flag = R_VAREDIT
if("everything","host","all")
flag = R_EVERYTHING
if("sound","sounds")
flag = R_SOUNDS
if("spawn","create")
flag = R_SPAWN
if("autologin", "autoadmin")
flag = R_AUTOLOGIN
if("dev")
flag = R_DEV
if("dbranks", "persistperms")
flag = R_PERSIST_PERMS
if("@","prev")
flag = previous_rights
return flag

View File

@@ -115,10 +115,10 @@
if("show_admins")
var/dat = "<HTML><HEAD><meta charset='UTF-8'></HEAD><BODY><B>Current admins:</B><HR>"
if(GLOB.admin_datums)
for(var/ckey in GLOB.admin_datums)
var/datum/admins/D = GLOB.admin_datums[ckey]
dat += "[ckey] - [D.rank.name]<br>"
if(GLOB.permissions.admin_datums)
for(var/ckey in GLOB.permissions.admin_datums)
var/datum/admins/D = GLOB.permissions.admin_datums[ckey]
dat += "[ckey] - [D.rank_name()]<br>"
dat += "</BODY></HTML>"
usr << browse(dat, "window=showadmins;size=600x500")

View File

@@ -19,7 +19,7 @@
else
var/values = list(
"player_ckey" = player_ckey,
"must_apply_to_admins" = !!(GLOB.admin_datums[player_ckey] || GLOB.deadmins[player_ckey]),
"must_apply_to_admins" = !!(GLOB.permissions.admin_datums[player_ckey] || GLOB.permissions.deadmins[player_ckey]),
)
var/sql_roles
if(islist(roles))
@@ -113,7 +113,7 @@
if(C && istype(C))
C.ban_cache = list()
var/is_admin = FALSE
if(GLOB.admin_datums[C.ckey] || GLOB.deadmins[C.ckey])
if(GLOB.permissions.admin_datums[C.ckey] || GLOB.permissions.deadmins[C.ckey])
is_admin = TRUE
var/datum/DBQuery/query_build_ban_cache = SSdbcore.NewQuery(
"SELECT [format_table_name("ban")].role, applies_to_admins FROM [format_table_name("ban")] WHERE ckey = :ckey AND unbanned_datetime IS NULL AND (expiration_time IS NULL OR expiration_time > NOW())",
@@ -487,7 +487,7 @@
if(query_check_adminban_count.NextRow())
var/adminban_count = text2num(query_check_adminban_count.item[1])
var/max_adminbans = MAX_ADMINBANS_PER_ADMIN
if(R_EVERYTHING && !(R_EVERYTHING & rank.can_edit_rights)) //edit rights are a more effective way to check hierarchical rank since many non-headmins have R_PERMISSIONS now
if(check_rights(R_PERMISSIONS, FALSE)) //edit rights are a more effective way to check hierarchical rank since many non-headmins have R_PERMISSIONS now
max_adminbans = MAX_ADMINBANS_PER_HEADMIN
if(adminban_count >= max_adminbans)
to_chat(usr, span_danger("You've already logged [max_adminbans] admin ban(s) or more. Do not abuse this function!"), confidential=TRUE)
@@ -557,7 +557,7 @@
if(C)
build_ban_cache(C)
to_chat(C, "[span_boldannounce("You have been [applies_to_admins ? "admin " : ""]banned by [usr.client.key] from [roles_to_ban[1] == "Server" ? "the server" : " Roles: [roles_to_ban.Join(", ")]"].\nReason: [reason]")]<br>[span_danger("This ban is [isnull(duration) ? "permanent." : "temporary, it will be removed in [time_message]."] The round ID is [GLOB.round_id].")]<br>[span_danger("To appeal this ban go to [appeal_url]")]", confidential=TRUE)
if(GLOB.admin_datums[C.ckey] || GLOB.deadmins[C.ckey])
if(GLOB.permissions.admin_datums[C.ckey] || GLOB.permissions.deadmins[C.ckey])
is_admin = TRUE
if(roles_to_ban[1] == "Server" && (!is_admin || (is_admin && applies_to_admins)))
qdel(C)
@@ -567,7 +567,7 @@
if(i.address == player_ip || i.computer_id == player_cid)
build_ban_cache(i)
to_chat(i, "[span_boldannounce("You have been [applies_to_admins ? "admin " : ""]banned by [usr.client.key] from [roles_to_ban[1] == "Server" ? "the server" : " Roles: [roles_to_ban.Join(", ")]"].\nReason: [reason]")]<br>[span_danger("This ban is [isnull(duration) ? "permanent." : "temporary, it will be removed in [time_message]."] The round ID is [GLOB.round_id].")]<br>[span_danger("To appeal this ban go to [appeal_url]")]")
if(GLOB.admin_datums[i.ckey] || GLOB.deadmins[i.ckey])
if(GLOB.permissions.admin_datums[i.ckey] || GLOB.permissions.deadmins[i.ckey])
is_admin = TRUE
if(roles_to_ban[1] == "Server" && (!is_admin || (is_admin && applies_to_admins)))
qdel(i)
@@ -805,7 +805,7 @@
if(query_check_adminban_count.NextRow())
var/adminban_count = text2num(query_check_adminban_count.item[1])
var/max_adminbans = MAX_ADMINBANS_PER_ADMIN
if(R_EVERYTHING && !(R_EVERYTHING & rank.can_edit_rights)) //edit rights are a more effective way to check hierarchical rank since many non-headmins have R_PERMISSIONS now
if(check_rights(R_PERMISSIONS, FALSE)) //edit rights are a more effective way to check hierarchical rank since many non-headmins have R_PERMISSIONS now
max_adminbans = MAX_ADMINBANS_PER_HEADMIN
if(adminban_count >= max_adminbans)
to_chat(usr, span_danger("You've already logged [max_adminbans] admin ban(s) or more. Do not abuse this function!"), confidential=TRUE)

View File

@@ -240,23 +240,14 @@
log_admin("[key_name(usr)] has triggered an event. ([E.name])")
return
else if(href_list["editrightsbrowser"])
edit_admin_permissions(0)
// 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"])
// 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"])
edit_rights_topic(href_list)
// else if(href_list["editrights"])
// edit_rights_topic(href_list)
else if(href_list["gamemode_panel"])
if(!check_rights(R_ADMIN))

View File

@@ -260,7 +260,7 @@
if(irc)
log_admin_private("PM: [key_name(src)]->IRC: [rawmsg]")
for(var/client/X in GLOB.admins)
for(var/client/X in GLOB.permissions.admins)
to_chat(X,
type = MESSAGE_TYPE_ADMINPM,
html = span_notice("<B>PM: [key_name(src, X, 0)]-&gt;External:</B> [keywordparsedmsg]"),
@@ -269,7 +269,7 @@
window_flash(recipient, ignorepref = TRUE)
log_admin_private("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)
for(var/client/X in GLOB.permissions.admins)
if(X.key!=key && X.key!=recipient.key) //check client/X is an admin and isn't the sender or recipientD
to_chat(X,
type = MESSAGE_TYPE_ADMINPM,

View File

@@ -15,7 +15,7 @@
msg = keywords_lookup(msg)
var/custom_asay_color = (CONFIG_GET(flag/allow_admin_asaycolor) && prefs.asaycolor) ? "<font color=[prefs.asaycolor]>" : null // Yogs -- yogs asay
msg = "<span class='adminsay'>[span_prefix("ADMIN:")] <EM>[key_name(usr, 1)]</EM> [ADMIN_FLW(mob)]: [custom_asay_color]<span class='message linkify'>[msg]</span></span>[custom_asay_color ? "</font>":null]"
to_chat(GLOB.admins,
to_chat(GLOB.permissions.admins,
type = MESSAGE_TYPE_ADMINCHAT,
html = msg,
confidential = TRUE)

View File

@@ -19,7 +19,7 @@
if (!msg)
return
var/rank_name = holder.rank
var/rank_name = holder.rank_name()
var/admin_name = key
var/follow_link = ""
if(holder.fakekey)

View File

@@ -894,7 +894,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention)
if("Players")
to_chat(usr, jointext(GLOB.player_list,","), confidential=TRUE)
if("Admins")
to_chat(usr, jointext(GLOB.admins,","), confidential=TRUE)
to_chat(usr, jointext(GLOB.permissions.admins,","), confidential=TRUE)
if("Mobs")
to_chat(usr, jointext(GLOB.mob_list,","), confidential=TRUE)
if("Living Mobs")

View File

@@ -92,8 +92,7 @@
if(confirm !="Yes")
return
refresh_admin_files() //yogs - DB support
load_admins()
GLOB.permissions.start()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Reload All Admins") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
message_admins("[key_name_admin(usr)] manually reloaded admins")

View File

@@ -40,9 +40,9 @@
prayer_type = "SPIRITUAL PRAYER"
var/msg_tmp = msg
msg = span_adminnotice("[icon2html(cross, GLOB.admins)]<b><font color=[font_color]>[prayer_type][deity ? " (to [deity])" : ""]: </font>[ADMIN_FULLMONTY(src)] [ADMIN_SC(src)]:</b> [msg]")
msg = span_adminnotice("[icon2html(cross, GLOB.permissions.admins)]<b><font color=[font_color]>[prayer_type][deity ? " (to [deity])" : ""]: </font>[ADMIN_FULLMONTY(src)] [ADMIN_SC(src)]:</b> [msg]")
for(var/client/C in GLOB.admins)
for(var/client/C in GLOB.permissions.admins)
if(C.prefs.chat_toggles & CHAT_PRAYER)
to_chat(C, msg, confidential=TRUE)
if(C.prefs.toggles & SOUND_PRAYERS)
@@ -57,7 +57,7 @@
/proc/message_centcom(text, mob/sender)
var/msg = copytext_char(sanitize(text), 1, MAX_MESSAGE_LEN)
msg = span_adminnotice("<b><font color=orange>CENTCOM:</font>[ADMIN_FULLMONTY(sender)] [ADMIN_CENTCOM_REPLY(sender)]:</b> [msg]")
to_chat(GLOB.admins, msg, confidential = TRUE)
to_chat(GLOB.permissions.admins, msg, confidential = TRUE)
for(var/obj/machinery/computer/communications/console in GLOB.machines)
console.override_cooldown()
@@ -65,7 +65,7 @@
/proc/message_syndicate(text, mob/sender)
var/msg = copytext_char(sanitize(text), 1, MAX_MESSAGE_LEN)
msg = span_adminnotice("<b><font color=crimson>SYNDICATE:</font>[ADMIN_FULLMONTY(sender)] [ADMIN_SYNDICATE_REPLY(sender)]:</b> [msg]")
to_chat(GLOB.admins, msg, confidential = TRUE)
to_chat(GLOB.permissions.admins, msg, confidential = TRUE)
for(var/obj/machinery/computer/communications/console in GLOB.machines)
console.override_cooldown()
@@ -73,13 +73,13 @@
/proc/nuke_request(text, mob/sender)
var/msg = copytext_char(sanitize(text), 1, MAX_MESSAGE_LEN)
msg = span_adminnotice("<b><font color=orange>NUKE CODE REQUEST:</font>[ADMIN_FULLMONTY(sender)] [ADMIN_CENTCOM_REPLY(sender)] [ADMIN_SET_SD_CODE] [ADMIN_SET_BC_CODE]:</b> [msg]")
to_chat(GLOB.admins, msg, confidential = TRUE)
to_chat(GLOB.permissions.admins, msg, confidential = TRUE)
for(var/obj/machinery/computer/communications/console in GLOB.machines)
console.override_cooldown()
/proc/Clown_announce(text , mob/Sender)
var/msg = copytext_char(sanitize(text), 1, MAX_MESSAGE_LEN)
msg = span_adminnotice("<b><font color=violet>CLOWN PLANET:</font>[ADMIN_FULLMONTY(Sender)] [ADMIN_SYNDICATE_REPLY(Sender)]:</b> [msg]")
to_chat(GLOB.admins, msg, confidential=TRUE)
to_chat(GLOB.permissions.admins, msg, confidential=TRUE)
for(var/obj/machinery/computer/communications/C in GLOB.machines)
C.override_cooldown()

View File

@@ -241,32 +241,9 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
tgui_panel.send_connected()
GLOB.ahelp_tickets.ClientLogin(src)
var/connecting_admin = FALSE //because de-admined admins connecting should be treated like admins.
//Admin Authorisation
holder = GLOB.admin_datums[ckey]
if(holder)
if(!holder.associate(src, FALSE)) // Prevent asking for MFA at this point, it likely won't work
holder = null
connecting_admin = TRUE
else if(GLOB.deadmins[ckey])
add_verb(src, /client/proc/readmin)
connecting_admin = TRUE
if(CONFIG_GET(flag/autoadmin))
if(!GLOB.admin_datums[ckey])
var/datum/admin_rank/autorank
for(var/datum/admin_rank/R in GLOB.admin_ranks)
if(R.name == CONFIG_GET(string/autoadmin_rank))
autorank = R
break
if(!autorank)
to_chat(world, "Autoadmin rank not found")
else
new /datum/admins(autorank, ckey)
if(CONFIG_GET(flag/enable_localhost_rank) && !connecting_admin)
var/localhost_addresses = list("127.0.0.1", "::1")
if(isnull(address) || (address in localhost_addresses))
var/datum/admin_rank/localhost_rank = new("!localhost!", R_EVERYTHING, R_DBRANKS, R_EVERYTHING) //+EVERYTHING -DBRANKS *EVERYTHING
new /datum/admins(localhost_rank, ckey, 1, 1)
var/connecting_admin = GLOB.permissions.load_permissions_for(src) //because de-admined admins connecting should be treated like admins.
if(connecting_admin && !holder)
stack_trace("[ckey] is an admin but has no holder")
// yogs start - mentor stuff
if(ckey in GLOB.mentor_datums)
@@ -523,8 +500,8 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if(holder)
adminGreet(1)
holder.owner = null
GLOB.admins -= src
if (!GLOB.admins.len && SSticker.IsRoundInProgress()) //Only report this stuff if we are currently playing.
GLOB.permissions.admins -= src
if (!GLOB.permissions.admins.len && SSticker.IsRoundInProgress()) //Only report this stuff if we are currently playing.
var/cheesy_message = pick(
"I have no admins online!",\
"I'm all alone :(",\
@@ -541,6 +518,9 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
)
send2irc("Server", "[cheesy_message] (No admins online)")
qdel(holder)
if(ckey in GLOB.permissions.deadmins)
qdel(GLOB.permissions.deadmins[ckey])
GLOB.ahelp_tickets.ClientLogout(src)
GLOB.directory -= ckey
@@ -588,10 +568,10 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
related_accounts_cid += "[query_get_related_cid.item[1]], "
qdel(query_get_related_cid)
var/admin_rank = "Player"
if (src.holder && src.holder.rank)
admin_rank = src.holder.rank.name
if (src.holder)
admin_rank = src.holder.rank_name()
else
if (!GLOB.deadmins[ckey] && check_randomizer(connectiontopic))
if (!GLOB.permissions.deadmins[ckey] && check_randomizer(connectiontopic))
return
var/new_player
var/datum/DBQuery/query_client_in_db = SSdbcore.NewQuery(
@@ -602,7 +582,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
qdel(query_client_in_db)
return
if(!query_client_in_db.NextRow())
if (CONFIG_GET(flag/panic_bunker) && !holder && !GLOB.deadmins[ckey])
if (CONFIG_GET(flag/panic_bunker) && !holder && !GLOB.permissions.deadmins[ckey])
log_access("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"))
to_chat(src, CONFIG_GET(string/panic_bunker_message))

View File

@@ -1355,7 +1355,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(href_list["bancheck"])
var/list/ban_details = is_banned_from_with_details(user.ckey, user.client.address, user.client.computer_id, href_list["bancheck"])
var/admin = FALSE
if(GLOB.admin_datums[user.ckey] || GLOB.deadmins[user.ckey])
if(GLOB.permissions.admin_datums[user.ckey] || GLOB.permissions.deadmins[user.ckey])
admin = TRUE
for(var/i in ban_details)
if(admin && !text2num(i["applies_to_admins"]))

View File

@@ -62,8 +62,8 @@
var/msg = "<b>Current Admins:</b>\n"
if(holder)
for(var/client/C in GLOB.admins)
msg += "\t[C] is a [C.holder.rank]"
for(var/client/C in GLOB.permissions.admins)
msg += "\t[C] is a [C.holder.rank_name()]"
if(C.holder.fakekey)
msg += " <i>(as [C.holder.fakekey])</i>"
@@ -79,11 +79,11 @@
msg += " (AFK)"
msg += "\n"
else
for(var/client/C in GLOB.admins)
for(var/client/C in GLOB.permissions.admins)
if(C.is_afk())
continue //Don't show afk admins to adminwho
if(!C.holder.fakekey)
msg += "\t[C] is a [C.holder.rank]\n"
msg += "\t[C] is a [C.holder.rank_name()]\n"
msg += span_info("Adminhelps are also sent to Discord. If no admins are available in game adminhelp anyways and an admin on Discord will see it and respond.") //yogs - IRC -> discord
to_chat(src, msg)

View File

@@ -143,7 +143,7 @@
LateChoices()
return
if(SSticker.queued_players.len || (relevant_cap && living_player_count() >= relevant_cap && !(ckey(key) in GLOB.admin_datums)))
if(SSticker.queued_players.len || (relevant_cap && living_player_count() >= relevant_cap && !(ckey(key) in GLOB.permissions.admin_datums)))
//yogs start -- donors bypassing the queue
if(ckey(key) in get_donators())
to_chat(usr, span_notice("Because you are a donator, you have bypassed the queue! Thank you for donating!"))
@@ -176,7 +176,7 @@
to_chat(usr, span_notice("There is an administrative lock on entering the game!"))
return
if(SSticker.queued_players.len && !(ckey(key) in GLOB.admin_datums))
if(SSticker.queued_players.len && !(ckey(key) in GLOB.permissions.admin_datums))
if((living_player_count() >= relevant_cap) || (src != SSticker.queued_players[1]))
to_chat(usr, span_warning("Server is full."))
return

View File

@@ -373,7 +373,7 @@
/mob/dead/new_player/proc/poll_rank()
. = "Player"
if(client.holder)
. = client.holder.rank.name
. = client.holder.rank_name()
/mob/dead/new_player/proc/vote_rig_check()
@@ -423,7 +423,7 @@
var/datum/admins/holder = client.holder
var/rank = "Player"
if (holder)
rank = holder.rank.name
rank = holder.rank_name()
var/ckey = client.ckey
var/address = client.address
@@ -566,7 +566,7 @@
qdel(query_numval_hasvoted)
var/adminrank = "Player"
if(client.holder)
adminrank = client.holder.rank.name
adminrank = client.holder.rank_name()
if(isnull(rating))
rating = "null"
var/datum/DBQuery/query_numval_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime ,pollid ,optionid ,ckey ,ip ,adminrank, rating) VALUES (Now(), :pollid, :optionid, :ckey, INET_ATON(:address), :adminrank, :rating", list("pollid" = pollid, "optionid" = optionid, "ckey" = ckey, "address" = client.address, "adminrank" = adminrank, "rating" = rating))
@@ -611,7 +611,7 @@
return 2
var/adminrank = "Player"
if(!QDELETED(client) && client.holder)
adminrank = client.holder.rank.name
adminrank = client.holder.rank_name()
var/datum/DBQuery/query_multi_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime, pollid, optionid, ckey, ip, adminrank) VALUES (Now(), :pollid, :optionid, :ckey, INET_ATON(:address), :adminrank)", list("pollid" = pollid, "optionid" = optionid, "ckey" = ckey, "address" = client.address, "adminrank" = adminrank))
if(!query_multi_vote.warn_execute())
qdel(query_multi_vote)
@@ -619,4 +619,4 @@
qdel(query_multi_vote)
if(!QDELETED(usr))
usr << browse(null,"window=playerpoll")
return 0
return 0

View File

@@ -168,7 +168,7 @@ GLOBAL_LIST_EMPTY(adminfaxes)
/obj/machinery/photocopier/faxmachine/proc/send_adminmessage(var/mob/sender, var/faxname, var/obj/item/sent, var/reply_type, font_colour="#006100")
var/msg = "<b><font color='[font_colour]'>[faxname]: </font>[key_name(sender, 1)] (<A HREF='?_src_=holder;[HrefToken(TRUE)];adminplayeropts=\ref[sender]'>PP</A>) (<A HREF='?_src_=vars;[HrefToken(TRUE)];Vars=\ref[sender]'>VV</A>) (<A HREF='?_src_=holder;[HrefToken(TRUE)];subtlemessage=\ref[sender]'>SM</A>) (<A HREF='?_src_=holder;[HrefToken(TRUE)];adminplayerobservefollow=\ref[sender]'>JMP</A>) (<a href='?_src_=holder;[HrefToken(TRUE)];[reply_type]=\ref[sender];originfax=\ref[src]'>REPLY</a>)</b>: Receiving '[sent.name]' via secure connection ... <a href='?_src_=holder;[HrefToken(TRUE)];AdminFaxView=\ref[sent]'>view message</a>"
msg = span_admin("<span class=\"message\">[msg]</span>")
to_chat(GLOB.admins,
to_chat(GLOB.permissions.admins,
type = MESSAGE_TYPE_ADMINLOG,
html = msg,
confidential = TRUE)

View File

@@ -124,7 +124,7 @@
return
COOLDOWN_START(src, request_cooldown, 1 MINUTES)
to_chat(usr, span_notice("Your request has been received by CentCom."))
to_chat(GLOB.admins, "<b>FERRY: <font color='#3d5bc3'>[ADMIN_LOOKUPFLW(usr)] (<A HREF='?_src_=holder;[HrefToken()];secrets=moveferry'>Move Ferry</a>)</b> is requesting to move the transport ferry to CentCom.</font>")
to_chat(GLOB.permissions.admins, "<b>FERRY: <font color='#3d5bc3'>[ADMIN_LOOKUPFLW(usr)] (<A HREF='?_src_=holder;[HrefToken()];secrets=moveferry'>Move Ferry</a>)</b> is requesting to move the transport ferry to CentCom.</font>")
return TRUE
/obj/machinery/computer/shuttle/emag_act(mob/user)

View File

@@ -0,0 +1,15 @@
/**
* tgui state: admin_state
*
* Checks that the user is an admin, end-of-story.
*
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(permissions_state, /datum/ui_state/permissions_state, new)
/datum/ui_state/permissions_state/can_use_topic(src_object, mob/user)
if(check_rights_for(user.client, R_PERMISSIONS))
return UI_INTERACTIVE
return UI_CLOSE

View File

@@ -4,13 +4,12 @@
# Rank is CASE-SENSITIVE, all punctuation save for '-', '_' and '@' will be stripped so spaces don't matter. #
# You can then define permissions for each rank by adding a '=' followed by keywords #
# These keywords represent groups of verbs and abilities. #
# keywords are preceded by a '+', '-' or '*' + adds permissions, - takes them away. #
# * is used only with SQL-based admin loading to denote what permissions the rank is allowed to edit #
# keywords are preceded by a '+'. #
# +@ (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!
# 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.
# If network-based admin loading is enabled, ranks and their keywords listed here will be used for protected admins and temporary rank changes
# Follow the format below when documenting new keywords so the server tools may parse it
@@ -35,37 +34,16 @@
# END_KEYWORDS
Host = +EVERYTHING *EVERYTHING
Host = +EVERYTHING
Council Member = +EVERYTHING *EVERYTHING
Council Member = +EVERYTHING
Head Developer = +EVERYTHING *EVERYTHING
Head Developer = +EVERYTHING
SysOp = +EVERYTHING *EVERYTHING
#Senior Admin = +EVERYTHING *EVERYTHING -DEV
#Community Manager = +EVERYTHING *EVERYTHING -DEV
#Primary Admin = +STEALTH +ADMIN +BASIC +BAN +SPAWN +VAREDIT +DEBUG +SERVER +FUN +POSSESS +BUILD +TICKET +SOUND
#Administrator-Mainterino = +STEALTH +ADMIN +BASIC +BAN +SPAWN +VAREDIT +DEBUG +SERVER +FUN +TICKET +BUILD +POLL +DEV
#Administrator = +STEALTH +ADMIN +BASIC +BAN +SPAWN +VAREDIT +DEBUG +SERVER +FUN +TICKET +BUILD
#Moderator-Mainterino = +STEALTH +ADMIN +VAREDIT +DEBUG +SERVER +BASIC +SPAWN +POLL +BAN +DEV
#Moderator = +STEALTH +ADMIN +BASIC +BAN +TICKET
#Retired Admin = +ADMIN +STEALTH +BASIC +SERVER +BAN +TICKET +VAREDIT
#Admin Observer = +STEALTH
#Maintainer = +STEALTH +ADMIN +VAREDIT +DEBUG +SERVER +BASIC +SPAWN +POLL -AUTOLOGIN +DEV
#LogDiver =
#Forum Mod =
#Bot = +EVERYTHING *EVERYTHING
SysOp = +EVERYTHING
Administrator = +STEALTH +ADMIN +BASIC +BAN +SPAWN +VAREDIT +DEBUG +SERVER +FUN +TICKET +BUILD +AUTOLOGIN
Admin Observer = +STEALTH +AUTOLOGIN
Maintainer = +STEALTH +ADMIN +VAREDIT +DEBUG +SERVER +BASIC +SPAWN +POLL +DEV

View File

@@ -25,10 +25,6 @@ ROUND_END_COUNTDOWN 90
##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 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
## Uncomment this entry to have certain jobs require your account to be at least a certain number of days old to select. You can configure the exact age requirement for different jobs by editing
## the minimal_player_age variable in the files in folder /code/game/jobs/job/.. for the job you want to edit. Set minimal_player_age to 0 to disable age requirement for that job.
## REQUIRES the database set up to work. Keep it hashed if you don't have a database set up.

View File

@@ -19,10 +19,11 @@ SERVERSQLNAME yogstation
## Put on byond hub: Uncomment this to put your server on the byond hub.
#HUB
## 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
## Determines the backend provider of admin rank data
# Use 'database' to load from an SQL database, requires SQL_ENABLED
# Use 'forums' to load from the Xenforo plugin, requires XENFORO_KEY
# Any other value, or missing the required configuration settings will use the legacy system only
PERMISSIONS_BACKEND none
##Uncomment this to stop any ranks loaded by the legacy system from having their flags edited by the permissions panel
# PROTECT_LEGACY_RANKS

View File

@@ -19,10 +19,11 @@ SERVERSQLNAME yogstation
## Put on byond hub: Uncomment this to put your server on the byond hub.
HUB
## 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
## Determines the backend provider of admin rank data
# Use 'database' to load from an SQL database, requires SQL_ENABLED
# Use 'forums' to load from the Xenforo plugin, requires XENFORO_KEY
# Any other value, or missing the required configuration settings will use the legacy system only
PERMISSIONS_BACKEND forums
##Uncomment this to stop any ranks loaded by the legacy system from having their flags edited by the permissions panel
PROTECT_LEGACY_RANKS

View File

@@ -0,0 +1,88 @@
import { useBackend, useLocalState } from "../backend";
import { Box, Button, Collapsible, Flex, Section, Tabs } from "../components";
import { Layout, Window } from "../layouts";
import { createLogger } from "../logging";
const logger = createLogger('PermissionsPanel');
export const PermissionsPanel = (props, context) => {
const { act, data } = useBackend(context);
return (
<Window
title="Permissions Panel"
width={600}
height={500}
resizable >
<Window.Content scrollable>
<Button
icon="plus"
color="green"
mb={1}
onClick={() => act('addNewAdmin')}>
Add New
</Button>
{data.admins.map((admin, i) => (
<AdminDisplay
key={i}
data={admin}
act={act} />
))}
</Window.Content>
</Window>
);
};
export const AdminDisplay = props => {
const { data, act } = props;
const {
ckey,
rank,
protected_admin,
protected_rank,
rights,
deadminned,
} = data;
return (
<Section
title={`${ckey} - ${rank}`}
buttons={(
<Button
icon="trash"
color="bad"
onClick={() => act('removeAdmin', {
ckey: ckey,
})} />
)} >
<Button onClick={() => act('forceSwapAdmin', {
ckey: ckey,
})}>
Force { deadminned ? "Re" : "De" }admin
</Button>
<Button onClick={() => act('resetMFA', {
ckey: ckey,
})}>
Reset MFA
</Button>
<Button
disabled={protected_admin}
onClick={() => act('editRank', {
ckey: ckey,
})}>
Change Rank
</Button>
<Collapsible
title="Permissions"
buttons={(
<Button
icon="edit"
content="Edit"
disabled={protected_rank}
onClick={() => act('editPerms', {
ckey: ckey,
})} />
)}>
{ rights }
</Collapsible>
</Section>
);
};

View File

@@ -1301,7 +1301,6 @@
#include "code\game\turfs\space\transit.dm"
#include "code\modules\admin\admin.dm"
#include "code\modules\admin\admin_investigate.dm"
#include "code\modules\admin\admin_ranks.dm"
#include "code\modules\admin\admin_verbs.dm"
#include "code\modules\admin\adminmenu.dm"
#include "code\modules\admin\antag_panel.dm"
@@ -1327,6 +1326,9 @@
#include "code\modules\admin\team_panel.dm"
#include "code\modules\admin\topic.dm"
#include "code\modules\admin\whitelist.dm"
#include "code\modules\admin\permissions\database.dm"
#include "code\modules\admin\permissions\forums.dm"
#include "code\modules\admin\permissions\permissions.dm"
#include "code\modules\admin\verbs\adminjump.dm"
#include "code\modules\admin\verbs\adminpm.dm"
#include "code\modules\admin\verbs\atmosdebug.dm"
@@ -3248,6 +3250,7 @@
#include "code\modules\tgui\states\not_incapacitated.dm"
#include "code\modules\tgui\states\notcontained.dm"
#include "code\modules\tgui\states\observer.dm"
#include "code\modules\tgui\states\permissions.dm"
#include "code\modules\tgui\states\physical.dm"
#include "code\modules\tgui\states\self.dm"
#include "code\modules\tgui\states\zlevel.dm"
@@ -3507,7 +3510,6 @@
#include "yogstation\code\game\turfs\simulated\minerals.dm"
#include "yogstation\code\game\turfs\simulated\floor\fancy_floor.dm"
#include "yogstation\code\modules\admin\admin.dm"
#include "yogstation\code\modules\admin\admin_ranks.dm"
#include "yogstation\code\modules\admin\admin_verbs.dm"
#include "yogstation\code\modules\admin\holder2.dm"
#include "yogstation\code\modules\admin\moja.dm"

View File

@@ -40,12 +40,12 @@
if(ismob(user))
var/mob/temp = user
if(temp)
return (temp.ckey in GLOB.deadmins)
return (temp.ckey in GLOB.permissions.deadmins)
if(istype(user, /client))
var/client/temp = user
if(temp)
return (temp.ckey in GLOB.deadmins)
return (temp.ckey in GLOB.permissions.deadmins)
return FALSE

View File

@@ -5,7 +5,7 @@ GLOBAL_VAR_INIT(mentornoot, FALSE)
require_comms_key = TRUE
/datum/world_topic/asay/Run(list/input)
to_chat(GLOB.admins, span_adminsay("[span_prefix("DISCORD:")] <EM>[input["admin"]]</EM>: [span_message("[input["asay"]]")]"), confidential=TRUE)
to_chat(GLOB.permissions.admins, span_adminsay("[span_prefix("DISCORD:")] <EM>[input["admin"]]</EM>: [span_message("[input["asay"]]")]"), confidential=TRUE)
/datum/world_topic/ooc
keyword = "ooc"
@@ -29,7 +29,7 @@ GLOBAL_VAR_INIT(mentornoot, FALSE)
/datum/world_topic/adminwho/Run(list/input)
var/list/message = list("Admins: ")
var/list/admin_keys = list()
for(var/adm in GLOB.admins)
for(var/adm in GLOB.permissions.admins)
var/client/C = adm
if(input["adminchannel"])
admin_keys += "[C][C.holder.fakekey ? "(Stealth)" : ""][C.is_afk() ? "(AFK)" : ""]"
@@ -66,7 +66,7 @@ GLOBAL_VAR_INIT(mentornoot, FALSE)
require_comms_key = TRUE
/datum/world_topic/msay/Run(list/input)
to_chat(GLOB.admins | GLOB.mentors, "<b><font color ='#8A2BE2'>[span_prefix("DISCORD MENTOR:")]</span> <EM>[input["admin"]]</EM>: [span_message("[input["msay"]]")]</span>")
to_chat(GLOB.permissions.admins | GLOB.mentors, "<b><font color ='#8A2BE2'>[span_prefix("DISCORD MENTOR:")]</span> <EM>[input["admin"]]</EM>: [span_message("[input["msay"]]")]</span>")
/datum/world_topic/mhelp
keyword = "mhelp"
@@ -86,7 +86,7 @@ GLOBAL_VAR_INIT(mentornoot, FALSE)
SEND_SOUND(C, sound('sound/items/bikehorn.ogg'))
to_chat(C, "<font color='purple'>Mentor PM from-<b>[discord_mentor_link(from, from_id)]</b>: [msg]</font>")
var/show_char_recip = !C.is_mentor() && CONFIG_GET(flag/mentors_mobname_only)
for(var/client/X in GLOB.mentors | GLOB.admins)
for(var/client/X in GLOB.mentors | GLOB.permissions.admins)
if(X != C)
to_chat(X, "<B><font color='green'>Mentor PM: [discord_mentor_link(from, from_id)]-&gt;[key_name_mentor(C, X, 0, 0, show_char_recip)]:</B> <font color ='cyan'> [msg]</font>")
return 1

View File

@@ -1,44 +0,0 @@
// Kn0ss0s: This proc allows your to use a database for admin ranks, whilst immediately providing a functioning actual fallback in the case of failure
/proc/refresh_admin_files()
return //this is a stupid system
/*
// Generate the Admins and Admins Ranks config files
if(SSdbcore.IsConnected())
var/datum/DBQuery/query_ranks = SSdbcore.NewQuery("SELECT `name`, `byond`, `rank_group` FROM `web_groups` ORDER BY `web_groups`.`rank_group` ASC, `web_groups`.`name` DESC")
if(query_ranks.Execute())
fdel("config/admin_ranks.txt")
var/ranksFile = file("config/admin_ranks.txt")
WRITE_FILE(ranksFile, "##############################################################################################################\n# ADMIN RANK DEFINES #\n# The format of this is very simple. Rank name goes first. #\n# Rank is CASE-SENSITIVE, all punctuation save for '-', '_' and '@' will be stripped so spaces don't matter. #\n# You can then define permissions for each rank by adding a '=' followed by keywords #\n# These keywords represent groups of verbs and abilities. #\n# keywords are preceded by either a '+' or a '-', + adds permissions, - takes them away. #\n# +@ (or +prev) is a special shorthand which adds all the rights of the rank above it. #\n# You can also specify verbs like so +/client/proc/some_added_verb or -/client/proc/some_restricted_verb #\n# Ranks with no keywords will just be given the most basic verbs and abilities ~Carn #\n##############################################################################################################\n# PLEASE NOTE: depending on config options, some abilities will be unavailable regardless if you have permission to use them!\n\n# KEYWORDS:\n# +ADMIN = general admin tools, verbs etc\n# +FUN = events, other event-orientated actions. Access to the fun secrets in the secrets panel.\n# +BAN = the ability to ban, jobban and fullban\n# +STEALTH = the ability to stealthmin (make yourself appear with a fake name to everyone but other admins\n# +POSSESS = the ability to possess objects\n# +REJUV (or +REJUVINATE) = the ability to heal, respawn, modify damage and use godmode\n# +BUILD (or +BUILDMODE) = the ability to use buildmode\n# +SERVER = higher-risk admin verbs and abilities, such as those which affect the server configuration.\n# +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.\n# +VAREDIT = everyone may view viewvars/debugvars/whatever you call it. This keyword allows you to actually EDIT those variables.\n# +RIGHTS (or +PERMISSIONS) = allows you to promote and/or demote people.\n# +SOUND (or +SOUNDS) = allows you to upload and play sounds\n# +SPAWN (or +CREATE) = mob transformations, spawning of most atoms including mobs (high-risk atoms, e.g. blackholes, will require the +FUN flag too)\n# +EVERYTHING (or +HOST or +ALL) = Simply gives you everything without having to type every flag\n\n# DO NOT EDIT THIS FILE DIRECTLY\n# IT IS AUTOMATICALLY GENERATED FROM THE SERVER DATABASE\n")
var/lastGroup = 1
// Write out each rank to the rank file
while(query_ranks.NextRow())
var/rank_name = query_ranks.item[1]
var/rank_byond = query_ranks.item[2]
var/rank_group = text2num(query_ranks.item[3])
if(lastGroup != rank_group)
lastGroup = rank_group
WRITE_FILE(ranksFile, " ")
WRITE_FILE(ranksFile, "[rank_name]\t=\t[rank_byond]")
qdel(query_ranks)
var/datum/DBQuery/query_admin = SSdbcore.NewQuery("SELECT `web_admins`.`username` AS admin, `web_groups`.`name` AS adminrank, `web_groups`.`rank_group` AS rank_group FROM `web_admins`, `web_groups` WHERE `web_admins`.`rank` = `web_groups`.`rankid` ORDER BY `web_groups`.`rank_group` ASC, adminrank DESC")
if(query_admin.Execute())
fdel("config/admins.txt")
var/adminsFile = file("config/admins.txt")
WRITE_FILE(adminsFile, "###############################################################################################\n# Basically, ckey goes first. Rank goes after the '=' #\n# Case is not important for ckey. #\n# Case IS important for the rank. #\n# All punctuation (spaces etc) EXCEPT '-', '_' and '@' will be stripped from rank names. #\n# Ranks can be anything defined in admin_ranks.txt #\n# NOTE: if the rank-name cannot be found in admin_ranks.txt, they will not be adminned! ~Carn #\n# NOTE: syntax was changed to allow hyphenation of ranknames, since spaces are stripped. #\n###############################################################################################\n\n# DO NOT EDIT THIS FILE DIRECTLY\n# IT IS AUTOMATICALLY GENERATED FROM THE SERVER DATABASE\n")
var/lastGroup = 1
// Write out each admin to the admins file
while(query_admin.NextRow())
var/name = query_admin.item[1]
var/adminrank = query_admin.item[2]
var/rank_group = text2num(query_admin.item[3])
if(lastGroup != rank_group)
lastGroup = rank_group
WRITE_FILE(adminsFile, " ")
WRITE_FILE(adminsFile, "[name]\t=\t[adminrank]")
qdel(query_admin)
*/

View File

@@ -203,10 +203,10 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
return
// There are no admins online, try deadmins
var/found_deadmin = FALSE
if(GLOB.deadmins.len > 0)
for(var/deadmin_ckey in GLOB.deadmins)
var/datum/admins/A = GLOB.deadmins[deadmin_ckey]
if(!A.check_for_rights(R_BAN))
if(GLOB.permissions.deadmins.len > 0)
for(var/deadmin_ckey in GLOB.permissions.deadmins)
var/datum/admins/A = GLOB.permissions.deadmins[deadmin_ckey]
if(!check_rights_for(A.owner, R_BAN))
continue
var/client/client = GLOB.directory[deadmin_ckey]
if(!client)
@@ -231,7 +231,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
/datum/admin_help/proc/check_owner() // Handles unclaimed tickets; returns TRUE if no longer unclaimed
if(!handling_admin && state == AHELP_ACTIVE)
var/msg = span_admin("<span class=\"prefix\">ADMIN LOG:</span> <span class=\"message linkify\"><font color='blue'>Ticket [TicketHref("#[id]")] Unclaimed!</font></span>")
for(var/client/X in GLOB.admins)
for(var/client/X in GLOB.permissions.admins)
if(check_rights_for(X,R_BAN))
to_chat(X,
type = MESSAGE_TYPE_ADMINLOG,
@@ -248,7 +248,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
if(world.time > last_bwoinking)
last_bwoinking = world.time + 1 SECONDS
for(var/client/X in GLOB.admins)
for(var/client/X in GLOB.permissions.admins)
if(check_rights_for(X,R_BAN) && (X.prefs.toggles & SOUND_ADMINHELP)) // Can't use check_rights here since it's dependent on $usr
SEND_SOUND(X, sound('sound/effects/adminhelp.ogg'))
return FALSE
@@ -304,7 +304,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
AddInteraction(msg)
//send this msg to all admins
for(var/client/X in GLOB.admins)
for(var/client/X in GLOB.permissions.admins)
if(X.prefs.toggles & SOUND_ADMINHELP)
SEND_SOUND(X, sound('sound/effects/adminhelp.ogg'))
window_flash(X, ignorepref = TRUE)
@@ -945,7 +945,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
/proc/get_admin_counts(requiredflags = R_BAN)
. = list("total" = list(), "noflags" = list(), "afk" = list(), "stealth" = list(), "present" = list())
for(var/client/X in GLOB.admins)
for(var/client/X in GLOB.permissions.admins)
.["total"] += X
if(requiredflags != 0 && !check_rights_for(X, requiredflags))
.["noflags"] += X
@@ -999,7 +999,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
/proc/ircadminwho()
var/list/message = list("Admins: ")
var/list/admin_keys = list()
for(var/adm in GLOB.admins)
for(var/adm in GLOB.permissions.admins)
var/client/C = adm
admin_keys += "[C][C.holder.fakekey ? "(Stealth)" : ""][C.is_afk() ? "(AFK)" : ""]"

View File

@@ -20,10 +20,10 @@
msg = keywords_lookup(msg)
if(check_rights(R_ADMIN,0))
msg = span_adminsay("[span_prefix("ADMIN:")] <EM>[key_name(usr, 1)]</EM> [ADMIN_FLW(mob)]: [span_message("[msg]")]")
to_chat(GLOB.admins, msg, confidential=TRUE)
to_chat(GLOB.permissions.admins, msg, confidential=TRUE)
else
msg = span_adminsay("[span_prefix("OBSERVER:")] <EM>[key_name(usr, 1)]</EM> [ADMIN_FLW(mob)]: [span_message("[msg]")]")
to_chat(GLOB.admins, msg, confidential=TRUE)
to_chat(GLOB.permissions.admins, msg, confidential=TRUE)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Asay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!

View File

@@ -31,8 +31,8 @@ GLOBAL_LIST_EMPTY(antag_token_users)
return
to_chat(src, span_userdanger("You will be notified if your antag token is used"))
C.antag_token_timer = addtimer(CALLBACK(src, .proc/deny_antag_token_request), 45 SECONDS, TIMER_STOPPABLE)
to_chat(GLOB.admins, span_adminnotice("<b><font color=orange>ANTAG TOKEN REQUEST:</font></b>[ADMIN_LOOKUPFLW(usr)] wants to use their antag token! (will auto-DENY in [DisplayTimeText(45 SECONDS)]). (<A HREF='?_src_=holder;[HrefToken(TRUE)];approve_antag_token=[REF(C)]'>APPROVE</A>)"))
for(var/client/A in GLOB.admins)
to_chat(GLOB.permissions.admins, span_adminnotice("<b><font color=orange>ANTAG TOKEN REQUEST:</font></b>[ADMIN_LOOKUPFLW(usr)] wants to use their antag token! (will auto-DENY in [DisplayTimeText(45 SECONDS)]). (<A HREF='?_src_=holder;[HrefToken(TRUE)];approve_antag_token=[REF(C)]'>APPROVE</A>)"))
for(var/client/A in GLOB.permissions.admins)
if(check_rights_for(A, R_ADMIN) && (A.prefs.toggles & SOUND_ADMINHELP)) // Can't use check_rights here since it's dependent on $usr
SEND_SOUND(A, sound('sound/effects/adminhelp.ogg'))
else

View File

@@ -85,7 +85,7 @@
for(var/T in GLOB.clients)
var/client/C = T
if(C in GLOB.admins)
if(C in GLOB.permissions.admins)
if(C in clients_to_hear)
to_chat(C, message_admin)
else

View File

@@ -1,7 +1,7 @@
/client/proc/find_admin_rank(client)
var/client/C = client
switch(C.holder.rank.name)
switch(C.holder.rank_name())
if("CouncilMember")
return "\[Council\]"
@@ -31,7 +31,7 @@
return "\[Retmin-tainer\]"
else
return "\[[C.holder.rank.name]\]"
return "\[[C.holder.rank_name()]\]"
/client/verb/give_tip()
set name = "Give Random Tip"

View File

@@ -30,9 +30,9 @@
var/msg = ""
var/list/Lines = list()
if(length(GLOB.admins))
if(length(GLOB.permissions.admins))
Lines += "<b>Admins:</b>"
for(var/X in GLOB.admins)
for(var/X in GLOB.permissions.admins)
var/client/C = X
if(C && C.holder && !C.holder.fakekey)
Lines += "\t <font color='#FF0000'>[C.key]</font>[show_admin_info(C)] ([round(C.avgping, 1)]ms)"

View File

@@ -25,7 +25,7 @@
if(mentor_datum)
mentor_datum.following = M
to_chat(GLOB.admins, span_mentor("[span_prefix("MENTOR:")] <EM>[key_name(usr)]</EM> is now following <EM>[key_name(M)]"), confidential=TRUE)
to_chat(GLOB.permissions.admins, span_mentor("[span_prefix("MENTOR:")] <EM>[key_name(usr)]</EM> is now following <EM>[key_name(M)]"), confidential=TRUE)
to_chat(usr, span_info("Click the \"Stop Following\" button in the Mentor tab to stop following [key_name(M)]."), confidential=TRUE)
log_mentor("[key_name(usr)] began following [key_name(M)]")
@@ -40,7 +40,7 @@
usr.reset_perspective()
remove_verb(src, /client/proc/mentor_unfollow)
if(mentor_datum)
to_chat(GLOB.admins, span_mentor("[span_prefix("MENTOR:")] <EM>[key_name(usr)]</EM> is no longer following <EM>[key_name(mentor_datum.following)]"), confidential=TRUE)
to_chat(GLOB.permissions.admins, span_mentor("[span_prefix("MENTOR:")] <EM>[key_name(usr)]</EM> is no longer following <EM>[key_name(mentor_datum.following)]"), confidential=TRUE)
log_mentor("[key_name(usr)] stopped following [key_name(mentor_datum.following)]")
mentor_datum.following = null

View File

@@ -32,7 +32,7 @@
var/mentor_msg = "<span class='mentornotice purple'><b>[admininfo]</b> <b>[key_name_mentor(src, 1, 0, 1, show_char)]</b>: [msg]</span>"
log_mentor("[admininfo] [key_name_mentor(src, 0, 0, 0, 0)]: [msg]")
for(var/client/X in GLOB.mentors | GLOB.admins)
for(var/client/X in GLOB.mentors | GLOB.permissions.admins)
if(X.prefs.toggles & SOUND_ADMINHELP)
send_mentor_sound(X)
to_chat(X, mentor_msg, confidential=TRUE)

View File

@@ -54,12 +54,12 @@
//get message text, limit it's length.and clean/escape html
if(!msg)
if(is_mentor())
to_chat((GLOB.admins - GLOB.deadmins) | GLOB.mentors, "<b><span class='purple mentor'>[key_name_mentor(src)] has started answering [key_name_mentor(C)]'s mentorhelp.</span></b>", confidential=TRUE)
to_chat((GLOB.permissions.admins - GLOB.permissions.deadmins) | GLOB.mentors, "<b><span class='purple mentor'>[key_name_mentor(src)] has started answering [key_name_mentor(C)]'s mentorhelp.</span></b>", confidential=TRUE)
msg = input(src,"Message:", "Private message") as text|null
if(!msg)
if(is_mentor())
to_chat((GLOB.admins - GLOB.deadmins) | GLOB.mentors, "<b><span class='purple mentor'>[key_name_mentor(src)] has decided not to answer [key_name_mentor(C)]'s mentorhelp.</span></b>", confidential=TRUE)
to_chat((GLOB.permissions.admins - GLOB.permissions.deadmins) | GLOB.mentors, "<b><span class='purple mentor'>[key_name_mentor(src)] has decided not to answer [key_name_mentor(C)]'s mentorhelp.</span></b>", confidential=TRUE)
return
// Neither party is a mentor, they shouldn't be PMing!
@@ -112,7 +112,7 @@
//we don't use message_Mentors here because the sender/receiver might get it too
var/show_char_sender = !is_mentor() && CONFIG_GET(flag/mentors_mobname_only)
var/show_char_recip = C && !C.is_mentor() && CONFIG_GET(flag/mentors_mobname_only)
for(var/client/X in GLOB.mentors | (GLOB.admins - GLOB.deadmins))
for(var/client/X in GLOB.mentors | (GLOB.permissions.admins - GLOB.permissions.deadmins))
if(X.key != key && (!C || X.key != C.key)) //check client/X is an Mentor and isn't the sender or recipient
if(discord_id)
to_chat(X, "<B><font color='green mentor'>Mentor PM: [key_name_mentor(src, X, 0, 0, show_char_sender)]-&gt;[discord_mentor_link(whom, discord_id)]:</B> <span class='blueteamradio mentor'> [msg]</span>", confidential=TRUE) //inform X

View File

@@ -23,7 +23,7 @@
else
msg = "<b><font color ='#E236D8'><span class='prefix mentor'>MENTOR:</span> <EM>[key_name(src, 0, 0)]</EM>: <span class='message mentor'>[msg]</span></font></b>"
to_chat((GLOB.admins - GLOB.deadmins) | GLOB.mentors, msg, confidential=TRUE)
to_chat((GLOB.permissions.admins - GLOB.permissions.deadmins) | GLOB.mentors, msg, confidential=TRUE)
/client/proc/get_mentor_say()
var/msg = input(src, null, "msay \"text\"") as text|null