Make DB admin ranks more useful (#29906)

* Make DB admin ranks more useful

* Avoid error message when cancelling permission toggle, allow adding localhost admins as real admins.

* Lint.

* Don't SQL error when a previously-unseen player connects.

* Use ckey of permission editor, not mob name.

* Strikethrough

* Order in the list.

* Deadmin, readmin, and 2fa.

* Correct merge error

---------

Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
This commit is contained in:
Charlie Nolan
2025-08-10 04:05:19 -07:00
committed by GitHub
parent f67dd166cb
commit 2b98749d32
20 changed files with 1037 additions and 469 deletions
+123 -32
View File
@@ -48,41 +48,119 @@ GLOBAL_PROTECT(admin_ranks) // this shit is being protected for obvious reasons
testing(msg)
#endif
/proc/reload_one_admin(admin_ckey, silent = FALSE)
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='boldannounce'>Admin reload blocked: Advanced ProcCall detected.</span>")
message_admins("[key_name(usr)] attempted to reload an admin via advanced proc-call")
log_admin("[key_name(usr)] attempted to reload an admin via advanced proc-call")
return
// Make sure it's actually in ckey format.
admin_ckey = ckey(admin_ckey)
// Remove any existing permissions.
var/datum/admins/admin_datum = GLOB.admin_datums[admin_ckey]
if(admin_datum)
qdel(admin_datum)
if(admin_ckey in (GLOB.de_admins + GLOB.de_mentors))
if(!silent)
message_admins("<span class='notice'>Admin permissions for [admin_ckey] will reload when they re-admin.</span>")
return
if(admin_ckey in GLOB.directory)
var/client/admin = GLOB.directory[admin_ckey]
var/localhosted = admin.try_localhost_autoadmin()
if(localhosted)
if(!silent)
message_admins("<span class='notice'>Admin permissions for [admin_ckey] reloaded. Full permissions granted as they are currently connected from localhost.</span>")
return
if(GLOB.configuration.admin.use_database_admins)
var/datum/db_query/get_admin = SSdbcore.NewQuery({"
SELECT
-- Use the display_rank if set, otherwise the name of their permissions_rank.
IFNULL(admin.display_rank, admin_ranks.name),
-- Permissions start with their admin rank permissions (if any)
(IFNULL(admin_ranks.default_permissions, 0)
-- Then add in any extra permissions they've been granted.
| admin.extra_permissions)
-- And exclude any permissions they've had removed.
& ~admin.removed_permissions
FROM admin
-- We want all admins, and admin_ranks where available.
LEFT OUTER JOIN admin_ranks
ON admin.permissions_rank = admin_ranks.id
WHERE admin.ckey=:admin_ckey"}, list(
"admin_ckey" = admin_ckey
))
if(!get_admin.warn_execute())
qdel(get_admin)
return
if(get_admin.NextRow())
var/rank = get_admin.item[1]
var/rights = get_admin.item[2]
if(rights == 0)
// If you have no rights, you don't get an admin datum.
qdel(get_admin)
if(!silent)
message_admins("<span class='notice'>Admin permissions for [admin_ckey] have been reloaded.</span>")
return
admin_datum = new(rank, rights, admin_ckey)
qdel(get_admin)
else
var/rank = GLOB.configuration.admin.ckey_rank_map[admin_ckey]
// Load permissions associated with this rank
var/rights = GLOB.admin_ranks[rank]
// Create their admin datum.
admin_datum = new /datum/admins(rank, rights, admin_ckey)
// Set up their permissions.
if(admin_datum)
admin_datum.associate(GLOB.directory[admin_ckey])
if(admin_ckey in GLOB.directory)
var/client/admin = GLOB.directory[admin_ckey]
to_chat(admin, "Holder? [admin.holder] Permissions? [admin.holder?.rights]")
if(!silent)
message_admins("<span class='notice'>Admin permissions for [admin_ckey] have been reloaded.</span>")
/proc/load_admins(run_async = FALSE)
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='boldannounce'>Admin reload blocked: Advanced ProcCall detected.</span>")
message_admins("[key_name(usr)] attempted to reload admins via advanced proc-call")
log_admin("[key_name(usr)] attempted to reload admins via advanced proc-call")
return
//clear the datums references
// Revoke all permissions.
var/list/localhost_admins = list()
for(var/datum/admins/admin_datum in GLOB.admin_datums)
if(admin_datum.is_localhost_autoadmin && admin_datum.owner)
localhost_admins += admin_datum.owner
qdel(admin_datum)
GLOB.admin_datums.Cut()
for(var/client/C in GLOB.admins)
C.hide_verbs()
C.holder = null
GLOB.admins.Cut()
// Remove all profiler access
// Just to be double-sure, revoke all profiler access
for(var/A in world.GetConfig("admin"))
world.SetConfig("APP/admin", A, null)
if(!GLOB.configuration.admin.use_database_admins)
load_admin_ranks()
//process each line seperately
for(var/iterator_key in GLOB.configuration.admin.ckey_rank_map)
var/ckey = ckey(iterator_key) // Snip out formatting
var/rank = GLOB.configuration.admin.ckey_rank_map[iterator_key]
for(var/ckey in GLOB.configuration.admin.ckey_rank_map)
var/rank = GLOB.configuration.admin.ckey_rank_map[ckey]
//load permissions associated with this rank
// Load permissions associated with this rank
var/rights = GLOB.admin_ranks[rank]
//create the admin datum and store it for later use
// Create their admin datum.
var/datum/admins/D = new /datum/admins(rank, rights, ckey)
if(D.rights & R_DEBUG || D.rights & R_VIEWRUNTIMES) // Grants profiler access to anyone with R_DEBUG or R_VIEWRUNTIMES
world.SetConfig("APP/admin", ckey, "role=admin")
//find the client for a ckey if they are connected and associate them with the new admin datum
// Set up their admin permissions.
D.associate(GLOB.directory[ckey])
else
@@ -93,34 +171,47 @@ GLOBAL_PROTECT(admin_ranks) // this shit is being protected for obvious reasons
load_admins()
return
var/datum/db_query/query = SSdbcore.NewQuery("SELECT ckey, admin_rank, level, flags FROM admin")
if(!query.warn_execute(async=run_async))
qdel(query)
var/datum/db_query/get_admins = SSdbcore.NewQuery({"
SELECT
admin.ckey,
-- Use the display_rank if set, otherwise the name of their permissions_rank.
IFNULL(admin.display_rank, admin_ranks.name),
-- Permissions start with their admin rank permissions (if any)
(IFNULL(admin_ranks.default_permissions, 0)
-- Then add in any extra permissions they've been granted.
| admin.extra_permissions)
-- And exclude any permissions they've had removed.
& ~admin.removed_permissions
FROM admin
-- We want all admins, and admin_ranks where available.
LEFT OUTER JOIN admin_ranks
ON admin.permissions_rank = admin_ranks.id"})
if(!get_admins.warn_execute(async=run_async))
qdel(get_admins)
return
while(query.NextRow())
var/ckey = query.item[1]
var/rank = query.item[2]
if(rank == "Removed") continue //This person was de-adminned. They are only in the admin list for archive purposes.
var/rights = query.item[4]
if(istext(rights)) rights = text2num(rights)
while(get_admins.NextRow())
var/ckey = get_admins.item[1]
var/rank = get_admins.item[2]
var/rights = get_admins.item[3]
if(rights == 0)
// If you have no rights, you don't get an admin datum.
continue
var/datum/admins/D = new /datum/admins(rank, rights, ckey)
if(D.rights & R_DEBUG || D.rights & R_VIEWRUNTIMES) // Grants profiler access to anyone with R_DEBUG or R_VIEWRUNTIMES
world.SetConfig("APP/admin", ckey, "role=admin")
//find the client for a ckey if they are connected and associate them with the new admin datum
// Set up their admin permissions.
D.associate(GLOB.directory[ckey])
qdel(get_admins)
qdel(query)
if(!GLOB.admin_datums)
if(!length(GLOB.admin_datums))
log_world("The database query in load_admins() resulted in no admins being added to the list. Reverting to legacy system.")
GLOB.configuration.admin.use_database_admins = FALSE
load_admins()
return
for(var/client/localhost_admin in localhost_admins)
localhost_admin.try_localhost_autoadmin()
#ifdef TESTING
var/msg = "Admins Built:\n"
for(var/ckey in GLOB.admin_datums)
-109
View File
@@ -879,119 +879,10 @@ GLOBAL_LIST_INIT(view_logs_verbs, list(
log_admin("[key_name(usr)] deadmined themself.")
message_admins("[key_name_admin(usr)] deadmined themself.")
if(check_rights(R_ADMIN, FALSE))
GLOB.de_admins += ckey
else
GLOB.de_mentors += ckey
deadmin()
add_verb(src, /client/proc/readmin)
update_active_keybindings()
to_chat(src, "<span class='interface'>You are now a normal player.</span>")
SSblackbox.record_feedback("tally", "admin_verb", 1, "De-admin") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/readmin()
set name = "Re-admin self"
set category = "Admin"
set desc = "Regain your admin powers."
var/datum/admins/D = GLOB.admin_datums[ckey]
var/rank = null
if(!GLOB.configuration.admin.use_database_admins)
for(var/iterator_key in GLOB.configuration.admin.ckey_rank_map)
var/_ckey = ckey(iterator_key) // Snip out formatting
if(ckey != _ckey)
continue
rank = GLOB.configuration.admin.ckey_rank_map[iterator_key]
break
else
if(!SSdbcore.IsConnected())
to_chat(src, "Warning, MYSQL database is not connected.")
return
var/datum/db_query/rank_read = SSdbcore.NewQuery(
"SELECT admin_rank FROM admin WHERE ckey=:ckey",
list("ckey" = ckey)
)
if(!rank_read.warn_execute())
qdel(rank_read)
return FALSE
while(rank_read.NextRow())
rank = ckeyEx(rank_read.item[1])
qdel(rank_read)
if(!D)
D = try_localhost_autoadmin()
if(!D)
if(!GLOB.configuration.admin.use_database_admins)
if(GLOB.admin_ranks[rank] == null)
error("Error while re-adminning [src], admin rank ([rank]) does not exist.")
to_chat(src, "Error while re-adminning, admin rank ([rank]) does not exist.")
return
// Do a little check here
if(GLOB.configuration.system.is_production && (GLOB.admin_ranks[rank] & R_ADMIN) && prefs._2fa_status == _2FA_DISABLED) // If they are an admin and their 2FA is disabled
to_chat(src,"<span class='boldannounceooc'><big>You do not have 2FA enabled. Admin verbs will be unavailable until you have enabled 2FA.</big></span>") // Very fucking obvious
return
D = new(rank, GLOB.admin_ranks[rank], ckey)
else
if(!SSdbcore.IsConnected())
to_chat(src, "Warning, MYSQL database is not connected.")
return
var/datum/db_query/admin_read = SSdbcore.NewQuery(
"SELECT ckey, admin_rank, flags FROM admin WHERE ckey=:ckey",
list("ckey" = ckey)
)
if(!admin_read.warn_execute())
qdel(admin_read)
return FALSE
while(admin_read.NextRow())
var/admin_ckey = admin_read.item[1]
var/admin_rank = admin_read.item[2]
var/flags = admin_read.item[3]
if(!admin_ckey)
to_chat(src, "Error while re-adminning, ckey [admin_ckey] was not found in the admin database.")
qdel(admin_read)
return
if(admin_rank == "Removed") //This person was de-adminned. They are only in the admin list for archive purposes.
to_chat(src, "Error while re-adminning, ckey [admin_ckey] is not an admin.")
qdel(admin_read)
return
if(istext(flags))
flags = text2num(flags)
var/client/check_client = GLOB.directory[ckey]
// Do a little check here
if(GLOB.configuration.system.is_production && (flags & R_ADMIN) && check_client.prefs._2fa_status == _2FA_DISABLED) // If they are an admin and their 2FA is disabled
to_chat(src,"<span class='boldannounceooc'><big>You do not have 2FA enabled. Admin verbs will be unavailable until you have enabled 2FA.</big></span>") // Very fucking obvious
qdel(admin_read)
return
D = new(admin_rank, flags, ckey)
qdel(admin_read)
var/client/C = GLOB.directory[ckey]
D.associate(C)
update_active_keybindings()
message_admins("[key_name_admin(usr)] re-adminned themselves.")
log_admin("[key_name(usr)] re-adminned themselves.")
GLOB.de_admins -= ckey
GLOB.de_mentors -= ckey
if(istype(mob, /mob/dead/observer))
var/mob/dead/observer/O = mob
O.update_admin_actions()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Re-admin")
return
else
to_chat(src, "You are already an admin.")
remove_verb(src, /client/proc/readmin)
GLOB.de_admins -= ckey
GLOB.de_mentors -= ckey
return
/client/proc/toggle_log_hrefs()
set name = "Toggle href logging"
set category = "Server"
+148 -98
View File
@@ -10,10 +10,15 @@ GLOBAL_PROTECT(href_token)
. += "[rand(10)]"
/datum/admins
var/rank = "Temporary Admin"
var/rank = "No Rank"
var/ckey
var/client/owner
/// Bitflag containing the current rights this admin holder is assigned to
var/rights = 0
/// Tracks if their permissions have been reduced due to a lack of 2fa.
var/restricted_by_2fa = FALSE
/// Was this auto-created for someone connecting from localhost?
var/is_localhost_autoadmin = FALSE
var/fakekey
var/big_brother = FALSE
var/current_tab = 0
@@ -27,7 +32,7 @@ GLOBAL_PROTECT(href_token)
/// Our index into GLOB.antagonist_teams, so that admins can have pretty tabs in the Check Teams menu.
var/team_switch_tab_index = 1
/datum/admins/New(initial_rank = "Temporary Admin", initial_rights = 0, ckey)
/datum/admins/New(initial_rank, initial_rights, ckey)
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='boldannounceooc'>Admin rank creation blocked: Advanced ProcCall detected.</span>")
message_admins("[key_name(usr)] attempted to create a new admin rank via advanced proc-call")
@@ -37,8 +42,11 @@ GLOBAL_PROTECT(href_token)
error("Admin datum created without a ckey argument. Datum has been deleted")
qdel(src)
return
rank = initial_rank
rights = initial_rights
src.ckey = ckey
if(initial_rank)
rank = initial_rank
if(initial_rights)
rights = initial_rights
href_token = GenerateToken()
GLOB.admin_datums[ckey] = src
@@ -48,22 +56,68 @@ GLOBAL_PROTECT(href_token)
message_admins("[key_name(usr)] attempted to delete an admin rank via advanced proc-call")
log_admin("[key_name(usr)] attempted to delete an admin rank via advanced proc-call")
return
disassociate()
..()
return QDEL_HINT_HARDDEL_NOW
/datum/admins/proc/associate(client/C)
/datum/admins/proc/associate(client/C, delay_2fa_complaint = FALSE)
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='boldannounceooc'>Rank association blocked: Advanced ProcCall detected.</span>")
message_admins("[key_name(usr)] attempted to associate an admin rank to a new client via advanced proc-call")
log_admin("[key_name(usr)] attempted to associate an admin rank to a new client via advanced proc-call")
return
if(istype(C))
owner = C
owner.holder = src
owner.add_admin_verbs() //TODO
remove_verb(owner, /client/proc/readmin)
owner.init_verbs() //re-initialize the verb list
GLOB.admins |= C
if(istype(C) && C.ckey != ckey)
CRASH("Admin datum belonging to [ckey] was asked to associate to client belonging to [C.ckey].")
owner = C
// Check for 2fa, if required.
if(needs_2fa() && istype(owner) && !owner.has_2fa())
// Disable most permissions.
rights = rights & (R_MENTOR | R_VIEWRUNTIMES)
restricted_by_2fa = TRUE
if(!delay_2fa_complaint)
// And tell them about it.
to_chat(owner,"<span class='boldannounceooc'><big>You do not have 2FA enabled. Admin verbs will be unavailable until you have enabled 2FA.</big></span>") // Very fucking obvious
// Regardless of client, tell BYOND if they should have profiler access.
if(rights & (R_DEBUG | R_VIEWRUNTIMES))
world.SetConfig("APP/admin", ckey, "role=admin")
if(!istype(owner))
return
owner.holder = src
owner.add_admin_verbs()
if(istype(owner.mob, /mob/dead/observer))
var/mob/dead/observer/ghost = owner.mob
ghost.update_admin_actions()
remove_verb(owner, /client/proc/readmin)
owner.init_verbs() //re-initialize the verb list
owner.update_active_keybindings()
if(rights & (R_DEBUG | R_VIEWRUNTIMES))
// Enable the buttons they should have.
winset(owner, "debugmcbutton", "is-disabled=true")
winset(owner, "profilecode", "is-disabled=true")
GLOB.admins |= owner
GLOB.de_admins -= ckey
GLOB.de_mentors -= ckey
/datum/admins/proc/needs_2fa()
if((rights & (R_MENTOR | R_VIEWRUNTIMES)) == rights)
// No significant permissions.
return FALSE
if(GLOB.configuration.admin.enable_localhost_autoadmin && owner && owner.is_connecting_from_localhost())
// Localhost connections are immune.
return FALSE
if(!GLOB.configuration.system.is_production)
// 2fa isn't required for anyone.
return FALSE
return TRUE
/datum/admins/proc/disassociate()
if(IsAdminAdvancedProcCall())
@@ -71,72 +125,74 @@ GLOBAL_PROTECT(href_token)
message_admins("[key_name(usr)] attempted to disassociate an admin rank from a client via advanced proc-call")
log_admin("[key_name(usr)] attempted to disassociate an admin rank from a client via advanced proc-call")
return
GLOB.admin_datums -= ckey
// Regardless of client, tell BYOND they shouldn't have access to the profiler.
world.SetConfig("APP/admin", ckey, null)
if(owner)
GLOB.admins -= owner
owner.hide_verbs()
owner.init_verbs()
owner.holder = null
owner.init_verbs()
if(istype(owner.mob, /mob/dead/observer))
var/mob/dead/observer/ghost = owner.mob
ghost.update_admin_actions()
owner.update_active_keybindings()
// Disable buttons they should no longer have.
winset(owner, "debugmcbutton", "is-disabled=true")
winset(owner, "profilecode", "is-disabled=true")
owner = null
/*
checks if usr is an admin with at least ONE of the flags in rights_required. (Note, they don't need all the flags)
if rights_required == 0, then it simply checks if they are an admin.
if it doesn't return 1 and show_msg=1 it will prints a message explaining why the check has failed
generally it would be used like so:
Checks if usr is an admin.
If rights_required == 0, then it simply checks if they are an admin.
If all is set, requires all the rights listed. Otherwise, any matching right is sufficient.
With show_msg, if the check fails, prints a message explaining why.
proc/admin_proc()
if(!check_rights(R_ADMIN)) return
Usage:
/some/proc()
if(!check_rights(R_ADMIN))
return
to_chat(world, "you have enough rights!")
NOTE: it checks usr! not src! So if you're checking somebody's rank in a proc which they did not call
you will have to do something like if(client.holder.rights & R_ADMIN) yourself.
NOTE: checks usr by default, not src
*/
/proc/check_rights(rights_required, show_msg = TRUE, mob/user = usr)
/proc/check_rights(rights_required, show_msg = TRUE, mob/user = usr, all = FALSE)
if(user && user.client)
if(rights_required)
if(user.client.holder)
if(rights_required & user.client.holder.rights)
return 1
else
if(show_msg)
to_chat(user, "<font color='red'>Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")].</font>")
else
if(user.client.holder)
return 1
else
if(show_msg)
to_chat(user, "<font color='red'>Error: You are not an admin.</font>")
return 0
// Basically the above proc but checks at a /client level
/proc/check_rights_client(rights_required, show_msg = TRUE, client/C)
if(C)
if(rights_required)
if(C.holder)
if(rights_required & C.holder.rights)
return TRUE
else
if(show_msg)
to_chat(C, "<font color='red'>Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")].</font>")
else
if(C.holder)
return TRUE
else
if(show_msg)
to_chat(C, "<font color='red'>Error: You are not an admin.</font>")
return check_rights_ckey(rights_required, show_msg, user.ckey, all)
return FALSE
//probably a bit iffy - will hopefully figure out a better solution
/proc/check_if_greater_rights_than(client/other)
if(usr && usr.client)
if(usr.client.holder)
if(!other || !other.holder)
return 1
if(usr.client.holder.rights != other.holder.rights)
if((usr.client.holder.rights & other.holder.rights) == other.holder.rights)
return 1 //we have all the rights they have and more
to_chat(usr, "<font color='red'>Error: Cannot proceed. They have more or equal rights to us.</font>")
return 0
// As above, for a /client
/proc/check_rights_client(rights_required, show_msg, client/C, all = FALSE)
if(C)
return check_rights_ckey(rights_required, show_msg, C.ckey, all)
return FALSE
// As above, for a ckey
/proc/check_rights_ckey(rights_required, show_msg, ckey, all)
var/datum/admins/holder = GLOB.admin_datums[ckey]
if(!holder)
if(show_msg)
to_chat(GLOB.directory[ckey], "<font color='red'>Error: You are not an admin.</font>")
return FALSE
if(!rights_required)
return TRUE
if((rights_required & holder.rights) == rights_required)
return TRUE
else if(!all && (rights_required & holder.rights))
return TRUE
if(show_msg)
if(all)
to_chat(GLOB.directory[ckey], "<font color='red'>Error: You do not have sufficient rights to do that. You require all of the following flags:[rights2text(rights_required," ")].</font>")
else
to_chat(GLOB.directory[ckey], "<font color='red'>Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")].</font>")
return FALSE
/client/proc/deadmin()
if(IsAdminAdvancedProcCall())
@@ -144,45 +200,39 @@ you will have to do something like if(client.holder.rights & R_ADMIN) yourself.
message_admins("[key_name(usr)] attempted to de-admin a client via advanced proc-call")
log_admin("[key_name(usr)] attempted to de-admin a client via advanced proc-call")
return
GLOB.admin_datums -= ckey
if(holder)
holder.disassociate()
if(check_rights(R_ADMIN, show_msg = FALSE))
GLOB.de_admins += ckey
else
GLOB.de_mentors += ckey
qdel(holder)
if(istype(mob, /mob/dead/observer))
var/mob/dead/observer/O = mob
O.update_admin_actions()
return TRUE
add_verb(src, /client/proc/readmin)
else
to_chat(src, "<font color='red'>Error: You were already not an admin.</font>")
//This proc checks whether subject has at least ONE of the rights specified in rights_required.
/proc/check_rights_for(client/subject, rights_required)
if(subject && subject.holder)
if(rights_required && !(rights_required & subject.holder.rights))
return 0
return 1
return 0
/client/proc/readmin()
set name = "Re-admin self"
set category = "Admin"
set desc = "Regain your admin powers."
/**
* Requires the holder to have all the rights specified
*
* rights_required = R_ADMIN|R_EVENT means they must have both flags, or it will return false
*/
/proc/check_rights_all(rights_required, show_msg = TRUE, mob/user = usr)
if(!user?.client)
return FALSE
if(!rights_required)
if(user.client.holder)
return TRUE
if(show_msg)
to_chat(user, "<font color='red'>Error: You are not an admin.</font>")
return FALSE
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='boldannounceooc'>Readmin blocked: Advanced ProcCall detected.</span>")
message_admins("[key_name(usr)] attempted to re-admin a client via advanced proc-call")
log_admin("[key_name(usr)] attempted to re-admin a client via advanced proc-call")
if(!user.client.holder)
return FALSE
if((user.client.holder.rights & rights_required) == rights_required)
return TRUE
if(show_msg)
to_chat(user, "<font color='red'>Error: You do not have sufficient rights to do that. You require all of the following flags:[rights2text(rights_required, " ")].</font>")
return FALSE
if(holder)
to_chat(usr, "<span class='notice'>You are already an admin.</span>")
else if(ckey in (GLOB.de_admins + GLOB.de_mentors))
GLOB.de_admins -= ckey
GLOB.de_mentors -= ckey
reload_one_admin(ckey, silent = TRUE)
log_admin("[key_name(usr)] re-adminned themselves.")
message_admins("[key_name_admin(usr)] re-adminned themselves.")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Re-admin") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
else
to_chat(usr, "<span class='boldannounceooc'>Readmin blocked: You were not an admin or mentor.</span>")
message_admins("[key_name(usr)] attempted to re-admin without being an admin or mentor")
log_admin("[key_name(usr)] attempted to re-admin without being an admin or mentor")
/datum/admins/vv_edit_var(var_name, var_value)
return FALSE // no admin abuse
+1 -1
View File
@@ -1004,7 +1004,7 @@ GLOBAL_VAR_INIT(gamma_ship_location, 1) // 0 = station , 1 = space
/proc/staff_countup(rank_mask = R_BAN)
var/list/result = list(0, 0, 0)
for(var/client/X in GLOB.admins)
if(rank_mask && !check_rights_for(X, rank_mask))
if(rank_mask && !check_rights_client(rank_mask, FALSE, X))
result[2]++
continue
if(X.holder.fakekey)
@@ -13,6 +13,13 @@
var/datum/asset/permissions_asset = get_asset_datum(/datum/asset/simple/permissions)
permissions_asset.send(usr)
var/db_section = ""
if(db_available())
db_section = {"
<div id='db_notice'><b>Admin database active.<br>All data is pulled from the database, and may require an admin reload to match actual permissions.<br>Automatically-granted localhost admin permissions are not listed.</b><br><br></div>
<div id='rank_edit'><b><a href='byond://?src=[UID()];editrank=1'>Edit admin ranks</a></b></div>
"}
var/output = {"<!DOCTYPE html>
<html><meta charset='utf-8'>
<head>
@@ -21,6 +28,8 @@
<link rel='stylesheet' type='text/css' href='[SSassets.transport.get_asset_url("panels.css")]'>
</head>
<body onload='selectTextField();updateSearch();'>
<div id='spacer'></div>
[db_section]
<div id='main'><table id='searchable' cellspacing='0'>
<tr class='title'>
<th style='width:20%;text-align:right;'>CKEY <a class='small' href='byond://?src=[UID()];editrights=add'>\[+\]</a></th>
@@ -28,23 +37,75 @@
</tr>
"}
for(var/adm_ckey in GLOB.admin_datums)
var/datum/admins/D = GLOB.admin_datums[adm_ckey]
if(!D) continue
var/rank = D.rank ? D.rank : "*none*"
var/rights = rights2text(D.rights," ")
if(!rights) rights = "*none*"
output += {"<tr>
<td style='text-align:right;'>[adm_ckey] <a class='small' href='byond://?src=[UID()];editrights=remove;ckey=[adm_ckey]'>\[-\]</a></td>
<td><a href='byond://?src=[UID()];editrights=rank;ckey=[adm_ckey]'>[rank]</a></td>
<td><a class='small' href='byond://?src=[UID()];editrights=permissions;ckey=[adm_ckey]'>[rights]</a></font></td>
</tr>"}
var/list/admin_details = list()
if(GLOB.configuration.admin.use_database_admins)
var/datum/db_query/get_admins = SSdbcore.NewQuery({"
SELECT
admin.ckey,
admin_ranks.name,
admin.display_rank,
IFNULL(admin_ranks.default_permissions, 0),
admin.extra_permissions,
admin.removed_permissions
FROM admin
-- We want all admins, and admin_ranks where available.
LEFT OUTER JOIN admin_ranks
ON admin.permissions_rank = admin_ranks.id
ORDER BY admin_ranks.name ASC, admin.ckey ASC"})
if(!get_admins.warn_execute())
qdel(get_admins)
return
/*output += "<tr>"
output += "<td style='text-align:right;'>[adm_ckey] <a class='small' href='byond://?src=[UID()];editrights=remove;ckey=[adm_ckey]'>\[-\]</a></td>"
output += "<td><a href='byond://?src=[UID()];editrights=rank;ckey=[adm_ckey]'>[rank]</a></td>"
output += "<td><a class='small' href='byond://?src=[UID()];editrights=permissions;ckey=[adm_ckey]'>[rights]</a></font></td>"
output += "</tr>"*/
while(get_admins.NextRow())
var/ckey = get_admins.item[1]
var/rank = get_admins.item[2]
var/display_rank = get_admins.item[3]
var/rank_permissions = get_admins.item[4]
var/extra_permissions = get_admins.item[5]
var/removed_permissions = get_admins.item[6]
if(!rank && !display_rank && !extra_permissions && !removed_permissions)
// Former admin, no current rank or permissions. Don't bother showing them.
continue
var/listed_rank
if(rank)
listed_rank = rank
else
listed_rank = "*none*"
if(display_rank)
listed_rank = "[display_rank] ([rank])"
var/display_rights = ranked_rights2text(rank_permissions, extra_permissions, removed_permissions," ")
if(display_rights == "")
display_rights = "*none*"
admin_details += list(list(
ckey,
listed_rank,
display_rights
))
qdel(get_admins)
else
var/list/sorted_ckeys = list()
for(var/adm_ckey in GLOB.admin_datums)
sorted_ckeys += adm_ckey
sortTim(sorted_ckeys, cmp = GLOBAL_PROC_REF(cmp_text_asc))
for(var/adm_ckey in sorted_ckeys)
var/datum/admins/D = GLOB.admin_datums[adm_ckey]
if(!D)
continue
var/rank = D.rank ? D.rank : "*none*"
var/rights = rights2text(D.rights," ")
if(!rights)
rights = "*none*"
admin_details += list(list(adm_ckey, rank, rights))
for(var/list/details in admin_details)
output += {"<tr>
<td style='text-align:right;'>[details[1]] <a class='small' href='byond://?src=[UID()];editrights=remove;ckey=[details[1]]'>\[-\]</a></td>
<td><a href='byond://?src=[UID()];editrights=rank;ckey=[details[1]]'>[details[2]]</a></td>
<td><a class='small' href='byond://?src=[UID()];editrights=permissions;ckey=[details[1]]'>[details[3]]</a></font></td>
</tr>"}
output += {"
</table></div>
@@ -54,202 +115,525 @@
usr << browse(output,"window=editrights;size=600x500")
/datum/admins/proc/log_admin_rank_modification(adm_ckey, new_rank)
if(!GLOB.configuration.admin.use_database_admins)
return
if(!usr.client)
return
if(!check_rights(R_PERMISSIONS))
return
/datum/admins/proc/db_available()
if(!SSdbcore.IsConnected())
to_chat(usr, "<span class='warning'>Failed to establish database connection</span>")
return FALSE
if(!GLOB.configuration.admin.use_database_admins)
return FALSE
return TRUE
/datum/admins/proc/can_edit_db()
if(!check_rights(R_PERMISSIONS))
return FALSE
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='boldannounceooc'>Admin edit blocked: Advanced ProcCall detected.</span>")
message_admins("[key_name(usr)] attempted to edit admin DB via advanced proc-call")
log_admin("[key_name(usr)] attempted to edit admin DB via advanced proc-call")
return FALSE
if(!usr.client)
return FALSE
if(!db_available())
to_chat(usr, "<span class='warning'>Admin database unavailable</span>")
return FALSE
return TRUE
/datum/admins/proc/set_db_rank(target_key, new_rank, display_rank = null, clear_custom_permissions = FALSE)
if(!can_edit_db())
return
if(!adm_ckey || !new_rank)
if(!istext(target_key))
to_chat(usr, "<span class='warning'>Provided key '[target_key]' is not text!</span>")
return
adm_ckey = ckey(adm_ckey)
if(!adm_ckey)
target_key = ckey(target_key)
if(target_key == "")
to_chat(usr, "<span class='warning'>Provided key was blank after converting to ckey!</span>")
return
if(!istext(adm_ckey) || !istext(new_rank))
if(!isnum(new_rank))
to_chat(usr, "<span class='warning'>Provided rank ID '[new_rank]' is not a number!</span>")
return
var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT id FROM admin WHERE ckey=:adm_ckey", list(
"adm_ckey" = adm_ckey
var/datum/db_query/get_rank_name = SSdbcore.NewQuery("SELECT name FROM admin_ranks WHERE id = :new_rank", list(
"new_rank" = new_rank
))
if(!select_query.warn_execute())
qdel(select_query)
if(!get_rank_name.warn_execute())
qdel(get_rank_name)
return
var/rank_name
if(get_rank_name.NextRow())
rank_name = get_rank_name.item[1]
qdel(get_rank_name)
if(!rank_name)
to_chat(usr, "<span class='warning'>Rank with ID [new_rank] not found in database!</span>")
return
var/datum/db_query/get_admin_id = SSdbcore.NewQuery("SELECT id FROM admin WHERE ckey = :target_key", list(
"target_key" = target_key
))
if(!get_admin_id.warn_execute())
qdel(get_admin_id)
return
var/display_note = ""
if(display_rank)
display_note = " with the custom rank title [display_rank]"
var/new_admin = TRUE
var/admin_id
while(select_query.NextRow())
if(get_admin_id.NextRow())
new_admin = FALSE
admin_id = text2num(select_query.item[1])
qdel(select_query)
flag_account_for_forum_sync(adm_ckey)
admin_id = text2num(get_admin_id.item[1])
qdel(get_admin_id)
if(new_admin)
var/datum/db_query/insert_query = SSdbcore.NewQuery("INSERT INTO admin (`id`, `ckey`, `admin_rank`, `level`, `flags`) VALUES (null, :adm_ckey, :new_rank, -1, 0)", list(
"adm_ckey" = adm_ckey,
"new_rank" = new_rank
var/datum/db_query/insert_new_admin = SSdbcore.NewQuery("INSERT INTO admin (`ckey`, `permissions_rank`, `display_rank`) VALUES (:target_key, :new_rank, :display_rank)", list(
"target_key" = target_key,
"new_rank" = new_rank,
"display_rank" = display_rank
))
if(!insert_query.warn_execute())
qdel(insert_query)
if(!insert_new_admin.warn_execute())
qdel(insert_new_admin)
return
qdel(insert_query)
qdel(insert_new_admin)
message_admins("<span class='notice'>Admin ranks updated by [usr.ckey]: [target_key] (NEW ADMIN) is now a [rank_name][display_note].</span>")
var/logtxt = "Added new admin [adm_ckey] to rank [new_rank]"
var/datum/db_query/log_query = SSdbcore.NewQuery("INSERT INTO admin_log (`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (Now() , :uckey, :uip, :logtxt)", list(
var/logtxt = "Added new admin [target_key] to rank [rank_name][display_note]"
var/datum/db_query/add_log = SSdbcore.NewQuery("INSERT INTO admin_log (`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (Now() , :uckey, :uip, :logtxt)", list(
"uckey" = usr.ckey,
"uip" = usr.client.address,
"logtxt" = logtxt
))
if(!log_query.warn_execute())
qdel(log_query)
if(!add_log.warn_execute())
qdel(add_log)
return
qdel(log_query)
to_chat(usr, "<span class='notice'>New admin added.</span>")
qdel(add_log)
else
if(!isnull(admin_id) && isnum(admin_id))
var/datum/db_query/insert_query = SSdbcore.NewQuery("UPDATE admin SET admin_rank=:new_rank WHERE id=:admin_id", list(
var/datum/db_query/update_admin_rank = SSdbcore.NewQuery("UPDATE admin SET permissions_rank = :new_rank, display_rank = :display_rank WHERE id = :admin_id", list(
"new_rank" = new_rank,
"display_rank" = display_rank,
"admin_id" = admin_id,
))
if(!insert_query.warn_execute())
qdel(insert_query)
if(!update_admin_rank.warn_execute())
qdel(update_admin_rank)
return
qdel(insert_query)
qdel(update_admin_rank)
message_admins("<span class='notice'>Admin ranks updated by [usr.ckey]: [target_key] is now a [rank_name][display_note].</span>")
var/logtxt = "Edited the rank of [target_key] to [rank_name][display_note]"
var/logtxt = "Edited the rank of [adm_ckey] to [new_rank]"
var/datum/db_query/log_query = SSdbcore.NewQuery("INSERT INTO admin_log (`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (Now() , :uckey, :uip, :logtxt)", list(
if(clear_custom_permissions)
var/datum/db_query/clear_permissions = SSdbcore.NewQuery("UPDATE admin SET extra_permissions = 0, removed_permissions = 0 WHERE id = :admin_id", list(
"admin_id" = admin_id,
))
if(!clear_permissions.warn_execute())
qdel(clear_permissions)
return
qdel(clear_permissions)
logtxt += " and cleared their custom permissions"
message_admins("<span class='notice'>Admin permissions updated by [usr.ckey]: [target_key] no longer has any custom permissions.</span>")
var/datum/db_query/add_log = SSdbcore.NewQuery("INSERT INTO admin_log (`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (Now() , :uckey, :uip, :logtxt)", list(
"uckey" = usr.ckey,
"uip" = usr.client.address,
"logtxt" = logtxt,
))
if(!log_query.warn_execute())
qdel(log_query)
if(!add_log.warn_execute())
qdel(add_log)
return
qdel(log_query)
to_chat(usr, "<span class='notice'>Admin rank changed.</span>")
qdel(add_log)
/datum/admins/proc/log_admin_permission_modification(adm_ckey, new_permission)
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='boldannounceooc'>Admin edit blocked: Advanced ProcCall detected.</span>")
message_admins("[key_name(usr)] attempted to edit admin ranks via advanced proc-call")
log_admin("[key_name(usr)] attempted to edit admin ranks via advanced proc-call")
return
if(!GLOB.configuration.admin.use_database_admins)
/datum/admins/proc/toggle_db_permission(target_key, permission_bit)
if(!can_edit_db())
return
if(!usr.client)
if(!istext(target_key))
to_chat(usr, "<span class='warning'>Provided key '[target_key]' is not text!</span>")
return
if(!check_rights(R_PERMISSIONS))
target_key = ckey(target_key)
if(target_key == "")
to_chat(usr, "<span class='warning'>Provided key was blank after converting to ckey!</span>")
return
if(!SSdbcore.IsConnected())
to_chat(usr, "<span class='warning'>Failed to establish database connection</span>")
if(!isnum(permission_bit) || permission_bit < 1 || floor(permission_bit) != permission_bit)
to_chat(usr, "<span class='warning'>Provided permission '[permission_bit]' is not positive whole number!</span>")
return
if(!adm_ckey || !new_permission)
var/pow2 = round(log(2, permission_bit), 1)
if((2 ** pow2) != permission_bit)
to_chat(usr, "<span class='warning'>Provided permission '[permission_bit]' is not a power of two, and would affect multiple permission bits!</span>")
return
adm_ckey = ckey(adm_ckey)
if(!adm_ckey)
return
if(istext(new_permission))
new_permission = text2num(new_permission)
if(!istext(adm_ckey) || !isnum(new_permission))
return
var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT id, flags FROM admin WHERE ckey=:adm_ckey", list(
"adm_ckey" = adm_ckey
var/datum/db_query/get_admin_permissions = SSdbcore.NewQuery("SELECT admin.id, admin_ranks.default_permissions, admin.extra_permissions, admin.removed_permissions FROM admin LEFT OUTER JOIN admin_ranks ON admin.permissions_rank = admin_ranks.id WHERE ckey = :target_key", list(
"target_key" = target_key
))
if(!select_query.warn_execute())
qdel(select_query)
if(!get_admin_permissions.warn_execute())
qdel(get_admin_permissions)
return
var/admin_id
var/admin_rights
while(select_query.NextRow())
admin_id = text2num(select_query.item[1])
admin_rights = text2num(select_query.item[2])
var/rank_permissions = 0
var/extra_permissions = 0
var/removed_permissions = 0
if(get_admin_permissions.NextRow())
admin_id = get_admin_permissions.item[1]
// || 0 forces this to be numeric if they have no rank in the database.
rank_permissions = get_admin_permissions.item[2] || 0
extra_permissions = get_admin_permissions.item[3]
removed_permissions = get_admin_permissions.item[4]
qdel(select_query)
qdel(get_admin_permissions)
if(!admin_id)
to_chat(usr, "<span class='warning'>No admin found with ckey [target_key]!</span>")
return
flag_account_for_forum_sync(adm_ckey)
if(admin_rights & new_permission) //This admin already has this permission, so we are removing it.
var/datum/db_query/insert_query = SSdbcore.NewQuery("UPDATE admin SET flags=:newflags WHERE id=:admin_id", list(
"newflags" = (admin_rights & ~new_permission),
flag_account_for_forum_sync(target_key)
if(removed_permissions & permission_bit)
// They had an exception removing this permission bit, let them have it.
var/datum/db_query/remove_removal = SSdbcore.NewQuery("UPDATE admin SET removed_permissions = removed_permissions & ~:permission_bit WHERE id = :admin_id", list(
"permission_bit" = permission_bit,
"admin_id" = admin_id
))
if(!insert_query.warn_execute())
qdel(insert_query)
if(!remove_removal.warn_execute())
qdel(remove_removal)
return
qdel(insert_query)
qdel(remove_removal)
message_admins("<span class='notice'>Admin permissions updated by [usr.ckey]: [target_key] is no longer excluded from having [rights2text(permission_bit)].</span>")
var/logtxt = "Removed permission [rights2text(new_permission)] (flag = [new_permission]) to admin [adm_ckey]"
var/datum/db_query/log_query = SSdbcore.NewQuery({"
var/logtxt = "Un-excluded permission [rights2text(permission_bit)] (flag = [permission_bit]) from admin [target_key]"
var/datum/db_query/create_log = SSdbcore.NewQuery({"
INSERT INTO admin_log (`datetime` ,`adminckey` ,`adminip` ,`log`)
VALUES (Now() , :uckey, :uip, :logtxt)"}, list(
"uckey" = usr.ckey,
"uip" = usr.client.address,
"logtxt" = logtxt
))
if(!log_query.warn_execute())
qdel(log_query)
return
qdel(log_query)
to_chat(usr, "<span class='notice'>Permission removed.</span>")
else //This admin doesn't have this permission, so we are adding it.
var/datum/db_query/insert_query = SSdbcore.NewQuery("UPDATE admin SET flags=:newflags WHERE id=:admin_id", list(
"newflags" = (admin_rights | new_permission),
create_log.warn_execute()
qdel(create_log)
return
if(extra_permissions & permission_bit)
// They had an exception adding this permission bit, remove it.
var/datum/db_query/remove_extra = SSdbcore.NewQuery("UPDATE admin SET extra_permissions = extra_permissions & ~:permission_bit WHERE id = :admin_id", list(
"permission_bit" = permission_bit,
"admin_id" = admin_id
))
if(!insert_query.warn_execute())
qdel(insert_query)
if(!remove_extra.warn_execute())
qdel(remove_extra)
return
qdel(insert_query)
qdel(remove_extra)
message_admins("<span class='notice'>Admin permissions updated by [usr.ckey]: [target_key] no longer has the extra permission [rights2text(permission_bit)].</span>")
var/logtxt = "Added permission [rights2text(new_permission)] (flag = [new_permission]) to admin [adm_ckey]"
var/datum/db_query/log_query = SSdbcore.NewQuery({"
var/logtxt = "Removed extra permission [rights2text(permission_bit)] (flag = [permission_bit]) from admin [target_key]"
var/datum/db_query/create_log = SSdbcore.NewQuery({"
INSERT INTO admin_log (`datetime` ,`adminckey` ,`adminip` ,`log`)
VALUES (Now() , :uckey, :uip, :logtxt)"}, list(
"uckey" = usr.ckey,
"uip" = usr.client.address,
"logtxt" = logtxt
))
create_log.warn_execute()
qdel(create_log)
return
if(!log_query.warn_execute())
qdel(log_query)
if(rank_permissions & permission_bit)
// They have this permission from their rank, and an exception removing it.
var/datum/db_query/create_removal = SSdbcore.NewQuery("UPDATE admin SET removed_permissions = removed_permissions | :permission_bit WHERE id = :admin_id", list(
"permission_bit" = permission_bit,
"admin_id" = admin_id
))
if(!create_removal.warn_execute())
qdel(create_removal)
return
qdel(log_query)
to_chat(usr, "<span class='notice'>Permission added.</span>")
qdel(create_removal)
message_admins("<span class='notice'>Admin permissions updated by [usr.ckey]: [target_key] is now excluded from having [rights2text(permission_bit)].</span>")
/datum/admins/proc/updateranktodb(ckey,newrank)
if(!SSdbcore.IsConnected())
return
if(!check_rights(R_PERMISSIONS))
var/logtxt = "Excluded permission [rights2text(permission_bit)] (flag = [permission_bit]) from admin [target_key]"
var/datum/db_query/create_log = SSdbcore.NewQuery({"
INSERT INTO admin_log (`datetime` ,`adminckey` ,`adminip` ,`log`)
VALUES (Now() , :uckey, :uip, :logtxt)"}, list(
"uckey" = usr.ckey,
"uip" = usr.client.address,
"logtxt" = logtxt
))
create_log.warn_execute()
qdel(create_log)
return
var/datum/db_query/query_update = SSdbcore.NewQuery("UPDATE player SET lastadminrank=:admin_rank WHERE ckey=:ckey", list(
"admin_rank" = newrank,
"ckey" = ckey
// They did not have this permission from their rank, add an exception granting it.
var/datum/db_query/add_extra = SSdbcore.NewQuery("UPDATE admin SET extra_permissions = extra_permissions | :permission_bit WHERE id = :admin_id", list(
"permission_bit" = permission_bit,
"admin_id" = admin_id
))
if(!query_update.warn_execute())
qdel(query_update)
if(!add_extra.warn_execute())
qdel(add_extra)
return
qdel(add_extra)
message_admins("<span class='notice'>Admin permissions updated by [usr.ckey]: [target_key] has been granted the extra permission [rights2text(permission_bit)].</span>")
var/logtxt = "Added extra permission [rights2text(permission_bit)] (flag = [permission_bit]) to admin [target_key]"
var/datum/db_query/create_log = SSdbcore.NewQuery({"
INSERT INTO admin_log (`datetime` ,`adminckey` ,`adminip` ,`log`)
VALUES (Now() , :uckey, :uip, :logtxt)"}, list(
"uckey" = usr.ckey,
"uip" = usr.client.address,
"logtxt" = logtxt
))
create_log.warn_execute()
qdel(create_log)
/datum/admins/proc/remove_db_admin(target_key, clear_custom_permissions = TRUE)
if(!can_edit_db())
return
qdel(query_update)
flag_account_for_forum_sync(ckey)
if(!istext(target_key))
to_chat(usr, "<span class='warning'>Provided key '[target_key]' is not text!</span>")
return
target_key = ckey(target_key)
if(target_key == "")
to_chat(usr, "<span class='warning'>Provided key was blank after converting to ckey!</span>")
return
var/datum/db_query/get_admin_id = SSdbcore.NewQuery("SELECT admin.id FROM admin WHERE ckey = :target_key", list(
"target_key" = target_key
))
if(!get_admin_id.warn_execute())
qdel(get_admin_id)
return
var/admin_id
if(get_admin_id.NextRow())
admin_id = get_admin_id.item[1]
qdel(get_admin_id)
if(!admin_id)
to_chat(usr, "<span class='warning'>No admin found with ckey [target_key]!</span>")
return
flag_account_for_forum_sync(target_key)
var/datum/db_query/remove_admin = SSdbcore.NewQuery("UPDATE admin SET permissions_rank = NULL, display_rank = NULL WHERE id = :admin_id", list(
"admin_id" = admin_id,
))
if(!remove_admin.warn_execute())
qdel(remove_admin)
return
qdel(remove_admin)
message_admins("<span class='notice'>Admin ranks updated by [usr.ckey]: [target_key] no longer has any admin rank.</span>")
var/logtxt = "Removed the admin rank of [target_key]"
if(clear_custom_permissions)
var/datum/db_query/clear_permissions = SSdbcore.NewQuery("UPDATE admin SET extra_permissions = 0, removed_permissions = 0 WHERE id = :admin_id", list(
"admin_id" = admin_id,
))
if(!clear_permissions.warn_execute())
qdel(clear_permissions)
return
qdel(clear_permissions)
message_admins("<span class='notice'>Admin permissions updated by [usr.ckey]: [target_key] no longer has any custom permissions.</span>")
logtxt += " and cleared their custom permissions"
var/datum/db_query/create_log = SSdbcore.NewQuery({"
INSERT INTO admin_log (`datetime` ,`adminckey` ,`adminip` ,`log`)
VALUES (Now() , :uckey, :uip, :logtxt)"}, list(
"uckey" = usr.ckey,
"uip" = usr.client.address,
"logtxt" = logtxt
))
create_log.warn_execute()
qdel(create_log)
/datum/admins/proc/create_db_rank(rank_name)
if(!can_edit_db())
return
if(get_db_rank_id(rank_name))
// We already have it.
return
var/datum/db_query/add_rank = SSdbcore.NewQuery("INSERT INTO admin_ranks SET name = :rank_name", list(
"rank_name" = rank_name
))
add_rank.warn_execute()
qdel(add_rank)
message_admins("<span class='notice'>Admin ranks updated by [usr.ckey]: new rank [rank_name] created.</span>")
var/logtxt = "Created the admin rank [rank_name]"
var/datum/db_query/create_log = SSdbcore.NewQuery({"
INSERT INTO admin_log (`datetime` ,`adminckey` ,`adminip` ,`log`)
VALUES (Now() , :uckey, :uip, :logtxt)"}, list(
"uckey" = usr.ckey,
"uip" = usr.client.address,
"logtxt" = logtxt
))
create_log.warn_execute()
qdel(create_log)
return get_db_rank_id(rank_name)
/datum/admins/proc/delete_db_rank(rank_name)
if(!can_edit_db())
return
var/rank_id = get_db_rank_id(rank_name)
if(!rank_id)
to_chat(usr, "<span class='warning'>No rank named [rank_name] found!</span>")
return
var/datum/db_query/get_admins_with_rank = SSdbcore.NewQuery("SELECT ckey FROM admin WHERE permissions_rank = :rank_id", list(
"rank_id" = rank_id
))
if(!get_admins_with_rank.warn_execute())
qdel(get_admins_with_rank)
return
var/list/admins = list()
while(get_admins_with_rank.NextRow())
admins += get_admins_with_rank.item[1]
qdel(get_admins_with_rank)
if(length(admins) > 0)
to_chat(usr, "<span class='warning'>[rank_name] is still in use, reassign the following admins first: [admins.Join(", ")]</span>")
return
var/datum/db_query/delete_rank = SSdbcore.NewQuery("DELETE FROM admin_ranks WHERE id = :rank_id", list(
"rank_id" = rank_id
))
if(!delete_rank.warn_execute())
qdel(delete_rank)
return
qdel(delete_rank)
message_admins("<span class='notice'>Admin ranks updated by [usr.ckey]: unused rank [rank_name] deleted.</span>")
var/logtxt = "Deleted the unused admin rank [rank_name]"
var/datum/db_query/create_log = SSdbcore.NewQuery({"
INSERT INTO admin_log (`datetime` ,`adminckey` ,`adminip` ,`log`)
VALUES (Now() , :uckey, :uip, :logtxt)"}, list(
"uckey" = usr.ckey,
"uip" = usr.client.address,
"logtxt" = logtxt
))
create_log.warn_execute()
qdel(create_log)
/datum/admins/proc/toggle_db_rank_permission(rank_name, permission_bit)
if(!can_edit_db())
return
var/datum/db_query/get_rank_details = SSdbcore.NewQuery("SELECT id, default_permissions FROM admin_ranks WHERE name = :rank_name", list(
"rank_name" = rank_name
))
if(!get_rank_details.warn_execute())
qdel(get_rank_details)
return
var/rank_id
var/rank_permissions
if(get_rank_details.NextRow())
rank_id = get_rank_details.item[1]
rank_permissions = get_rank_details.item[2]
qdel(get_rank_details)
if(!rank_id)
to_chat(usr, "<span class='warning'>No rank named [rank_name] found!</span>")
return
if(!isnum(permission_bit) || permission_bit < 1 || floor(permission_bit) != permission_bit)
to_chat(usr, "<span class='warning'>Provided permission '[permission_bit]' is not positive whole number!</span>")
return
var/pow2 = round(log(2, permission_bit), 1)
if((2 ** pow2) != permission_bit)
to_chat(usr, "<span class='warning'>Provided permission '[permission_bit]' is not a power of two, and would affect multiple permission bits!</span>")
return
if(rank_permissions & permission_bit)
// Revoke the permission.
var/datum/db_query/create_removal = SSdbcore.NewQuery("UPDATE admin_ranks SET default_permissions = default_permissions & ~:permission_bit WHERE id = :rank_id", list(
"permission_bit" = permission_bit,
"rank_id" = rank_id
))
if(!create_removal.warn_execute())
qdel(create_removal)
return
qdel(create_removal)
message_admins("<span class='notice'>Admin ranks updated by [usr.ckey]: [rank_name] no longer has [rights2text(permission_bit)]. An admin reload is required to apply this change.</span>")
var/logtxt = "Removed permission [rights2text(permission_bit)] (flag = [permission_bit]) from admin rank [rank_name]"
var/datum/db_query/create_log = SSdbcore.NewQuery({"
INSERT INTO admin_log (`datetime` ,`adminckey` ,`adminip` ,`log`)
VALUES (Now() , :uckey, :uip, :logtxt)"}, list(
"uckey" = usr.ckey,
"uip" = usr.client.address,
"logtxt" = logtxt
))
create_log.warn_execute()
qdel(create_log)
return
// Grant the permission
var/datum/db_query/grant_permission = SSdbcore.NewQuery("UPDATE admin_ranks SET default_permissions = default_permissions | :permission_bit WHERE id = :rank_id", list(
"permission_bit" = permission_bit,
"rank_id" = rank_id
))
if(!grant_permission.warn_execute())
qdel(grant_permission)
return
qdel(grant_permission)
message_admins("<span class='notice'>Admin ranks updated by [usr.ckey]: [rank_name] has been given [rights2text(permission_bit)]. An admin reload is required to apply this change.</span>")
var/logtxt = "Added permission [rights2text(permission_bit)] (flag = [permission_bit]) to admin rank [rank_name]"
var/datum/db_query/create_log = SSdbcore.NewQuery({"
INSERT INTO admin_log (`datetime` ,`adminckey` ,`adminip` ,`log`)
VALUES (Now() , :uckey, :uip, :logtxt)"}, list(
"uckey" = usr.ckey,
"uip" = usr.client.address,
"logtxt" = logtxt
))
create_log.warn_execute()
qdel(create_log)
/datum/admins/proc/get_db_ranks()
if(!db_available())
return
var/datum/db_query/get_ranks = SSdbcore.NewQuery("SELECT name FROM admin_ranks")
if(!get_ranks.warn_execute())
qdel(get_ranks)
CRASH("Unable to get admin ranks from database.")
var/list/ranks = list()
while(get_ranks.NextRow())
ranks += get_ranks.item[1]
qdel(get_ranks)
return ranks
/datum/admins/proc/get_db_rank_id(rank_name)
if(!db_available())
return
var/datum/db_query/get_rank_id = SSdbcore.NewQuery("SELECT id FROM admin_ranks WHERE name = :rank_name", list(
"rank_name" = rank_name
))
if(!get_rank_id.warn_execute())
qdel(get_rank_id)
return
var/rank_id
if(get_rank_id.NextRow())
rank_id = get_rank_id.item[1]
qdel(get_rank_id)
return rank_id
/datum/admins/proc/get_db_rank_permissions(rank_name)
if(!db_available())
return
var/datum/db_query/get_rank_permissions = SSdbcore.NewQuery("SELECT default_permissions FROM admin_ranks WHERE name = :rank_name", list(
"rank_name" = rank_name
))
if(!get_rank_permissions.warn_execute())
qdel(get_rank_permissions)
return
var/rank_permissions
if(get_rank_permissions.NextRow())
rank_permissions = get_rank_permissions.item[1]
qdel(get_rank_permissions)
return rank_permissions
+92 -21
View File
@@ -242,6 +242,44 @@
DB_ban_record(bantype, playermob, banduration, banreason, banjob, null, banckey, banip, bancid)
else if(href_list["editrank"])
if(!check_rights(R_PERMISSIONS))
message_admins("[key_name_admin(usr)] attempted to edit the admin permissions without sufficient rights.")
log_admin("[key_name(usr)] attempted to edit the admin permissions without sufficient rights.")
return
var/rank = input("Please select a rank", "Rank to edit", null, null) as null|anything in (get_db_ranks() | "*New Rank*")
if(!rank)
return
if(rank == "*New Rank*")
rank = input("Please input a new rank", "New custom rank", null, null) as null|text
if(!rank)
return
var/rank_id = create_db_rank(rank)
if(!rank_id)
to_chat(usr, "<span class='warning'>A rank named [rank] already esists.</span>")
return
while(TRUE)
var/action = input("What do you want to do to [rank]?", "Editing rank [rank]", null, null) as null|anything in list("List Permissions", "Toggle Permission", "Delete Rank (must be unused)")
if(!action)
return
if(action == "List Permissions")
to_chat(usr, "<span class='notice'>Rank [rank] has the permissions: [rights2text(get_db_rank_permissions(rank), " ")]</span>")
continue
if(action == "Toggle Permission")
var/list/permissionlist = list()
for(var/i=1, i<=R_MAXPERMISSION, i<<=1) //that <<= is shorthand for i = i << 1. Which is a left bitshift
permissionlist[rights2text(i)] = i
var/permission = input("Select a permission to turn on/off", "Editing rank [rank]", null, null) as null|anything in permissionlist
if(!permission)
continue
toggle_db_rank_permission(rank, permissionlist[permission])
edit_admin_permissions()
continue
delete_db_rank(rank)
return
else if(href_list["editrights"])
if(!check_rights(R_PERMISSIONS))
message_admins("[key_name_admin(usr)] attempted to edit the admin permissions without sufficient rights.")
@@ -253,8 +291,10 @@
var/task = href_list["editrights"]
if(task == "add")
var/new_ckey = ckey(clean_input("New admin's ckey","Admin ckey", null))
if(!new_ckey) return
if(new_ckey in GLOB.admin_datums)
if(!new_ckey)
return
var/datum/admins/existing_datum = GLOB.admin_datums[new_ckey]
if(existing_datum && existing_datum.rank != "!LOCALHOST!")
to_chat(usr, "<font color='red'>Error: Topic 'editrights': [new_ckey] is already an admin</font>")
return
adm_ckey = new_ckey
@@ -268,63 +308,90 @@
var/datum/admins/D = GLOB.admin_datums[adm_ckey]
if(task == "remove")
if(alert("Are you sure you want to remove [adm_ckey]?","Message","Yes","Cancel") == "Yes")
if(!D) return
if(alert("Are you sure you want to remove [adm_ckey]'s admin rank?","Message","Yes","Cancel") == "Yes")
if(!GLOB.configuration.admin.use_database_admins && !D)
return
GLOB.admin_datums -= adm_ckey
D.disassociate()
if(D)
qdel(D)
updateranktodb(adm_ckey, "player")
if(GLOB.configuration.admin.use_database_admins)
var/clear_custom_permissions = alert("Do you also want to clear any custom permissions [adm_ckey] may have?","Message","Yes","No") == "Yes"
remove_db_admin(adm_ckey, clear_custom_permissions)
reload_one_admin(adm_ckey)
edit_admin_permissions()
return
message_admins("[key_name_admin(usr)] removed [adm_ckey] from the admins list")
log_admin("[key_name(usr)] removed [adm_ckey] from the admins list")
log_admin_rank_modification(adm_ckey, "Removed")
else if(task == "rank")
var/new_rank
if(length(GLOB.admin_ranks))
new_rank = input("Please select a rank", "New rank", null, null) as null|anything in (GLOB.admin_ranks|"*New Rank*")
var/display_rank
if(GLOB.configuration.admin.use_database_admins)
new_rank = input("Please select a rank", "New rank", null, null) as null|anything in (get_db_ranks() | "*New Rank*")
else if(length(GLOB.admin_ranks))
new_rank = input("Please select a rank", "New rank", null, null) as null|anything in (GLOB.admin_ranks | "*New Rank*")
else
new_rank = input("Please select a rank", "New rank", null, null) as null|anything in list("Mentor", "Trial Admin", "Game Admin", "Developer", "*New Rank*")
var/rights = 0
if(D)
rights = D.rights
var/rank_id
switch(new_rank)
if(null,"") return
if("*New Rank*")
new_rank = input("Please input a new rank", "New custom rank", null, null) as null|text
new_rank = input("Please input a new rank", "Admin rank for [adm_ckey]", null, null) as null|text
if(!GLOB.configuration.admin.use_database_admins)
new_rank = ckeyEx(new_rank)
if(!new_rank)
to_chat(usr, "<font color='red'>Error: Topic 'editrights': Invalid rank</font>")
return
if(!GLOB.configuration.admin.use_database_admins)
if(GLOB.configuration.admin.use_database_admins)
rank_id = create_db_rank(new_rank)
if(!rank_id)
to_chat(usr, "<span class='warning'>A rank named [new_rank] already esists.</span>")
return
display_rank = input("Give them a custom title?", "Admin rank for [adm_ckey]", null, null) as null|text
else
if(length(GLOB.admin_ranks))
if(new_rank in GLOB.admin_ranks)
rights = GLOB.admin_ranks[new_rank] //we typed a rank which already exists, use its rights
else
GLOB.admin_ranks[new_rank] = 0 //add the new rank to admin_ranks
else
if(!GLOB.configuration.admin.use_database_admins)
if(GLOB.configuration.admin.use_database_admins)
rank_id = get_db_rank_id(new_rank)
if(!rank_id)
to_chat(usr, "<span class='warning'>Rank '[new_rank] not found.</span>")
return
display_rank = input("Give them a custom title?", "Admin rank for [adm_ckey]", null, null) as null|text
else
new_rank = ckeyEx(new_rank)
rights = GLOB.admin_ranks[new_rank] //we input an existing rank, use its rights
if(GLOB.configuration.admin.use_database_admins)
var/clear_custom_permissions = alert("Do you also want to remove any custom permissions [adm_ckey] may have?","Message","Yes","No") == "Yes"
if(display_rank == "")
display_rank = null
set_db_rank(adm_ckey, rank_id, display_rank, clear_custom_permissions)
reload_one_admin(adm_ckey)
edit_admin_permissions()
return
if(D)
D.disassociate() //remove adminverbs and unlink from client
D.rank = new_rank //update the rank
D.rights = rights //update the rights based on admin_ranks (default: 0)
else
D = new /datum/admins(new_rank, rights, adm_ckey)
qdel(D)
D = new /datum/admins(new_rank, rights, adm_ckey)
var/client/C = GLOB.directory[adm_ckey] //find the client with the specified ckey (if they are logged in)
D.associate(C) //link up with the client and add verbs
updateranktodb(adm_ckey, new_rank)
message_admins("[key_name_admin(usr)] edited the admin rank of [adm_ckey] to [new_rank]")
log_admin("[key_name(usr)] edited the admin rank of [adm_ckey] to [new_rank]")
log_admin_rank_modification(adm_ckey, new_rank)
else if(task == "permissions")
if(!D) return
if(!D && !GLOB.configuration.admin.use_database_admins)
return
while(TRUE)
var/list/permissionlist = list()
for(var/i=1, i<=R_MAXPERMISSION, i<<=1) //that <<= is shorthand for i = i << 1. Which is a left bitshift
@@ -332,6 +399,11 @@
var/new_permission = input("Select a permission to turn on/off", adm_ckey + "'s Permissions", null, null) as null|anything in permissionlist
if(!new_permission)
return
if(GLOB.configuration.admin.use_database_admins)
toggle_db_permission(adm_ckey, permissionlist[new_permission])
reload_one_admin(adm_ckey)
edit_admin_permissions()
continue
var/oldrights = D.rights
var/toggleresult = "ON"
D.rights ^= permissionlist[new_permission]
@@ -340,7 +412,6 @@
message_admins("[key_name_admin(usr)] toggled the [new_permission] permission of [adm_ckey] to [toggleresult]")
log_admin("[key_name(usr)] toggled the [new_permission] permission of [adm_ckey] to [toggleresult]")
log_admin_permission_modification(adm_ckey, permissionlist[new_permission])
edit_admin_permissions()