evennia/src/commands/default/admin.py
Griatch 08b3de9e5e OBS: You'll need to resync/rebuild your database!
- This implements an updated, clearer and more robust access system. The policy is now to lock that which is not explicitly left open.
- Permission strings -> Lock strings. Separating permissions and locks makes more sense security-wise
- No more permissiongroup table; permissions instead use a simple tuple PERMISSIONS_HIERARCHY to define an access hierarchy
- Cleaner lock-definition syntax, all based on function calls.
- New objects/players/channels get a default security policy during creation (set through typeclass)

As part of rebuilding and testing the new lock/permission system I got into testing and debugging several other systems, fixing some
outstanding issues:
- @reload now fully updates the database asynchronously. No need to reboot server when changing cmdsets
- Dozens of new test suites added for about 30 commands so far
- Help for channels made more clever and informative.
2011-03-15 16:08:32 +00:00

478 lines
16 KiB
Python

"""
Admin commands
"""
from django.conf import settings
from django.contrib.auth.models import User
from src.players.models import PlayerDB
from src.server.sessionhandler import SESSIONS
from src.utils import utils
from src.commands.default.muxcommand import MuxCommand
class CmdBoot(MuxCommand):
"""
@boot
Usage
@boot[/switches] <player obj> [: reason]
Switches:
quiet - Silently boot without informing player
port - boot by port number instead of name or dbref
Boot a player object from the server. If a reason is
supplied it will be echoed to the user unless /quiet is set.
"""
key = "@boot"
locks = "cmd:perm(boot) or perm(Wizard)"
help_category = "Admin"
def func(self):
"Implementing the function"
caller = self.caller
args = self.args
if not args:
caller.msg("Usage: @boot[/switches] <player> [:reason]")
return
if ':' in args:
args, reason = [a.strip() for a in args.split(':', 1)]
boot_list = []
reason = ""
if 'port' in self.switches:
# Boot a particular port.
sessions = SESSIONS.get_session_list(True)
for sess in sessions:
# Find the session with the matching port number.
if sess.getClientAddress()[1] == int(args):
boot_list.append(sess)
break
else:
# Boot by player object
pobj = caller.search("*%s" % args, global_search=True)
if not pobj:
return
if pobj.character.has_player:
if not pobj.access(caller, 'boot'):
string = "You don't have the permission to boot %s."
pobj.msg(string)
return
# we have a bootable object with a connected user
matches = SESSIONS.sessions_from_player(pobj)
for match in matches:
boot_list.append(match)
else:
caller.msg("That object has no connected player.")
return
if not boot_list:
caller.msg("No matches found.")
return
# Carry out the booting of the sessions in the boot list.
feedback = None
if not 'quiet' in self.switches:
feedback = "You have been disconnected by %s.\n" % caller.name
if reason:
feedback += "\nReason given: %s" % reason
for session in boot_list:
name = session.name
session.msg(feedback)
session.disconnect()
caller.msg("You booted %s." % name)
class CmdDelPlayer(MuxCommand):
"""
delplayer - delete player from server
Usage:
@delplayer[/switch] <name> [: reason]
Switch:
delobj - also delete the player's currently
assigned in-game object.
Completely deletes a user from the server database,
making their nick and e-mail again available.
"""
key = "@delplayer"
locks = "cmd:perm(delplayer) or perm(Immortals)"
help_category = "Admin"
def func(self):
"Implements the command."
caller = self.caller
args = self.args
if not args:
caller.msg("Usage: @delplayer[/delobj] <player/user name or #id> [: reason]")
return
reason = ""
if ':' in args:
args, reason = [arg.strip() for arg in args.split(':', 1)]
# We use player_search since we want to be sure to find also players
# that lack characters.
players = caller.search("*%s" % args)
if not players:
try:
players = PlayerDB.objects.filter(id=args)
except ValueError:
pass
if not players:
# try to find a user instead of a Player
try:
user = User.objects.get(id=args)
except Exception:
try:
user = User.objects.get(username__iexact=args)
except Exception:
string = "No Player nor User found matching '%s'." % args
caller.msg(string)
return
try:
player = user.get_profile()
except Exception:
player = None
if player and not player.access(caller, 'delete'):
string = "You don't have the permissions to delete this player."
caller.msg(string)
return
string = ""
name = user.username
user.delete()
if player:
name = player.name
player.delete()
string = "Player %s was deleted." % name
else:
string += "The User %s was deleted. It had no Player associated with it." % name
caller.msg(string)
return
elif utils.is_iter(players):
string = "There were multiple matches:"
for player in players:
string += "\n %s %s" % (player.id, player.key)
return
else:
# one single match
player = players
user = player.user
character = player.character
if not player.access(caller, 'delete'):
string = "You don't have the permissions to delete that player."
caller.msg(string)
return
uname = user.username
# boot the player then delete
if character and character.has_player:
caller.msg("Booting and informing player ...")
string = "\nYour account '%s' is being *permanently* deleted.\n" % uname
if reason:
string += " Reason given:\n '%s'" % reason
character.msg(string)
caller.execute_cmd("@boot %s" % uname)
player.delete()
user.delete()
caller.msg("Player %s was successfully deleted." % uname)
class CmdEmit(MuxCommand):
"""
@emit
Usage:
@emit[/switches] [<obj>, <obj>, ... =] <message>
@remit [<obj>, <obj>, ... =] <message>
@pemit [<obj>, <obj>, ... =] <message>
Switches:
room : limit emits to rooms only (default)
players : limit emits to players only
contents : send to the contents of matched objects too
Emits a message to the selected objects or to
your immediate surroundings. If the object is a room,
send to its contents. @remit and @pemit are just
limited forms of @emit, for sending to rooms and
to players respectively.
"""
key = "@emit"
aliases = ["@pemit", "@remit"]
locks = "cmd:perm(emit) or perm(Builders)"
help_category = "Admin"
def func(self):
"Implement the command"
caller = self.caller
args = self.args
if not args:
string = "Usage: "
string += "\n@emit[/switches] [<obj>, <obj>, ... =] <message>"
string += "\n@remit [<obj>, <obj>, ... =] <message>"
string += "\n@pemit [<obj>, <obj>, ... =] <message>"
caller.msg(string)
return
rooms_only = 'rooms' in self.switches
players_only = 'players' in self.switches
send_to_contents = 'contents' in self.switches
# we check which command was used to force the switches
if self.cmdstring == '@remit':
rooms_only = True
elif self.cmdstring == '@pemit':
players_only = True
if not self.rhs:
message = self.args
objnames = [caller.location.key]
else:
message = self.rhs
objnames = self.lhslist
# send to all objects
for objname in objnames:
obj = caller.search(objname, global_search=True)
if not obj:
return
if rooms_only and not obj.location == None:
caller.msg("%s is not a room. Ignored." % objname)
continue
if players_only and not obj.has_player:
caller.msg("%s has no active player. Ignored." % objname)
continue
if obj.access(caller, 'tell'):
obj.msg(message)
if send_to_contents:
for content in obj.contents:
content.msg(message)
caller.msg("Emitted to %s and its contents." % objname)
else:
caller.msg("Emitted to %s." % objname)
else:
caller.msg("You are not allowed to send to %s." % objname)
class CmdNewPassword(MuxCommand):
"""
@setpassword
Usage:
@userpassword <user obj> = <new password>
Set a player's password.
"""
key = "@userpassword"
locks = "cmd:perm(newpassword) or perm(Wizards)"
help_category = "Admin"
def func(self):
"Implement the function."
caller = self.caller
if not self.rhs:
caller.msg("Usage: @userpassword <user obj> = <new password>")
return
# the player search also matches 'me' etc.
player = caller.search("*%s" % self.lhs, global_search=True)
if not player:
return
player.user.set_password(self.rhs)
player.user.save()
caller.msg("%s - new password set to '%s'." % (player.name, self.rhs))
if player.character != caller:
player.msg("%s has changed your password to '%s'." % (caller.name, self.rhs))
class CmdPerm(MuxCommand):
"""
@perm - set permissions
Usage:
@perm[/switch] [<object>] = [<permission>]
@perm[/switch] [*<player>] = [<permission>]
Switches:
del : delete the given permission from <object>.
list : list all permissions, or those set on <object>
Use * before the search string to add permissions to a player.
This command sets/clears individual permission strings on an object.
Use /list without any arguments to see all available permissions
or those defined on the <object>/<player> argument.
"""
key = "@perm"
aliases = "@setperm"
locks = "cmd:perm(perm) or perm(Immortals)"
help_category = "Admin"
def func(self):
"Implement function"
caller = self.caller
switches = self.switches
lhs, rhs = self.lhs, self.rhs
if not self.args:
if "list" not in switches:
string = "Usage: @setperm[/switch] [object = permission]\n"
string += " @setperm[/switch] [*player = permission]"
caller.msg(string)
return
else:
#just print all available permissions
string = "\nAll defined permission groups and keys (i.e. not locks):"
pgroups = list(PermissionGroup.objects.all())
pgroups.sort(lambda x, y: cmp(x.key, y.key)) # sort by group key
for pgroup in pgroups:
string += "\n\n - {w%s{n (%s):" % (pgroup.key, pgroup.desc)
string += "\n%s" % \
utils.fill(", ".join(sorted(pgroup.group_permissions)))
caller.msg(string)
return
# locate the object/player
obj = caller.search(lhs, global_search=True)
if not obj:
return
pstring = ""
if utils.inherits_from(obj, settings.BASE_PLAYER_TYPECLASS):
pstring = " Player "
if not rhs:
string = "Permission string on %s{w%s{n: " % (pstring, obj.key)
if not obj.permissions:
string += "<None>"
else:
string += ", ".join(obj.permissions)
if pstring and obj.is_superuser:
string += "\n(... But this player is a SUPERUSER! "
string += "All access checked are passed automatically.)"
elif obj.player and obj.player.is_superuser:
string += "\n(... But this object's player is a SUPERUSER! "
string += "All access checked are passed automatically.)"
caller.msg(string)
return
# we supplied an argument on the form obj = perm
cstring = ""
tstring = ""
if 'del' in switches:
# delete the given permission(s) from object.
for perm in self.rhslist:
try:
index = obj.permissions.index(perm)
except ValueError:
cstring += "\nPermission '%s' was not defined on %s%s." % (perm, pstring, lhs)
continue
permissions = obj.permissions
del permissions[index]
obj.permissions = permissions
cstring += "\nPermission '%s' was removed from %s%s." % (perm, pstring, obj.name)
tstring += "\n%s revokes the permission '%s' from you." % (caller.name, perm)
else:
# As an extra check, we warn the user if they customize the
# permission string (which is okay, and is used by the lock system)
permissions = obj.permissions
for perm in self.rhslist:
if perm in permissions:
cstring += "\nPermission '%s' is already defined on %s%s." % (rhs, pstring, obj.name)
else:
permissions.append(perm)
obj.permissions = permissions
cstring += "\nPermission '%s' given to %s%s." % (rhs, pstring, obj.name)
tstring += "\n%s granted you the permission '%s'." % (caller.name, rhs)
caller.msg(cstring.strip())
if tstring:
obj.msg(tstring.strip())
class CmdPuppet(MuxCommand):
"""
Switch control to an object
Usage:
@puppet <character object>
This will attempt to "become" a different character. Note that this command does not check so that
the target object has the appropriate cmdset. You cannot puppet a character that is already "taken".
"""
key = "@puppet"
locks = "cmd:perm(puppet) or perm(Builders)"
help_category = "Admin"
def func(self):
"""
Simple puppet method (does not check permissions)
"""
caller = self.caller
if not self.args:
caller.msg("Usage: @puppet <character>")
return
player = caller.player
new_character = caller.search(self.args)
if not new_character:
return
if not utils.inherits_from(new_character, settings.BASE_CHARACTER_TYPECLASS):
caller.msg("%s is not a Character." % self.args)
return
if new_character.player:
caller.msg("This character is already under the control of a player.")
if player.swap_character(new_character):
new_character.msg("You now control %s." % new_character.name)
else:
caller.msg("You cannot control %s." % new_character.name)
class CmdWall(MuxCommand):
"""
@wall
Usage:
@wall <message>
Announces a message to all connected players.
"""
key = "@wall"
locks = "cmd:perm(wall) or perm(Wizards)"
help_category = "Admin"
def func(self):
"Implements command"
if not self.args:
self.caller.msg("Usage: @wall <message>")
return
message = "%s shouts \"%s\"" % (self.caller.name, self.args)
SESSIONS.announce_all(message)