From 46e2cd3ecb3801f6d78725c473589821939af521 Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 12 Oct 2009 20:58:15 +0000 Subject: [PATCH] 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 --- src/commands/general.py | 3 - src/commands/objmanip.py | 79 ++++++++--- src/locks.py | 221 +++++++++++++++++++++--------- src/script_parents/basicobject.py | 11 ++ 4 files changed, 225 insertions(+), 89 deletions(-) diff --git a/src/commands/general.py b/src/commands/general.py index 5e0b620d1d..d8f78557f8 100644 --- a/src/commands/general.py +++ b/src/commands/general.py @@ -3,15 +3,12 @@ Generic command module. Pretty much every command should go here for now. """ import time -from django.conf import settings from django.contrib.auth.models import User from src.config.models import ConfigValue from src.helpsys.models import HelpEntry -from src.objects.models import Object from src.ansi import ANSITable from src import defines_global from src import session_mgr -from src import ansi from src.util import functions_general import src.helpsys.management.commands.edit_helpfiles as edit_help from src.cmdtable import GLOBAL_CMD_TABLE diff --git a/src/commands/objmanip.py b/src/commands/objmanip.py index c36377c63d..c5f8475522 100644 --- a/src/commands/objmanip.py +++ b/src/commands/objmanip.py @@ -9,7 +9,6 @@ from src import locks from src import ansi from src.cmdtable import GLOBAL_CMD_TABLE from src import defines_global -from src import logger def cmd_teleport(command): """ @@ -186,7 +185,7 @@ def cmd_set(command): source_object.emit_to("Set what?") return 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. if not target: return @@ -926,7 +925,7 @@ def cmd_dig(command): arg_list = args.split("=",1) if len(arg_list) < 2: #just create a room, no exits - room_name = largs[0].strip() + room_name = arg_list[0].strip() else: #deal with args left of = larg = arg_list[0] @@ -1264,7 +1263,18 @@ def cmd_lock(command): - a user #dbref (#2, #45 etc) - a Group name (Builder, Immortal etc, case sensitive) - 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 storing error messages in an attribute. For DefaultLocks, UseLocks and @@ -1298,7 +1308,7 @@ def cmd_lock(command): switches = command.command_switches if not arg: - source_object.emit_to("Usage: @lock[/switch] [:type] [= [,key2,key3,...]]") + source_object.emit_to("Usage: @lock[/switch] [:type] [= [,key2,key3,...]]") return keys = "" #deal with all possible arguments. @@ -1360,33 +1370,66 @@ def cmd_lock(command): source_object.emit_to("Added impassable '%s' lock to %s." % (ltype, obj.get_name())) else: 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()] - 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: #differentiate different type of keys 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: - gkeys.append(key) + # a group key + group_keys.append(key) 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: source_object.emit_to("Key '%s' is not recognized as a valid dbref, group or permission." % key) return # Create actual key objects from the respective lists keys = [] - if okeys: - keys.append(locks.ObjKey(okeys)) - if gkeys: - keys.append(locks.GroupKey(gkeys)) - if pkeys: - keys.append(locks.PermKey(pkeys)) + if obj_keys: + keys.append(locks.ObjKey(obj_keys)) + if group_keys: + keys.append(locks.GroupKey(group_keys)) + if perm_keys: + 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 obj_locks.add_type(ltype, keys) - kstring = "" + kstring = " " 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)) obj.set_attribute("LOCKS",obj_locks) diff --git a/src/locks.py b/src/locks.py index 2fb3b6b289..3ab8ce2721 100644 --- a/src/locks.py +++ b/src/locks.py @@ -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 a True/False result. """ -import traceback + from src.objects.models import Object -from src import logger class Key(object): """ @@ -17,51 +16,53 @@ class Key(object): 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 object. - With the NOT flag the key is inversed, that is, only objects which do not - match the criterias (exact or not) will be considered to have access. + With the invert_result flag the key is inversed, that is, only objects which do not + 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 permlist (list of strings) - permission definitions grouplist (list of strings) - group names 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. """ self.criteria = criteria self.extra = extra #set the boolean operators - self.NOT = not NOT + self.invert_result = not invert_result 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) def __str__(self): - s = "" + string = " " if not self.criteria: - s += " " + string += " " for crit in self.criteria: - s += " <%s>" % crit - return s.strip() + string += " %s," % crit + return string[:-1].strip() def _result(self, result): + "Return result depending on exact criterion." if self.exact: result = result == len(self.criteria) if result: - return self.NOT + return self.invert_result 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. """ - if self.NOT: + if self.invert_result: return not self.impassable return self.impassable @@ -69,70 +70,149 @@ class ObjKey(Key): """ This implements a Key matching against object id """ - def check(self, object): - + def check(self, obj): + "Checks object against the key." if self.impassable: - return self.NOT - if object.dbref() in self.criteria: - return self.NOT + return self.invert_result + if obj.dbref() in self.criteria: + return self.invert_result else: - return not self.NOT + return not self.invert_result class GroupKey(Key): """ This key matches against group membership """ - def check(self, object): - if self.impassable: return self.NOT - user = object.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])) + def check(self, obj): + "Checks object against the key." + if self.impassable: + return self.invert_result + 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): """ This key matches against permissions """ - def check(self, object): - if self.impassable: return self.NOT - user = object.get_user_account() - if not user: return False - return self._result(len([p for p in self.criteria if object.has_perm(p)])) + def check(self, obj): + "Checks object against the key." + if self.impassable: + return self.invert_result + 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): """ - 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): - if self.impassable: return self.NOT - return self._result(len([f for f in self.criteria if object.has_flag(f)])) + def __str__(self): + string = " " + if not self.criteria: + string += " " + 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): """ This key use a list of arbitrary attributes to define access. - The attribute names are in the usual criteria. If there is a matching - list of values in the self.extra list we compare the values directly, - otherwise we just check for the existence of the attribute. + self.criteria contains a list of tuples [(attrname, value),...]. + The attribute with the given name must match the given value in + order to pass the lock. """ - def check(self, object): - if self.impassable: return self.NOT - val_list = self.extra - attr_list = self.criteria - if len(val_list) == len(attr_list): - return self._result(len([i for i in range(attr_list) - if object.get_attribute_value(attr_list[i])==val_list[i]])) - else: - return _result(len([a for a in attr_list if object.get_attribute_value(a)])) + def __str__(self): + string = " " + if not self.criteria: + string += " " + for crit in self.criteria: + string += " obj.%s=%s," % (crit[0],crit[1]) + return string[:-1].strip() + + 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 += " " + 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): """ - The Locks object defines an overall grouping of Locks based after type. Each lock - contains a set of keys to limit access to a certain action. - The Lock object is stored in the attribute LOCKS on the object in question and the - engine queries it during the relevant situations. + The Locks object defines an overall grouping of Locks based after type. + The Locks object is stored in the reserved attribute LOCKS on the locked object. + Each Locks instance stores a set of keys for each Lock type, normally + 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: - Default, Use and Enter; it's not clear if any more are really needed. + Below is a list of Lock-types copied from MUX. Currently Evennia only use + 3 lock types: Default, Use and Enter; it's not clear if any more are really + needed. Name: Affects: Effect: ----------------------------------------------------------------------- @@ -192,10 +272,10 @@ class Locks(object): self.locks = {} def __str__(self): - s = "" + string = "" for lock in self.locks.keys(): - s += " %s" % lock - return s.strip() + string += " %s" % lock + return string.strip() def add_type(self, ltype, keys=[]): """ @@ -206,27 +286,32 @@ class Locks(object): keys = [keys] self.locks[ltype] = keys - def del_type(self,ltype): + def del_type(self, ltype): """ Clears a lock. """ if self.has_type(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) def show(self): + """ + Displays a fancier view of the stored locks and their keys. + """ if not self.locks: return "No locks." - s = "" + string = " " for lock, keys in self.locks.items(): - s += "\n %s\n " % lock + string += "\n %s\n " % lock for key in keys: - s += " %s" % key - return s + string += " %s," % key + 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, 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 for key in self.locks[ltype]: try: - result = result or key.check(object) + result = result or key.check(obj) except KeyError: pass - if not result and object.is_superuser(): - object.emit_to("Lock '%s' - Superuser override." % ltype) + if not result and obj.is_superuser(): + obj.emit_to("Lock '%s' - Superuser override." % ltype) return True return result diff --git a/src/script_parents/basicobject.py b/src/script_parents/basicobject.py index 2ddb6146f4..3791af5237 100644 --- a/src/script_parents/basicobject.py +++ b/src/script_parents/basicobject.py @@ -235,3 +235,14 @@ class EvenniaBasicObject(object): return locks.check("EnterLock", pobject) else: 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