Some cleanup in the lockhandler.
This commit is contained in:
parent
fd857f5c86
commit
c47ff33aae
1 changed files with 50 additions and 32 deletions
|
|
@ -5,7 +5,7 @@ A lock defines access to a particular subsystem or property of
|
||||||
Evennia. For example, the "owner" property can be impmemented as a
|
Evennia. For example, the "owner" property can be impmemented as a
|
||||||
lock. Or the disability to lift an object or to ban users.
|
lock. Or the disability to lift an object or to ban users.
|
||||||
|
|
||||||
A lock consists of two three parts:
|
A lock consists of three parts:
|
||||||
|
|
||||||
- access_type - this defines what kind of access this lock regulates. This
|
- access_type - this defines what kind of access this lock regulates. This
|
||||||
just a string.
|
just a string.
|
||||||
|
|
@ -42,7 +42,7 @@ Lock functions should most often be pretty general and ideally possible to
|
||||||
re-use and combine in various ways to build clever locks.
|
re-use and combine in various ways to build clever locks.
|
||||||
|
|
||||||
|
|
||||||
# Lock definition
|
# Lock definition ("Lock string")
|
||||||
|
|
||||||
A lock definition is a string with a special syntax. It is added to
|
A lock definition is a string with a special syntax. It is added to
|
||||||
each object's lockhandler, making that lock available from then on.
|
each object's lockhandler, making that lock available from then on.
|
||||||
|
|
@ -59,12 +59,13 @@ Example:
|
||||||
We want to limit who may edit a particular object (let's call this access_type
|
We want to limit who may edit a particular object (let's call this access_type
|
||||||
for 'edit', it depends on what the command is looking for). We want this to
|
for 'edit', it depends on what the command is looking for). We want this to
|
||||||
only work for those with the Permission 'Builders'. So we use our lock
|
only work for those with the Permission 'Builders'. So we use our lock
|
||||||
function above and call it like this:
|
function above and define it like this:
|
||||||
|
|
||||||
'edit:perm(Builders)'
|
'edit:perm(Builders)'
|
||||||
|
|
||||||
Here, the lock-function perm() will be called (accessing_obj and accessed_obj are added
|
Here, the lock-function perm() will be called with the string
|
||||||
automatically, you only need to add the args/kwargs, if any).
|
'Builders' (accessing_obj and accessed_obj are added automatically,
|
||||||
|
you only need to add the args/kwargs, if any).
|
||||||
|
|
||||||
If we wanted to make sure the accessing object was BOTH a Builders and a GoodGuy, we
|
If we wanted to make sure the accessing object was BOTH a Builders and a GoodGuy, we
|
||||||
could use AND:
|
could use AND:
|
||||||
|
|
@ -86,9 +87,13 @@ From then on, a command that wants to check for 'edit' access on this
|
||||||
object would do something like this:
|
object would do something like this:
|
||||||
|
|
||||||
if not target_obj.lockhandler.has_perm(caller, 'edit'):
|
if not target_obj.lockhandler.has_perm(caller, 'edit'):
|
||||||
caller.msg("Sorry you cannot edit that.")
|
caller.msg("Sorry, you cannot edit that.")
|
||||||
|
|
||||||
|
All objects also has a shortcut called 'access' that is recommended to use instead:
|
||||||
|
|
||||||
|
if not target_obj.access(caller, 'edit'):
|
||||||
|
caller.msg("Sorry, you cannot edit that.")
|
||||||
|
|
||||||
# Permissions
|
# Permissions
|
||||||
|
|
||||||
Permissions are just text strings stored in a comma-separated list on
|
Permissions are just text strings stored in a comma-separated list on
|
||||||
|
|
@ -108,9 +113,9 @@ from src.utils import logger, utils
|
||||||
#
|
#
|
||||||
|
|
||||||
class LockException(Exception):
|
class LockException(Exception):
|
||||||
|
"raised during an error in a lock."
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Cached lock functions
|
# Cached lock functions
|
||||||
#
|
#
|
||||||
|
|
@ -166,7 +171,6 @@ class LockHandler(object):
|
||||||
self._cache_locks(self.obj.lock_storage)
|
self._cache_locks(self.obj.lock_storage)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return ";".join(self.locks[key][2] for key in sorted(self.locks))
|
return ";".join(self.locks[key][2] for key in sorted(self.locks))
|
||||||
|
|
||||||
|
|
@ -181,8 +185,10 @@ class LockHandler(object):
|
||||||
|
|
||||||
def _parse_lockstring(self, storage_lockstring):
|
def _parse_lockstring(self, storage_lockstring):
|
||||||
"""
|
"""
|
||||||
Helper function.
|
Helper function. This is normally only called when the
|
||||||
locks are stored as a string 'atype:[NOT] lock()[[ AND|OR [NOT] lock() [...]];atype...
|
lockstring is cached and does preliminary checking. locks are
|
||||||
|
stored as a string 'atype:[NOT] lock()[[ AND|OR [NOT] lock()[...]];atype...
|
||||||
|
|
||||||
"""
|
"""
|
||||||
locks = {}
|
locks = {}
|
||||||
if not storage_lockstring:
|
if not storage_lockstring:
|
||||||
|
|
@ -204,14 +210,14 @@ class LockHandler(object):
|
||||||
evalstring = rhs.replace('AND','and').replace('OR','or').replace('NOT','not')
|
evalstring = rhs.replace('AND','and').replace('OR','or').replace('NOT','not')
|
||||||
nfuncs = len(funclist)
|
nfuncs = len(funclist)
|
||||||
for funcstring in funclist:
|
for funcstring in funclist:
|
||||||
funcname, rest = [part.strip().strip(')') for part in funcstring.split('(', 1)]
|
funcname, rest = (part.strip().strip(')') for part in funcstring.split('(', 1))
|
||||||
func = LOCKFUNCS.get(funcname, None)
|
func = LOCKFUNCS.get(funcname, None)
|
||||||
if not callable(func):
|
if not callable(func):
|
||||||
elist.append("Lock: function '%s' is not available." % funcstring)
|
elist.append("Lock: function '%s' is not available." % funcstring)
|
||||||
continue
|
continue
|
||||||
args = [arg.strip() for arg in rest.split(',') if not '=' in arg]
|
args = list(arg.strip() for arg in rest.split(',') if not '=' in arg)
|
||||||
kwargs = dict([arg.split('=', 1) for arg in rest.split(',') if '=' in arg])
|
kwargs = dict([arg.split('=', 1) for arg in rest.split(',') if '=' in arg])
|
||||||
lock_funcs.append((func, args, kwargs))
|
lock_funcs.append((func, args, kwargs))
|
||||||
evalstring = evalstring.replace(funcstring, '%s')
|
evalstring = evalstring.replace(funcstring, '%s')
|
||||||
if len(lock_funcs) < nfuncs:
|
if len(lock_funcs) < nfuncs:
|
||||||
continue
|
continue
|
||||||
|
|
@ -228,12 +234,13 @@ class LockHandler(object):
|
||||||
(access_type, locks[access_type][2], raw_lockstring))
|
(access_type, locks[access_type][2], raw_lockstring))
|
||||||
locks[access_type] = (evalstring, tuple(lock_funcs), raw_lockstring)
|
locks[access_type] = (evalstring, tuple(lock_funcs), raw_lockstring)
|
||||||
if wlist and self.log_obj:
|
if wlist and self.log_obj:
|
||||||
# not an error, so only report if log_obj is available.
|
# a warning text was set, it's not an error, so only report if log_obj is available.
|
||||||
self._log_error("\n".join(wlist))
|
self._log_error("\n".join(wlist))
|
||||||
if elist:
|
if elist:
|
||||||
|
# an error text was set, raise exception.
|
||||||
raise LockException("\n".join(elist))
|
raise LockException("\n".join(elist))
|
||||||
self.no_errors = False
|
self.no_errors = False
|
||||||
|
# return the gathered locks in an easily executable form
|
||||||
return locks
|
return locks
|
||||||
|
|
||||||
def _cache_locks(self, storage_lockstring):
|
def _cache_locks(self, storage_lockstring):
|
||||||
|
|
@ -312,7 +319,24 @@ class LockHandler(object):
|
||||||
accessing_obj - the object seeking access
|
accessing_obj - the object seeking access
|
||||||
access_type - the type of access wanted
|
access_type - the type of access wanted
|
||||||
default - if no suitable lock type is found, use this
|
default - if no suitable lock type is found, use this
|
||||||
no_superuser_bypass - don't use this unless you really, really need to
|
no_superuser_bypass - don't use this unless you really, really need to,
|
||||||
|
it makes supersusers susceptible to the lock check.
|
||||||
|
|
||||||
|
A lock is executed in the follwoing way:
|
||||||
|
|
||||||
|
Parsing the lockstring, we (during cache) extract the valid
|
||||||
|
lock functions and store their function objects in the right
|
||||||
|
order along with their args/kwargs. These are now executed in
|
||||||
|
sequence, creating a list of True/False values. This is put
|
||||||
|
into the evalstring, which is a string of AND/OR/NOT entries
|
||||||
|
separated by placeholders where each function result should
|
||||||
|
go. We just put those results in and evaluate the string to
|
||||||
|
get a final, combined True/False value for the lockstring.
|
||||||
|
|
||||||
|
The important bit with this solution is that the full
|
||||||
|
lockstring is never blindly evaluated, and thus there (should
|
||||||
|
be) no way to sneak in malign code in it. Only "safe" lock
|
||||||
|
functions (as defined by your settings) are executed.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.reset_flag:
|
if self.reset_flag:
|
||||||
|
|
@ -331,15 +355,11 @@ class LockHandler(object):
|
||||||
if access_type in self.locks:
|
if access_type in self.locks:
|
||||||
# we have a lock, test it.
|
# we have a lock, test it.
|
||||||
evalstring, func_tup, raw_string = self.locks[access_type]
|
evalstring, func_tup, raw_string = self.locks[access_type]
|
||||||
# we have previously stored the function object and all the args/kwargs as list/dict,
|
# execute all lock funcs in the correct order, producing a tuple of True/False results.
|
||||||
# now we just execute them all in sequence. The result will be a list of True/False
|
true_false = tuple(bool(tup[0](accessing_obj, self.obj, *tup[1], **tup[2])) for tup in func_tup)
|
||||||
# statements. Note that there is no eval here, these are normal command calls!
|
# the True/False tuple goes into evalstring, which combines them
|
||||||
true_false = (bool(tup[0](accessing_obj, self.obj, *tup[1], **tup[2])) for tup in func_tup)
|
# with AND/OR/NOT in order to get the final result.
|
||||||
# we now input these True/False list into the evalstring, which combines them with
|
return eval(evalstring % true_false)
|
||||||
# AND/OR/NOT in order to get the final result
|
|
||||||
#true_false = tuple(true_false)
|
|
||||||
#print accessing_obj, self.obj, access_type, true_false, evalstring, func_tup, raw_string
|
|
||||||
return eval(evalstring % tuple(true_false))
|
|
||||||
else:
|
else:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
@ -357,12 +377,10 @@ class LockHandler(object):
|
||||||
|
|
||||||
locks = self. _parse_lockstring(lockstring)
|
locks = self. _parse_lockstring(lockstring)
|
||||||
for access_type in locks:
|
for access_type in locks:
|
||||||
evalstring, func_tup, raw_string = locks[access_type]
|
evalstring, func_tup, raw_string = locks[access_type]
|
||||||
true_false = (tup[0](accessing_obj, self.obj, *tup[1], **tup[2]) for tup in func_tup)
|
true_false = tuple(tup[0](accessing_obj, self.obj, *tup[1], **tup[2]) for tup in func_tup)
|
||||||
return eval(evalstring % tuple(true_false))
|
return eval(evalstring % true_false)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test():
|
def test():
|
||||||
# testing
|
# testing
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue