mirror of
https://github.com/evennia/evennia.git
synced 2026-03-17 05:16:31 +01:00
- 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.
185 lines
7.2 KiB
Python
185 lines
7.2 KiB
Python
"""
|
|
The base Command class.
|
|
|
|
All commands in Evennia inherit from the 'Command' class in this module.
|
|
|
|
"""
|
|
|
|
from src.locks.lockhandler import LockHandler
|
|
from src.utils.utils import is_iter
|
|
|
|
class CommandMeta(type):
|
|
"""
|
|
This metaclass makes some minor on-the-fly convenience fixes to the command
|
|
class in case the admin forgets to put things in lowercase etc.
|
|
"""
|
|
def __init__(mcs, *args, **kwargs):
|
|
"""
|
|
Simply make sure all data are stored as lowercase and
|
|
do checking on all properties that should be in list form.
|
|
Sets up locks to be more forgiving.
|
|
"""
|
|
mcs.key = mcs.key.lower()
|
|
if mcs.aliases and not is_iter(mcs.aliases):
|
|
mcs.aliases = mcs.aliases.split(',')
|
|
mcs.aliases = [str(alias).strip().lower() for alias in mcs.aliases]
|
|
|
|
# pre-process locks as defined in class definition
|
|
temp = []
|
|
if hasattr(mcs, 'permissions'):
|
|
mcs.locks = mcs.permissions
|
|
for lockstring in mcs.locks.split(';'):
|
|
if lockstring and not ':' in lockstring:
|
|
lockstring = "cmd:%s" % lockstring
|
|
temp.append(lockstring)
|
|
mcs.lock_storage = ";".join(temp)
|
|
|
|
mcs.help_category = mcs.help_category.lower()
|
|
super(CommandMeta, mcs).__init__(*args, **kwargs)
|
|
|
|
# The Command class is the basic unit of an Evennia command; when
|
|
# defining new commands, the admin subclass this class and
|
|
# define their own parser method to handle the input. The
|
|
# advantage of this is inheritage; commands that have similar
|
|
# structure can parse the input string the same way, minimizing
|
|
# parsing errors.
|
|
|
|
class Command(object):
|
|
"""
|
|
Base command
|
|
|
|
Usage:
|
|
command [args]
|
|
|
|
This is the base command class. Inherit from this
|
|
to create new commands.
|
|
|
|
The cmdhandler makes the following variables available to the
|
|
command methods (so you can always assume them to be there):
|
|
self.caller - the game object calling the command
|
|
self.cmdstring - the command name used to trigger this command (allows
|
|
you to know which alias was used, for example)
|
|
cmd.args - everything supplied to the command following the cmdstring
|
|
(this is usually what is parsed in self.parse())
|
|
cmd.cmdset - the cmdset from which this command was matched (useful only
|
|
seldomly, notably for help-type commands, to create dynamic
|
|
help entries and lists)
|
|
cmd.obj - the object on which this command is defined. If a default command,
|
|
this is usually the same as caller.
|
|
|
|
(Note that this initial string is also used by the system to create the help
|
|
entry for the command, so it's a good idea to format it similar to this one)
|
|
"""
|
|
# Tie our metaclass, for some convenience cleanup
|
|
__metaclass__ = CommandMeta
|
|
|
|
# the main way to call this command (e.g. 'look')
|
|
key = "command"
|
|
# alternative ways to call the command (e.g. 'l', 'glance', 'examine')
|
|
aliases = []
|
|
# a list of lock definitions on the form cmd:[NOT] func(args) [ AND|OR][ NOT] func2(args)
|
|
locks = ""
|
|
# used by the help system to group commands in lists.
|
|
help_category = "general"
|
|
# There is also the property 'obj'. This gets set by the system
|
|
# on the fly to tie this particular command to a certain in-game entity.
|
|
# self.obj should NOT be defined here since it will not be overwritten
|
|
# if it already exists.
|
|
|
|
def __init__(self):
|
|
self.lockhandler = LockHandler(self)
|
|
|
|
def __str__(self):
|
|
"Print the command"
|
|
return self.key
|
|
|
|
def __eq__(self, cmd):
|
|
"""
|
|
Compare two command instances to each other by matching their
|
|
key and aliases.
|
|
input can be either a cmd object or the name of a command.
|
|
"""
|
|
if type(cmd) != self:
|
|
return self.match(cmd)
|
|
return self.match(cmd.key)
|
|
|
|
def __contains__(self, query):
|
|
"""
|
|
This implements searches like 'if query in cmd'. It's a fuzzy matching
|
|
used by the help system, returning True if query can be found
|
|
as a substring of the commands key or its aliases.
|
|
|
|
input can be either a command object or a command name.
|
|
"""
|
|
if type(query) == type(Command()):
|
|
query = query.key
|
|
return (query in self.key) or \
|
|
(sum([query in alias for alias in self.aliases]) > 0)
|
|
|
|
def match(self, cmdname):
|
|
"""
|
|
This is called by the system when searching the available commands,
|
|
in order to determine if this is the one we wanted. cmdname was
|
|
previously extracted from the raw string by the system.
|
|
cmdname is always lowercase when reaching this point.
|
|
"""
|
|
return (cmdname == self.key) or (cmdname in self.aliases)
|
|
|
|
def access(self, srcobj, access_type="cmd", default=False):
|
|
"""
|
|
This hook is called by the cmdhandler to determine if srcobj
|
|
is allowed to execute this command. It should return a boolean
|
|
value and is not normally something that need to be changed since
|
|
it's using the Evennia permission system directly.
|
|
"""
|
|
return self.lockhandler.check(srcobj, access_type, default=default)
|
|
|
|
# Common Command hooks
|
|
|
|
def at_pre_cmd(self):
|
|
"""
|
|
This hook is called before self.parse() on all commands
|
|
"""
|
|
pass
|
|
|
|
def at_post_cmd(self):
|
|
"""
|
|
This hook is called after the command has finished executing
|
|
(after self.func()).
|
|
"""
|
|
pass
|
|
|
|
def parse(self):
|
|
"""
|
|
Once the cmdhandler has identified this as the command we
|
|
want, this function is run. If many of your commands have
|
|
a similar syntax (for example 'cmd arg1 = arg2') you should simply
|
|
define this once and just let other commands of the same form
|
|
inherit from this. See the docstring of this module for
|
|
which object properties are available to use
|
|
(notably self.args).
|
|
"""
|
|
pass
|
|
|
|
def func(self):
|
|
"""
|
|
This is the actual executing part of the command.
|
|
It is called directly after self.parse(). See the docstring
|
|
of this module for which object properties are available
|
|
(beyond those set in self.parse())
|
|
"""
|
|
# a simple test command to show the available properties
|
|
string = "-" * 50
|
|
string += "\n{w%s{n - Command variables from evennia:\n" % self.key
|
|
string += "-" * 50
|
|
string += "\nname of cmd (self.key): {w%s{n\n" % self.key
|
|
string += "cmd aliases (self.aliases): {w%s{n\n" % self.aliases
|
|
string += "cmd perms (self.permissions): {w%s{n\n" % self.permissions
|
|
string += "help category (self.help_category): {w%s{n\n" % self.help_category
|
|
string += "object calling (self.caller): {w%s{n\n" % self.caller
|
|
string += "object storing cmdset (self.obj): {w%s{n\n" % self.obj
|
|
string += "command string given (self.cmdstring): {w%s{n\n" % self.cmdstring
|
|
# show cmdset.key instead of cmdset to shorten output
|
|
string += utils.fill("current cmdset (self.cmdset): {w%s{n\n" % self.cmdset)
|
|
|
|
self.caller.msg(string)
|