mirror of
https://github.com/evennia/evennia.git
synced 2026-03-22 15:56:30 +01:00
Further cleanup of source; making class methods _private for clarity in the API.
This commit is contained in:
parent
fc156b5a54
commit
c8df141e89
18 changed files with 607 additions and 588 deletions
|
|
@ -5,15 +5,15 @@ A lock defines access to a particular subsystem or property of
|
|||
Evennia. For example, the "owner" property can be impmemented as a
|
||||
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
|
||||
just a string.
|
||||
just a string.
|
||||
- function call - this is one or many calls to functions that will determine
|
||||
if the lock is passed or not.
|
||||
- lock function(s). These are regular python functions with a special
|
||||
- lock function(s). These are regular python functions with a special
|
||||
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
|
||||
|
||||
|
|
@ -26,26 +26,26 @@ take four arguments looking like this:
|
|||
|
||||
The accessing object is the object wanting to gain access.
|
||||
The accessed object is the object this lock resides on
|
||||
args and kwargs will hold optional arguments and/or keyword arguments
|
||||
args and kwargs will hold optional arguments and/or keyword arguments
|
||||
to the function as a list and a dictionary respectively.
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
perm(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"Checking if the object has a particular, desired permission"
|
||||
if args:
|
||||
desired_perm = args[0]
|
||||
desired_perm = args[0]
|
||||
return desired_perm in accessing_obj.permissions
|
||||
return False
|
||||
return False
|
||||
|
||||
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")
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
The lock definition looks like this:
|
||||
|
||||
|
|
@ -54,15 +54,15 @@ The lock definition looks like this:
|
|||
That is, the access_type, a colon followed by calls to lock functions
|
||||
combined with AND or OR. NOT negates the result of the following call.
|
||||
|
||||
Example:
|
||||
|
||||
We want to limit who may edit a particular object (let's call this access_type
|
||||
Example:
|
||||
|
||||
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
|
||||
only work for those with the Permission 'Builders'. So we use our lock
|
||||
function above and define it like this:
|
||||
|
||||
'edit:perm(Builders)'
|
||||
|
||||
|
||||
Here, the lock-function perm() will be called with the string
|
||||
'Builders' (accessing_obj and accessed_obj are added automatically,
|
||||
you only need to add the args/kwargs, if any).
|
||||
|
|
@ -73,17 +73,17 @@ could use AND:
|
|||
'edit:perm(Builders) AND perm(GoodGuy)'
|
||||
|
||||
To allow EITHER Builders and GoodGuys, we replace AND with OR. perm() is just one example,
|
||||
the lock function can do anything and compare any properties of the calling object to
|
||||
the lock function can do anything and compare any properties of the calling object to
|
||||
decide if the lock is passed or not.
|
||||
|
||||
'lift:attrib(very_strong) AND NOT attrib(bad_back)'
|
||||
|
||||
To make these work, add the string to the lockhandler of the object you want
|
||||
to apply the lock to:
|
||||
to apply the lock to:
|
||||
|
||||
obj.lockhandler.add('edit:perm(Builders)')
|
||||
|
||||
From then on, a command that wants to check for 'edit' access on this
|
||||
From then on, a command that wants to check for 'edit' access on this
|
||||
object would do something like this:
|
||||
|
||||
if not target_obj.lockhandler.has_perm(caller, 'edit'):
|
||||
|
|
@ -93,8 +93,8 @@ All objects also has a shortcut called 'access' that is recommended to use inste
|
|||
|
||||
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
|
||||
typeclassed objects. The default perm() lock function uses them,
|
||||
|
|
@ -106,8 +106,8 @@ to any other identifier you can use.
|
|||
|
||||
import re, inspect
|
||||
from django.conf import settings
|
||||
from src.utils import logger, utils
|
||||
|
||||
from src.utils import logger, utils
|
||||
__all__ = ("LockHandler", )
|
||||
#
|
||||
# Exception class
|
||||
#
|
||||
|
|
@ -120,17 +120,17 @@ class LockException(Exception):
|
|||
# Cached lock functions
|
||||
#
|
||||
|
||||
LOCKFUNCS = {}
|
||||
def cache_lockfuncs():
|
||||
_LOCKFUNCS = {}
|
||||
def _cache_lockfuncs():
|
||||
"Updates the cache."
|
||||
global LOCKFUNCS
|
||||
LOCKFUNCS = {}
|
||||
global _LOCKFUNCS
|
||||
_LOCKFUNCS = {}
|
||||
for modulepath in settings.LOCK_FUNC_MODULES:
|
||||
modulepath = utils.pypath_to_realpath(modulepath)
|
||||
mod = utils.mod_import(modulepath)
|
||||
if mod:
|
||||
if mod:
|
||||
for tup in (tup for tup in inspect.getmembers(mod) if callable(tup[1])):
|
||||
LOCKFUNCS[tup[0]] = tup[1]
|
||||
_LOCKFUNCS[tup[0]] = tup[1]
|
||||
else:
|
||||
logger.log_errmsg("Couldn't load %s from PERMISSION_FUNC_MODULES." % modulepath)
|
||||
|
||||
|
|
@ -138,21 +138,21 @@ def cache_lockfuncs():
|
|||
# pre-compiled regular expressions
|
||||
#
|
||||
|
||||
RE_FUNCS = re.compile(r"\w+\([^)]*\)")
|
||||
RE_SEPS = re.compile(r"(?<=[ )])AND(?=\s)|(?<=[ )])OR(?=\s)|(?<=[ )])NOT(?=\s)")
|
||||
RE_OK = re.compile(r"%s|and|or|not")
|
||||
_RE_FUNCS = re.compile(r"\w+\([^)]*\)")
|
||||
_RE_SEPS = re.compile(r"(?<=[ )])AND(?=\s)|(?<=[ )])OR(?=\s)|(?<=[ )])NOT(?=\s)")
|
||||
_RE_OK = re.compile(r"%s|and|or|not")
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
# Lock handler
|
||||
# Lock handler
|
||||
#
|
||||
#
|
||||
|
||||
class LockHandler(object):
|
||||
"""
|
||||
This handler should be attached to all objects implementing
|
||||
permission checks, under the property 'lockhandler'.
|
||||
This handler should be attached to all objects implementing
|
||||
permission checks, under the property 'lockhandler'.
|
||||
"""
|
||||
|
||||
def __init__(self, obj):
|
||||
|
|
@ -160,13 +160,13 @@ class LockHandler(object):
|
|||
Loads and pre-caches all relevant locks and their
|
||||
functions.
|
||||
"""
|
||||
if not LOCKFUNCS:
|
||||
cache_lockfuncs()
|
||||
if not _LOCKFUNCS:
|
||||
_cache_lockfuncs()
|
||||
self.obj = obj
|
||||
self.locks = {}
|
||||
self.log_obj = None
|
||||
self.locks = {}
|
||||
self.log_obj = None
|
||||
self.no_errors = True
|
||||
self.reset_flag = False
|
||||
self.reset_flag = False
|
||||
|
||||
self._cache_locks(self.obj.lock_storage)
|
||||
|
||||
|
|
@ -177,7 +177,7 @@ class LockHandler(object):
|
|||
def _log_error(self, message):
|
||||
"Try to log errors back to object"
|
||||
if self.log_obj and hasattr(self.log_obj, 'msg'):
|
||||
self.log_obj.msg(message)
|
||||
self.log_obj.msg(message)
|
||||
elif hasattr(self.obj, 'msg'):
|
||||
self.obj.msg(message)
|
||||
else:
|
||||
|
|
@ -192,12 +192,12 @@ class LockHandler(object):
|
|||
"""
|
||||
locks = {}
|
||||
if not storage_lockstring:
|
||||
return locks
|
||||
return locks
|
||||
nlocks = storage_lockstring.count(';') + 1
|
||||
duplicates = 0
|
||||
elist = [] # errors
|
||||
elist = [] # errors
|
||||
wlist = [] # warnings
|
||||
for raw_lockstring in storage_lockstring.split(';'):
|
||||
for raw_lockstring in storage_lockstring.split(';'):
|
||||
lock_funcs = []
|
||||
try:
|
||||
access_type, rhs = (part.strip() for part in raw_lockstring.split(':', 1))
|
||||
|
|
@ -206,42 +206,42 @@ class LockHandler(object):
|
|||
return locks
|
||||
|
||||
# parse the lock functions and separators
|
||||
funclist = RE_FUNCS.findall(rhs)
|
||||
funclist = _RE_FUNCS.findall(rhs)
|
||||
evalstring = rhs.replace('AND','and').replace('OR','or').replace('NOT','not')
|
||||
nfuncs = len(funclist)
|
||||
for funcstring in funclist:
|
||||
for funcstring in funclist:
|
||||
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):
|
||||
elist.append("Lock: function '%s' is not available." % funcstring)
|
||||
continue
|
||||
continue
|
||||
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))
|
||||
evalstring = evalstring.replace(funcstring, '%s')
|
||||
if len(lock_funcs) < nfuncs:
|
||||
evalstring = evalstring.replace(funcstring, '%s')
|
||||
if len(lock_funcs) < nfuncs:
|
||||
continue
|
||||
try:
|
||||
# purge the eval string of any superfluos items, then test it
|
||||
evalstring = " ".join(RE_OK.findall(evalstring))
|
||||
evalstring = " ".join(_RE_OK.findall(evalstring))
|
||||
eval(evalstring % tuple(True for func in funclist))
|
||||
except Exception:
|
||||
elist.append("Lock: definition '%s' has syntax errors." % raw_lockstring)
|
||||
continue
|
||||
if access_type in locks:
|
||||
if access_type in locks:
|
||||
duplicates += 1
|
||||
wlist.append("Lock: access type '%s' changed from '%s' to '%s' " % \
|
||||
(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:
|
||||
# a warning text was set, it's not an error, so only report if log_obj is available.
|
||||
self._log_error("\n".join(wlist))
|
||||
if elist:
|
||||
# an error text was set, raise exception.
|
||||
raise LockException("\n".join(elist))
|
||||
# an error text was set, raise exception.
|
||||
raise LockException("\n".join(elist))
|
||||
self.no_errors = False
|
||||
# return the gathered locks in an easily executable form
|
||||
return locks
|
||||
# return the gathered locks in an easily executable form
|
||||
return locks
|
||||
|
||||
def _cache_locks(self, storage_lockstring):
|
||||
"""Store data"""
|
||||
|
|
@ -253,29 +253,29 @@ class LockHandler(object):
|
|||
|
||||
def add(self, lockstring, log_obj=None):
|
||||
"""
|
||||
Add a new lockstring on the form '<access_type>:<functions>'. Multiple
|
||||
access types should be separated by semicolon (;).
|
||||
Add a new lockstring on the form '<access_type>:<functions>'. Multiple
|
||||
access types should be separated by semicolon (;).
|
||||
|
||||
If log_obj is given, it will be fed error information.
|
||||
"""
|
||||
if log_obj:
|
||||
self.log_obj = log_obj
|
||||
self.no_errors = True
|
||||
# sanity checks
|
||||
for lockdef in lockstring.split(';'):
|
||||
self.no_errors = True
|
||||
# sanity checks
|
||||
for lockdef in lockstring.split(';'):
|
||||
if not ':' in lockstring:
|
||||
self._log_error("Lock: '%s' contains no colon (:)." % lockdef)
|
||||
return False
|
||||
return False
|
||||
access_type, rhs = [part.strip() for part in lockdef.split(':', 1)]
|
||||
if not access_type:
|
||||
self._log_error("Lock: '%s' has no access_type (left-side of colon is empty)." % lockdef)
|
||||
return False
|
||||
return False
|
||||
if rhs.count('(') != rhs.count(')'):
|
||||
self._log_error("Lock: '%s' has mismatched parentheses." % lockdef)
|
||||
return False
|
||||
if not RE_FUNCS.findall(rhs):
|
||||
return False
|
||||
if not _RE_FUNCS.findall(rhs):
|
||||
self._log_error("Lock: '%s' has no valid lock functions." % lockdef)
|
||||
return False
|
||||
return False
|
||||
# get the lock string
|
||||
storage_lockstring = self.obj.lock_storage
|
||||
if storage_lockstring:
|
||||
|
|
@ -285,8 +285,8 @@ class LockHandler(object):
|
|||
# cache the locks will get rid of eventual doublets
|
||||
self._cache_locks(storage_lockstring)
|
||||
self._save_locks()
|
||||
self.log_obj = None
|
||||
return self.no_errors
|
||||
self.log_obj = None
|
||||
return self.no_errors
|
||||
|
||||
def get(self, access_type):
|
||||
"get the lockstring of a particular type"
|
||||
|
|
@ -297,8 +297,8 @@ class LockHandler(object):
|
|||
if access_type in self.locks:
|
||||
del self.locks[access_type]
|
||||
self._save_locks()
|
||||
return True
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
def clear(self):
|
||||
"Remove all locks"
|
||||
|
|
@ -307,10 +307,10 @@ class LockHandler(object):
|
|||
def reset(self):
|
||||
"""
|
||||
Set the reset flag, so the the lock will be re-cached at next checking.
|
||||
This is usually set by @reload.
|
||||
This is usually set by @reload.
|
||||
"""
|
||||
self.reset_flag = True
|
||||
|
||||
|
||||
def check(self, accessing_obj, access_type, default=False, no_superuser_bypass=False):
|
||||
"""
|
||||
Checks a lock of the correct type by passing execution
|
||||
|
|
@ -322,8 +322,8 @@ class LockHandler(object):
|
|||
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:
|
||||
|
||||
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
|
||||
|
|
@ -340,11 +340,11 @@ class LockHandler(object):
|
|||
|
||||
"""
|
||||
if self.reset_flag:
|
||||
# rebuild cache
|
||||
# rebuild cache
|
||||
self._cache_locks(self.obj.lock_storage)
|
||||
self.reset_flag = False
|
||||
self.reset_flag = False
|
||||
|
||||
if (not no_superuser_bypass
|
||||
if (not no_superuser_bypass
|
||||
and ((hasattr(accessing_obj, 'is_superuser') and accessing_obj.is_superuser)
|
||||
or (hasattr(accessing_obj, 'player') and hasattr(accessing_obj.player, 'is_superuser') and accessing_obj.player.is_superuser)
|
||||
or (hasattr(accessing_obj, 'get_player') and (accessing_obj.get_player()==None or accessing_obj.get_player().is_superuser)))):
|
||||
|
|
@ -353,11 +353,11 @@ class LockHandler(object):
|
|||
return True
|
||||
|
||||
if access_type in self.locks:
|
||||
# we have a lock, test it.
|
||||
evalstring, func_tup, raw_string = self.locks[access_type]
|
||||
# we have a lock, test it.
|
||||
evalstring, func_tup, raw_string = self.locks[access_type]
|
||||
# execute all lock funcs in the correct order, producing a tuple of True/False results.
|
||||
true_false = tuple(bool(tup[0](accessing_obj, self.obj, *tup[1], **tup[2])) for tup in func_tup)
|
||||
# the True/False tuple goes into evalstring, which combines them
|
||||
# the True/False tuple goes into evalstring, which combines them
|
||||
# with AND/OR/NOT in order to get the final result.
|
||||
return eval(evalstring % true_false)
|
||||
else:
|
||||
|
|
@ -367,22 +367,22 @@ class LockHandler(object):
|
|||
"""
|
||||
Do a direct check against a lockstring ('atype:func()..'), without any
|
||||
intermediary storage on the accessed object (this can be left
|
||||
to None if the lock functions called don't access it). atype can also be
|
||||
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.
|
||||
"""
|
||||
if ((hasattr(accessing_obj, 'is_superuser') and accessing_obj.is_superuser)
|
||||
or (hasattr(accessing_obj, 'player') and hasattr(accessing_obj.player, 'is_superuser') and accessing_obj.player.is_superuser)
|
||||
or (hasattr(accessing_obj, 'get_player') and (accessing_obj.get_player()==None or accessing_obj.get_player().is_superuser))):
|
||||
return True
|
||||
|
||||
return True
|
||||
|
||||
locks = self. _parse_lockstring(lockstring)
|
||||
for access_type in locks:
|
||||
evalstring, func_tup, raw_string = locks[access_type]
|
||||
true_false = tuple(tup[0](accessing_obj, self.obj, *tup[1], **tup[2]) for tup in func_tup)
|
||||
return eval(evalstring % true_false)
|
||||
|
||||
def test():
|
||||
# testing
|
||||
|
||||
def _test():
|
||||
# testing
|
||||
|
||||
class TestObj(object):
|
||||
pass
|
||||
|
|
@ -390,9 +390,9 @@ def test():
|
|||
import pdb
|
||||
obj1 = TestObj()
|
||||
obj2 = TestObj()
|
||||
|
||||
|
||||
#obj1.lock_storage = "owner:dbref(#4);edit:dbref(#5) or perm(Wizards);examine:perm(Builders);delete:perm(Wizards);get:all()"
|
||||
#obj1.lock_storage = "cmd:all();admin:id(1);listen:all();send:all()"
|
||||
#obj1.lock_storage = "cmd:all();admin:id(1);listen:all();send:all()"
|
||||
obj1.lock_storage = "listen:perm(Immortals)"
|
||||
|
||||
pdb.set_trace()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue