Updated lock system to use Google-style syntax as per #709.
This commit is contained in:
parent
aff6f8c4b4
commit
58465ef93c
1 changed files with 149 additions and 52 deletions
|
|
@ -1,10 +1,9 @@
|
||||||
"""
|
"""
|
||||||
Locks
|
A *lock* defines access to a particular subsystem or property of
|
||||||
|
|
||||||
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 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
|
||||||
|
|
@ -15,8 +14,6 @@ A lock consists of three parts:
|
||||||
set of allowed arguments. They should always return a boolean depending
|
set of allowed arguments. They should always return a boolean depending
|
||||||
on if they allow access or not.
|
on if they allow access or not.
|
||||||
|
|
||||||
# Lock function
|
|
||||||
|
|
||||||
A lock function is defined by existing in one of the modules
|
A lock function is defined by existing in one of the modules
|
||||||
listed by settings.LOCK_FUNC_MODULES. It should also always
|
listed by settings.LOCK_FUNC_MODULES. It should also always
|
||||||
take four arguments looking like this:
|
take four arguments looking like this:
|
||||||
|
|
@ -42,7 +39,8 @@ 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 string")
|
|
||||||
|
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.
|
||||||
|
|
@ -95,7 +93,8 @@ use instead:
|
||||||
if not target_obj.access(caller, 'edit'):
|
if not target_obj.access(caller, 'edit'):
|
||||||
caller.msg("Sorry, you cannot edit that.")
|
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
|
||||||
typeclassed objects. The default perm() lock function uses them,
|
typeclassed objects. The default perm() lock function uses them,
|
||||||
|
|
@ -121,7 +120,9 @@ WARNING_LOG = "lockwarnings.log"
|
||||||
#
|
#
|
||||||
|
|
||||||
class LockException(Exception):
|
class LockException(Exception):
|
||||||
"raised during an error in a lock."
|
"""
|
||||||
|
Raised during an error in a lock.
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -131,7 +132,9 @@ class LockException(Exception):
|
||||||
|
|
||||||
_LOCKFUNCS = {}
|
_LOCKFUNCS = {}
|
||||||
def _cache_lockfuncs():
|
def _cache_lockfuncs():
|
||||||
"Updates the cache."
|
"""
|
||||||
|
Updates the cache.
|
||||||
|
"""
|
||||||
global _LOCKFUNCS
|
global _LOCKFUNCS
|
||||||
_LOCKFUNCS = {}
|
_LOCKFUNCS = {}
|
||||||
for modulepath in settings.LOCK_FUNC_MODULES:
|
for modulepath in settings.LOCK_FUNC_MODULES:
|
||||||
|
|
@ -161,12 +164,17 @@ class LockHandler(object):
|
||||||
"""
|
"""
|
||||||
This handler should be attached to all objects implementing
|
This handler should be attached to all objects implementing
|
||||||
permission checks, under the property 'lockhandler'.
|
permission checks, under the property 'lockhandler'.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
"""
|
"""
|
||||||
Loads and pre-caches all relevant locks and their
|
Loads and pre-caches all relevant locks and their functions.
|
||||||
functions.
|
|
||||||
|
Args:
|
||||||
|
obj (object): The object on which the lockhandler is
|
||||||
|
defined.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not _LOCKFUNCS:
|
if not _LOCKFUNCS:
|
||||||
_cache_lockfuncs()
|
_cache_lockfuncs()
|
||||||
|
|
@ -186,7 +194,11 @@ class LockHandler(object):
|
||||||
Helper function. This is normally only called when the
|
Helper function. This is normally only called when the
|
||||||
lockstring is cached and does preliminary checking. locks are
|
lockstring is cached and does preliminary checking. locks are
|
||||||
stored as a string
|
stored as a string
|
||||||
'atype:[NOT] lock()[[ AND|OR [NOT] lock()[...]];atype...
|
|
||||||
|
atype:[NOT] lock()[[ AND|OR [NOT] lock()[...]];atype...
|
||||||
|
|
||||||
|
Args:
|
||||||
|
storage_locksring (str): The lockstring to parse.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
locks = {}
|
locks = {}
|
||||||
|
|
@ -245,28 +257,45 @@ class LockHandler(object):
|
||||||
return locks
|
return locks
|
||||||
|
|
||||||
def _cache_locks(self, storage_lockstring):
|
def _cache_locks(self, storage_lockstring):
|
||||||
"""Store data"""
|
"""
|
||||||
|
Store data
|
||||||
|
"""
|
||||||
self.locks = self._parse_lockstring(storage_lockstring)
|
self.locks = self._parse_lockstring(storage_lockstring)
|
||||||
|
|
||||||
def _save_locks(self):
|
def _save_locks(self):
|
||||||
"Store locks to obj"
|
"""
|
||||||
|
Store locks to obj
|
||||||
|
"""
|
||||||
self.obj.lock_storage = ";".join([tup[2] for tup in self.locks.values()])
|
self.obj.lock_storage = ";".join([tup[2] for tup in self.locks.values()])
|
||||||
|
|
||||||
def cache_lock_bypass(self, obj):
|
def cache_lock_bypass(self, obj):
|
||||||
"""
|
"""
|
||||||
We cache superuser bypass checks here for efficiency. This needs to
|
We cache superuser bypass checks here for efficiency. This
|
||||||
be re-run when a player is assigned to a character.
|
needs to be re-run when a player is assigned to a character.
|
||||||
We need to grant access to superusers. We need to check both directly
|
We need to grant access to superusers. We need to check both
|
||||||
on the object (players), through obj.player and using the get_player()
|
directly on the object (players), through obj.player and using
|
||||||
method (this sits on serversessions, in some rare cases where a
|
the get_player() method (this sits on serversessions, in some
|
||||||
check is done before the login process has yet been fully finalized)
|
rare cases where a check is done before the login process has
|
||||||
|
yet been fully finalized)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj (object): This is checked for the `is_superuser` property.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.lock_bypass = hasattr(obj, "is_superuser") and obj.is_superuser
|
self.lock_bypass = hasattr(obj, "is_superuser") and obj.is_superuser
|
||||||
|
|
||||||
def add(self, lockstring):
|
def add(self, lockstring):
|
||||||
"""
|
"""
|
||||||
Add a new lockstring on the form '<access_type>:<functions>'. Multiple
|
Add a new lockstring to handler.
|
||||||
access types should be separated by semicolon (;).
|
|
||||||
|
Args:
|
||||||
|
lockstring (str): A string on the form
|
||||||
|
`"<access_type>:<functions>"`. Multiple access types
|
||||||
|
should be separated by semicolon (`;`).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
success (bool): The outcome of the addition, `False` on
|
||||||
|
error.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# sanity checks
|
# sanity checks
|
||||||
|
|
@ -296,7 +325,20 @@ class LockHandler(object):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def replace(self, lockstring):
|
def replace(self, lockstring):
|
||||||
"Replaces the lockstring entirely."
|
"""
|
||||||
|
Replaces the lockstring entirely.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
lockstring (str): The new lock definition.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
success (bool): False if an error occurred.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
LockException: If a critical error occurred.
|
||||||
|
If so, the old string is recovered.
|
||||||
|
|
||||||
|
"""
|
||||||
old_lockstring = str(self)
|
old_lockstring = str(self)
|
||||||
self.clear()
|
self.clear()
|
||||||
try:
|
try:
|
||||||
|
|
@ -306,13 +348,34 @@ class LockHandler(object):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def get(self, access_type=None):
|
def get(self, access_type=None):
|
||||||
"get the full lockstring or the lockstring of a particular access type."
|
"""
|
||||||
|
Get the full lockstring or the lockstring of a particular
|
||||||
|
access type.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
access_type (str, optional):
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
lockstring (str): The matched lockstring, or the full
|
||||||
|
lockstring if no access_type was given.
|
||||||
|
"""
|
||||||
|
|
||||||
if access_type:
|
if access_type:
|
||||||
return self.locks.get(access_type, ["", "", ""])[2]
|
return self.locks.get(access_type, ["", "", ""])[2]
|
||||||
return str(self)
|
return str(self)
|
||||||
|
|
||||||
def delete(self, access_type):
|
def delete(self, access_type):
|
||||||
"Remove a lock from the handler"
|
"""
|
||||||
|
Remove a particular lock from the handler
|
||||||
|
|
||||||
|
Args:
|
||||||
|
access_type (str): The type of lock to remove.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
success (bool): If the access_type was not found
|
||||||
|
in the lock, this returns `False`.
|
||||||
|
|
||||||
|
"""
|
||||||
if access_type in self.locks:
|
if access_type in self.locks:
|
||||||
del self.locks[access_type]
|
del self.locks[access_type]
|
||||||
self._save_locks()
|
self._save_locks()
|
||||||
|
|
@ -320,45 +383,53 @@ class LockHandler(object):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"Remove all locks"
|
"""
|
||||||
|
Remove all locks in the handler.
|
||||||
|
|
||||||
|
"""
|
||||||
self.locks = {}
|
self.locks = {}
|
||||||
self.lock_storage = ""
|
self.lock_storage = ""
|
||||||
self._save_locks()
|
self._save_locks()
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""
|
"""
|
||||||
Set the reset flag, so the the lock will be re-cached at next checking.
|
Set the reset flag, so the the lock will be re-cached at next
|
||||||
This is usually set by @reload.
|
checking. This is usually called by @reload.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self._cache_locks(self.obj.lock_storage)
|
self._cache_locks(self.obj.lock_storage)
|
||||||
self.cache_lock_bypass(self.obj)
|
self.cache_lock_bypass(self.obj)
|
||||||
|
|
||||||
def check(self, accessing_obj, access_type, default=False, no_superuser_bypass=False):
|
def check(self, accessing_obj, access_type, default=False, no_superuser_bypass=False):
|
||||||
"""
|
"""
|
||||||
Checks a lock of the correct type by passing execution
|
Checks a lock of the correct type by passing execution off to
|
||||||
off to the lock function(s).
|
the lock function(s).
|
||||||
|
|
||||||
accessing_obj - the object seeking access
|
Args:
|
||||||
access_type - the type of access wanted
|
accessing_obj (object): The object seeking access.
|
||||||
default - if no suitable lock type is found, use this
|
access_type (str): The type of access wanted.
|
||||||
no_superuser_bypass - don't use this unless you really, really need to,
|
default (bool, optional): If no suitable lock type is
|
||||||
it makes supersusers susceptible to the lock check.
|
found, default to this result.
|
||||||
|
no_superuser_bypass (bool): 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:
|
Notes:
|
||||||
|
A lock is executed in the follwoing way:
|
||||||
|
|
||||||
Parsing the lockstring, we (during cache) extract the valid
|
Parsing the lockstring, we (during cache) extract the valid
|
||||||
lock functions and store their function objects in the right
|
lock functions and store their function objects in the right
|
||||||
order along with their args/kwargs. These are now executed in
|
order along with their args/kwargs. These are now executed in
|
||||||
sequence, creating a list of True/False values. This is put
|
sequence, creating a list of True/False values. This is put
|
||||||
into the evalstring, which is a string of AND/OR/NOT entries
|
into the evalstring, which is a string of AND/OR/NOT entries
|
||||||
separated by placeholders where each function result should
|
separated by placeholders where each function result should
|
||||||
go. We just put those results in and evaluate the string to
|
go. We just put those results in and evaluate the string to
|
||||||
get a final, combined True/False value for the lockstring.
|
get a final, combined True/False value for the lockstring.
|
||||||
|
|
||||||
The important bit with this solution is that the full
|
The important bit with this solution is that the full
|
||||||
lockstring is never blindly evaluated, and thus there (should
|
lockstring is never blindly evaluated, and thus there (should
|
||||||
be) no way to sneak in malign code in it. Only "safe" lock
|
be) no way to sneak in malign code in it. Only "safe" lock
|
||||||
functions (as defined by your settings) are executed.
|
functions (as defined by your settings) are executed.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
|
@ -385,6 +456,15 @@ class LockHandler(object):
|
||||||
return default
|
return default
|
||||||
|
|
||||||
def _eval_access_type(self, accessing_obj, locks, access_type):
|
def _eval_access_type(self, accessing_obj, locks, access_type):
|
||||||
|
"""
|
||||||
|
Helper method for evaluating the access type using eval().
|
||||||
|
|
||||||
|
Args:
|
||||||
|
accessing_obj (object): Object seeking access.
|
||||||
|
locks (dict): The pre-parsed representation of all access-types.
|
||||||
|
access_type (str): An access-type key to evaluate.
|
||||||
|
|
||||||
|
"""
|
||||||
evalstring, func_tup, raw_string = locks[access_type]
|
evalstring, func_tup, raw_string = locks[access_type]
|
||||||
true_false = tuple(tup[0](accessing_obj, self.obj, *tup[1], **tup[2])
|
true_false = tuple(tup[0](accessing_obj, self.obj, *tup[1], **tup[2])
|
||||||
for tup in func_tup)
|
for tup in func_tup)
|
||||||
|
|
@ -393,10 +473,27 @@ class LockHandler(object):
|
||||||
def check_lockstring(self, accessing_obj, lockstring, no_superuser_bypass=False,
|
def check_lockstring(self, accessing_obj, lockstring, no_superuser_bypass=False,
|
||||||
default=False, access_type=None):
|
default=False, access_type=None):
|
||||||
"""
|
"""
|
||||||
Do a direct check against a lockstring ('atype:func()..'), without any
|
Do a direct check against a lockstring ('atype:func()..'),
|
||||||
intermediary storage on the accessed object (this can be left
|
without any intermediary storage on the accessed object.
|
||||||
to None if the lock functions called don't access it). atype can also be
|
|
||||||
put to a dummy value since no lock selection is made.
|
Args:
|
||||||
|
accessing_obj (object or None): The object seeking access.
|
||||||
|
Importantly, this can be left unset if the lock functions
|
||||||
|
don't access it, no updating or storage of locks are made
|
||||||
|
against this object in this method.
|
||||||
|
lockstring (str): Lock string to check, on the form
|
||||||
|
`"access_type:lock_definition"` where the `access_type`
|
||||||
|
part can potentially be set to a dummy value to just check
|
||||||
|
a lock condition.
|
||||||
|
no_superuser_bypass (bool, optional): Force superusers to heed lock.
|
||||||
|
default (bool, optional): Fallback result to use if `access_type` is set
|
||||||
|
but no such `access_type` is found in the given `lockstring`.
|
||||||
|
access_type (str, bool): If set, only this access_type will be looked up
|
||||||
|
among the locks defined by `lockstring`.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
access (bool): If check is passed or not.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if accessing_obj.locks.lock_bypass and not no_superuser_bypass:
|
if accessing_obj.locks.lock_bypass and not no_superuser_bypass:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue