Added FuncKeys - locking of objects depending on the result from an arbitrary function.

Added AttrKeys and FlagKeys - locks depending on the value of the unlocker's attribute or if they have a flag set.
Finally I updated @lock to handle all these lock types. There are a large amount of possible configurations though, so more testing is needed.
/Griatch
This commit is contained in:
Griatch 2009-10-12 20:58:15 +00:00
parent 42254355a0
commit 46e2cd3ecb
4 changed files with 225 additions and 89 deletions

View file

@ -3,15 +3,12 @@ Generic command module. Pretty much every command should go here for
now. now.
""" """
import time import time
from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from src.config.models import ConfigValue from src.config.models import ConfigValue
from src.helpsys.models import HelpEntry from src.helpsys.models import HelpEntry
from src.objects.models import Object
from src.ansi import ANSITable from src.ansi import ANSITable
from src import defines_global from src import defines_global
from src import session_mgr from src import session_mgr
from src import ansi
from src.util import functions_general from src.util import functions_general
import src.helpsys.management.commands.edit_helpfiles as edit_help import src.helpsys.management.commands.edit_helpfiles as edit_help
from src.cmdtable import GLOBAL_CMD_TABLE from src.cmdtable import GLOBAL_CMD_TABLE

View file

@ -9,7 +9,6 @@ from src import locks
from src import ansi from src import ansi
from src.cmdtable import GLOBAL_CMD_TABLE from src.cmdtable import GLOBAL_CMD_TABLE
from src import defines_global from src import defines_global
from src import logger
def cmd_teleport(command): def cmd_teleport(command):
""" """
@ -186,7 +185,7 @@ def cmd_set(command):
source_object.emit_to("Set what?") source_object.emit_to("Set what?")
return return
target_name = eq_args[0].strip() target_name = eq_args[0].strip()
target = source_object.search_for_object(eq_args[0]) target = source_object.search_for_object(target_name)
# Use search_for_object to handle duplicate/nonexistant results. # Use search_for_object to handle duplicate/nonexistant results.
if not target: if not target:
return return
@ -926,7 +925,7 @@ def cmd_dig(command):
arg_list = args.split("=",1) arg_list = args.split("=",1)
if len(arg_list) < 2: if len(arg_list) < 2:
#just create a room, no exits #just create a room, no exits
room_name = largs[0].strip() room_name = arg_list[0].strip()
else: else:
#deal with args left of = #deal with args left of =
larg = arg_list[0] larg = arg_list[0]
@ -1264,7 +1263,18 @@ def cmd_lock(command):
- a user #dbref (#2, #45 etc) - a user #dbref (#2, #45 etc)
- a Group name (Builder, Immortal etc, case sensitive) - a Group name (Builder, Immortal etc, case sensitive)
- a Permission string (genperms.get, etc) - a Permission string (genperms.get, etc)
If no keys are given, the object is locked for everyone. - a Function():return_value pair. (ex: alliance():Red). The
function() is called on the locked object (if it exists) and
if its return value matches the Key is passed. If no
return_value is given, matches against True.
- an Attribute:return_value pair (ex: key:yellow_key). The
Attribute is the name of an attribute defined on the locked
object. If this attribute has a value matching return_value,
the lock is passed. If no return_value is given, both
attributes and flags will be searched, requiring a True
value.
If no keys at all are given, the object is locked for everyone.
When the lock blocks a user, you may customize which error is given by When the lock blocks a user, you may customize which error is given by
storing error messages in an attribute. For DefaultLocks, UseLocks and storing error messages in an attribute. For DefaultLocks, UseLocks and
@ -1360,33 +1370,66 @@ def cmd_lock(command):
source_object.emit_to("Added impassable '%s' lock to %s." % (ltype, obj.get_name())) source_object.emit_to("Added impassable '%s' lock to %s." % (ltype, obj.get_name()))
else: else:
keys = [k.strip() for k in keys.split(",")] keys = [k.strip() for k in keys.split(",")]
okeys, gkeys, pkeys = [], [], [] obj_keys, group_keys, perm_keys = [], [], []
func_keys, attr_keys, flag_keys = [], [], []
allgroups = [g.name for g in Group.objects.all()] allgroups = [g.name for g in Group.objects.all()]
allperms = ["%s.%s" % (p.content_type.app_label, p.codename) for p in Permission.objects.all()] allperms = ["%s.%s" % (p.content_type.app_label, p.codename)
for p in Permission.objects.all()]
for key in keys: for key in keys:
#differentiate different type of keys #differentiate different type of keys
if Object.objects.is_dbref(key): if Object.objects.is_dbref(key):
okeys.append(key) # this is an object key, like #2, #6 etc
obj_keys.append(key)
elif key in allgroups: elif key in allgroups:
gkeys.append(key) # a group key
group_keys.append(key)
elif key in allperms: elif key in allperms:
pkeys.append(key) # a permission string
perm_keys.append(key)
elif '()' in key:
# a function()[:returnvalue] tuple.
# Check if we also request a return value
funcname, rvalue = [k.strip() for k in key.split('()',1)]
if not funcname:
funcname = "lock_func"
rvalue = rvalue.lstrip(':')
if not rvalue:
rvalue = True
# pack for later adding.
func_keys.append((funcname, rvalue))
elif ':' in key:
# an attribute/flag[:returnvalue] tuple.
attr_name, rvalue = [k.strip() for k in key.split(':',1)]
if not rvalue:
# if return value is not set, also search for a key.
rvalue = True
flag_keys.append(attr_name)
# pack for later adding
attr_keys.append((attr_name, rvalue))
else: else:
source_object.emit_to("Key '%s' is not recognized as a valid dbref, group or permission." % key) source_object.emit_to("Key '%s' is not recognized as a valid dbref, group or permission." % key)
return return
# Create actual key objects from the respective lists # Create actual key objects from the respective lists
keys = [] keys = []
if okeys: if obj_keys:
keys.append(locks.ObjKey(okeys)) keys.append(locks.ObjKey(obj_keys))
if gkeys: if group_keys:
keys.append(locks.GroupKey(gkeys)) keys.append(locks.GroupKey(group_keys))
if pkeys: if perm_keys:
keys.append(locks.PermKey(pkeys)) keys.append(locks.PermKey(perm_keys))
if func_keys:
keys.append(locks.FuncKey(func_keys, obj.dbref()))
if attr_keys:
keys.append(locks.AttrKey(attr_keys))
if flag_keys:
keys.append(locks.FlagKey(flag_keys))
#store the keys in the lock #store the keys in the lock
obj_locks.add_type(ltype, keys) obj_locks.add_type(ltype, keys)
kstring = "" kstring = " "
for key in keys: for key in keys:
kstring += " %s" % key kstring += " %s," % key
kstring = kstring[:-1]
source_object.emit_to("Added lock '%s' to %s with keys%s." % (ltype, obj.get_name(), kstring)) source_object.emit_to("Added lock '%s' to %s with keys%s." % (ltype, obj.get_name(), kstring))
obj.set_attribute("LOCKS",obj_locks) obj.set_attribute("LOCKS",obj_locks)

View file

@ -5,9 +5,8 @@ A lock object contains a set of criteria (keys). When queried, the
lock tries the tested object/player against these criteria and returns lock tries the tested object/player against these criteria and returns
a True/False result. a True/False result.
""" """
import traceback
from src.objects.models import Object from src.objects.models import Object
from src import logger
class Key(object): class Key(object):
""" """
@ -17,51 +16,53 @@ class Key(object):
the entire key is considered a match. With the 'exact' criterion, all criteria the entire key is considered a match. With the 'exact' criterion, all criteria
contained in the key (except the list of object dbrefs) must also exist in the contained in the key (except the list of object dbrefs) must also exist in the
object. object.
With the NOT flag the key is inversed, that is, only objects which do not With the invert_result flag the key is inversed, that is, only objects which do not
match the criterias (exact or not) will be considered to have access. match the criteria (exact or not) will be considered to have access.
Supplying no criteria will make the lock impassable (NOT flag results in an alvays open lock) Supplying no criteria will make the lock impassable (invert_result flag results in an alvays open lock)
""" """
def __init__(self, criteria=[], extra=[], NOT=False, exact=False): def __init__(self, criteria=[], extra=None, invert_result=False, exact=False):
""" """
Defines the basic permission laws Defines the basic permission laws
permlist (list of strings) - permission definitions permlist (list of strings) - permission definitions
grouplist (list of strings) - group names grouplist (list of strings) - group names
objlist (list of obj or dbrefs) - match individual objects to the lock objlist (list of obj or dbrefs) - match individual objects to the lock
NOT (bool) - invert the lock invert_result (bool) - invert the lock
exact (bool) - objects must match all criteria. Default is OR operation. exact (bool) - objects must match all criteria. Default is OR operation.
""" """
self.criteria = criteria self.criteria = criteria
self.extra = extra self.extra = extra
#set the boolean operators #set the boolean operators
self.NOT = not NOT self.invert_result = not invert_result
self.exact = exact self.exact = exact
# if we have no criteria, this is an impassable lock (or always open if NOT given). # if we have no criteria, this is an impassable lock
# (or always open if invert_result given).
self.impassable = not(criteria) self.impassable = not(criteria)
def __str__(self): def __str__(self):
s = "" string = " "
if not self.criteria: if not self.criteria:
s += " <Impassable>" string += " <Impassable>"
for crit in self.criteria: for crit in self.criteria:
s += " <%s>" % crit string += " %s," % crit
return s.strip() return string[:-1].strip()
def _result(self, result): def _result(self, result):
"Return result depending on exact criterion."
if self.exact: if self.exact:
result = result == len(self.criteria) result = result == len(self.criteria)
if result: if result:
return self.NOT return self.invert_result
else: else:
return not self.NOT return not self.invert_result
def check(self, object): def check(self, obj):
""" """
Compare the object to see if the key matches. Compare the object to see if the key matches.
""" """
if self.NOT: if self.invert_result:
return not self.impassable return not self.impassable
return self.impassable return self.impassable
@ -69,70 +70,149 @@ class ObjKey(Key):
""" """
This implements a Key matching against object id This implements a Key matching against object id
""" """
def check(self, object): def check(self, obj):
"Checks object against the key."
if self.impassable: if self.impassable:
return self.NOT return self.invert_result
if object.dbref() in self.criteria: if obj.dbref() in self.criteria:
return self.NOT return self.invert_result
else: else:
return not self.NOT return not self.invert_result
class GroupKey(Key): class GroupKey(Key):
""" """
This key matches against group membership This key matches against group membership
""" """
def check(self, object): def check(self, obj):
if self.impassable: return self.NOT "Checks object against the key."
user = object.get_user_account() if self.impassable:
if not user: return False return self.invert_result
return self._result(len([g for g in user.groups.all() if str(g) in self.criteria])) user = obj.get_user_account()
if not user:
return False
return self._result(len([g for g in user.groups.all()
if str(g) in self.criteria]))
class PermKey(Key): class PermKey(Key):
""" """
This key matches against permissions This key matches against permissions
""" """
def check(self, object): def check(self, obj):
if self.impassable: return self.NOT "Checks object against the key."
user = object.get_user_account() if self.impassable:
if not user: return False return self.invert_result
return self._result(len([p for p in self.criteria if object.has_perm(p)])) user = obj.get_user_account()
if not user:
return False
return self._result(len([p for p in self.criteria
if obj.has_perm(p)]))
class FlagKey(Key): class FlagKey(Key):
""" """
This key use a set of object flagss to define access. This key use a set of object flags to define access.
Only if the trying object has the correct flags will
it pass the lock.
self.criterion holds the flag names
""" """
def check(self, object): def __str__(self):
if self.impassable: return self.NOT string = " "
return self._result(len([f for f in self.criteria if object.has_flag(f)])) if not self.criteria:
string += " <Impassable>"
for crit in self.criteria:
string += " obj.%s," % str(crit).upper()
return string[:-1].strip()
def check(self, obj):
"Checks object against the key."
if self.impassable:
return self.invert_result
return self._result(len([f for f in self.criteria
if obj.has_flag(f)]))
class AttrKey(Key): class AttrKey(Key):
""" """
This key use a list of arbitrary attributes to define access. This key use a list of arbitrary attributes to define access.
The attribute names are in the usual criteria. If there is a matching self.criteria contains a list of tuples [(attrname, value),...].
list of values in the self.extra list we compare the values directly, The attribute with the given name must match the given value in
otherwise we just check for the existence of the attribute. order to pass the lock.
""" """
def check(self, object): def __str__(self):
if self.impassable: return self.NOT string = " "
val_list = self.extra if not self.criteria:
attr_list = self.criteria string += " <Impassable>"
if len(val_list) == len(attr_list): for crit in self.criteria:
return self._result(len([i for i in range(attr_list) string += " obj.%s=%s," % (crit[0],crit[1])
if object.get_attribute_value(attr_list[i])==val_list[i]])) return string[:-1].strip()
else:
return _result(len([a for a in attr_list if object.get_attribute_value(a)])) def check(self, obj):
"Checks object against the key."
if self.impassable:
return self.invert_result
return self._result(len([tup for tup in self.criteria
if len(tup)>1 and
obj.get_attribute_value(tup[0]) == tup[1]]))
class FuncKey(Key):
"""
This Key stores a set of function names and return values. The matching
of those return values depend on the function (defined on the locked object) to
return a matching value to the one stored in the key
The relevant data is stored in the key in this format:
self.criteria = list of (funcname, return_value) tuples, where funcname
is a function to be called on the locked object.
This function func(obj) takes the calling object
as argument and only
if its return value matches the one set in the tuple
will the lock be passed. Note that the return value
can, in the case of locks set with @lock, only be
a string, so in the comparison we do a string
conversion of the return values.
self.index contains the locked object's dbref.
"""
def __str__(self):
string = ""
if not self.criteria:
string += " <Impassable>"
for crit in self.criteria:
string += " lockobj.%s(obj) => %s" % (crit[0],crit[1])
return string.strip()
def check(self, obj):
"Checks object against the stored locks."
if self.impassable:
return self.invert_result
# we need the locked object since the lock-function is defined on it.
lock_obj = Object.objects.dbref_search(self.extra)
if not lock_obj:
return self.invert_result
# build tuples of functions and their return values
ftuple_list = [(getattr(lock_obj.scriptlink, tup[0], None),
tup[1]) for tup in self.criteria
if len(tup) > 1]
# loop through the comparisons. Convert to strings before
# doing the comparison.
return self._result(len([ftup for ftup in ftuple_list
if callable(ftup[0]) and
str(ftup[0](obj)) == str(ftup[1])]))
class Locks(object): class Locks(object):
""" """
The Locks object defines an overall grouping of Locks based after type. Each lock The Locks object defines an overall grouping of Locks based after type.
contains a set of keys to limit access to a certain action. The Locks object is stored in the reserved attribute LOCKS on the locked object.
The Lock object is stored in the attribute LOCKS on the object in question and the Each Locks instance stores a set of keys for each Lock type, normally
engine queries it during the relevant situations. created using the @lock command in-game. The engine queries Locks.check()
with an object as argument in order to determine if the object has access.
Below is a list copied from MUX. Currently Evennia only use 3 lock types: Below is a list of Lock-types copied from MUX. Currently Evennia only use
Default, Use and Enter; it's not clear if any more are really needed. 3 lock types: Default, Use and Enter; it's not clear if any more are really
needed.
Name: Affects: Effect: Name: Affects: Effect:
----------------------------------------------------------------------- -----------------------------------------------------------------------
@ -192,10 +272,10 @@ class Locks(object):
self.locks = {} self.locks = {}
def __str__(self): def __str__(self):
s = "" string = ""
for lock in self.locks.keys(): for lock in self.locks.keys():
s += " %s" % lock string += " %s" % lock
return s.strip() return string.strip()
def add_type(self, ltype, keys=[]): def add_type(self, ltype, keys=[]):
""" """
@ -206,27 +286,32 @@ class Locks(object):
keys = [keys] keys = [keys]
self.locks[ltype] = keys self.locks[ltype] = keys
def del_type(self,ltype): def del_type(self, ltype):
""" """
Clears a lock. Clears a lock.
""" """
if self.has_type(ltype): if self.has_type(ltype):
del self.locks[ltype] del self.locks[ltype]
def has_type(self,ltype): def has_type(self, ltype):
"Checks if LockType ltype exists in the lock."
return self.locks.has_key(ltype) return self.locks.has_key(ltype)
def show(self): def show(self):
"""
Displays a fancier view of the stored locks and their keys.
"""
if not self.locks: if not self.locks:
return "No locks." return "No locks."
s = "" string = " "
for lock, keys in self.locks.items(): for lock, keys in self.locks.items():
s += "\n %s\n " % lock string += "\n %s\n " % lock
for key in keys: for key in keys:
s += " %s" % key string += " %s," % key
return s string = string[:-1]
return string
def check(self, ltype, object): def check(self, ltype, obj):
""" """
This is called by the engine. It checks if this lock is of the right type, This is called by the engine. It checks if this lock is of the right type,
and if so if there is access. If the type does not exist, there is no and if so if there is access. If the type does not exist, there is no
@ -237,10 +322,10 @@ class Locks(object):
result = False result = False
for key in self.locks[ltype]: for key in self.locks[ltype]:
try: try:
result = result or key.check(object) result = result or key.check(obj)
except KeyError: except KeyError:
pass pass
if not result and object.is_superuser(): if not result and obj.is_superuser():
object.emit_to("Lock '%s' - Superuser override." % ltype) obj.emit_to("Lock '%s' - Superuser override." % ltype)
return True return True
return result return result

View file

@ -235,3 +235,14 @@ class EvenniaBasicObject(object):
return locks.check("EnterLock", pobject) return locks.check("EnterLock", pobject)
else: else:
return True return True
def lock_func(self, obj):
"""
This is a custom function called by locks with the FuncKey key. Its
return value should match that specified in the lock (so no true/false
lock result is actually determined in here). Default desired return
value is True. Also remember that the comparison in FuncKey is made
using the string representation of the return value, since @lock can
only define string lock criteria.
"""
return False