OBS: You'll need to resync/rebuild your database!

- This implements an updated, clearer and more robust access system. The policy is now to lock that which is not explicitly left open.
- Permission strings -> Lock strings. Separating permissions and locks makes more sense security-wise
- No more permissiongroup table; permissions instead use a simple tuple PERMISSIONS_HIERARCHY to define an access hierarchy
- Cleaner lock-definition syntax, all based on function calls.
- New objects/players/channels get a default security policy during creation (set through typeclass)

As part of rebuilding and testing the new lock/permission system I got into testing and debugging several other systems, fixing some
outstanding issues:
- @reload now fully updates the database asynchronously. No need to reboot server when changing cmdsets
- Dozens of new test suites added for about 30 commands so far
- Help for channels made more clever and informative.
This commit is contained in:
Griatch 2011-03-15 16:08:32 +00:00
parent c2030c2c0c
commit 08b3de9e5e
49 changed files with 1714 additions and 1877 deletions

View file

@ -10,7 +10,6 @@ See src/commands/default/muxcommand.py for an example.
from src.commands.command import Command as BaseCommand
from src.commands.default.muxcommand import MuxCommand as BaseMuxCommand
from src.permissions import permissions
from src.utils import utils
class MuxCommand(BaseMuxCommand):
@ -41,7 +40,7 @@ class MuxCommand(BaseMuxCommand):
cmdhandler):
self.key - the name of this command ('look')
self.aliases - the aliases of this cmd ('l')
self.permissions - permission string for this command
self.locks - lock definition for this command, usually cmd:<func>
self.help_category - overall category of command
self.caller - the object calling this command
@ -105,7 +104,7 @@ class Command(BaseCommand):
used by Evennia to create the automatic help entry for
the command, so make sure to document consistently here.
"""
def has_perm(self, srcobj):
def access(self, srcobj):
"""
This is called by the cmdhandler to determine
if srcobj is allowed to execute this command. This
@ -114,7 +113,7 @@ class Command(BaseCommand):
By default, We use checks of the 'cmd' type of lock to determine
if the command should be run.
"""
return permissions.has_perm(srcobj, self, 'cmd')
return super(Command, self).access(srcobj)
def at_pre_cmd(self):
"""

View file

@ -91,7 +91,7 @@ class Object(BaseObject):
"""
pass
class Character(BaseCharacter):
"""
This is the default object created for a new user connecting - the
@ -103,7 +103,6 @@ class Character(BaseCharacter):
def at_object_creation(self):
# This adds the default cmdset to the player every time they log
# in. Don't change this unless you really know what you are doing.
#self.scripts.add(scripts.AddDefaultCmdSet)
super(Character, self).at_object_creation()
# expand with whatever customizations you want below...
@ -147,6 +146,7 @@ class Exit(BaseExit):
clean up a bit after themselves though, easiest accomplished
by letting by_object_delete() call the object's parent.
"""
def at_object_delete(self):
"""
The game needs to do some cache cleanups when deleting an exit,
@ -158,7 +158,6 @@ class Exit(BaseExit):
# custom modifications below.
# ...
class Player(BasePlayer):
"""
This class describes the actual OOC player (i.e. the user connecting

View file

@ -338,7 +338,7 @@ def cmdhandler(caller, raw_string, unloggedin=False, testing=False):
cmd_candidate, cmd = matches[0]
# Check so we have permission to use this command.
if not cmd.has_perm(caller):
if not cmd.access(caller):
cmd = cmdset.get(CMD_NOPERM)
if cmd:
sysarg = raw_string

View file

@ -28,9 +28,11 @@ class CmdSetMeta(type):
# by default we key the cmdset the same as the
# name of its class.
mcs.key = mcs.__name__
mcs.path = "%s.%s" % (mcs.__module__, mcs.__name__)
if not type(mcs.key_mergetypes) == dict:
mcs.key_mergetypes = {}
super(CmdSetMeta, mcs).__init__(*args, **kwargs)

View file

@ -99,7 +99,6 @@ def import_cmdset(python_path, cmdsetobj, emit_to_obj=None, no_logging=False):
module = __import__(modulepath, fromlist=[True])
cmdsetclass = module.__dict__[classname]
CACHED_CMDSETS[wanted_cache_key] = cmdsetclass
#print "cmdset %s found." % wanted_cache_key
#instantiate the cmdset (and catch its errors)
if callable(cmdsetclass):
cmdsetclass = cmdsetclass(cmdsetobj)
@ -362,3 +361,21 @@ class CmdSetHandler(object):
self.cmdset_stack = [self.cmdset_stack[0]]
self.mergetype_stack = [self.cmdset_stack[0].mergetype]
self.update()
def reset(self):
"""
Force reload of all cmdsets in handler. This should be called
after CACHED_CMDSETS have been cleared (normally by @reload).
"""
new_cmdset_stack = []
new_mergetype_stack = []
for cmdset in self.cmdset_stack:
if cmdset.key == "Empty":
new_cmdset_stack.append(cmdset)
new_mergetype_stack.append("Union")
else:
new_cmdset_stack.append(self.import_cmdset(cmdset.path))
new_mergetype_stack.append(cmdset.mergetype)
self.cmdset_stack = new_cmdset_stack
self.mergetype_stack = new_mergetype_stack
self.update()

View file

@ -5,7 +5,7 @@ All commands in Evennia inherit from the 'Command' class in this module.
"""
from src.permissions import permissions
from src.locks.lockhandler import LockHandler
from src.utils.utils import is_iter
class CommandMeta(type):
@ -17,18 +17,26 @@ class CommandMeta(type):
"""
Simply make sure all data are stored as lowercase and
do checking on all properties that should be in list form.
Sets up locks to be more forgiving.
"""
mcs.key = mcs.key.lower()
if mcs.aliases and not is_iter(mcs.aliases):
mcs.aliases = mcs.aliases.split(',')
mcs.aliases = [str(alias).strip().lower() for alias in mcs.aliases]
if mcs.permissions and not is_iter(mcs.permissions) :
mcs.permissions = mcs.permissions.split(',')
mcs.permissions = [str(perm).strip().lower() for perm in mcs.permissions]
# pre-process locks as defined in class definition
temp = []
if hasattr(mcs, 'permissions'):
mcs.locks = mcs.permissions
for lockstring in mcs.locks.split(';'):
if lockstring and not ':' in lockstring:
lockstring = "cmd:%s" % lockstring
temp.append(lockstring)
mcs.lock_storage = ";".join(temp)
mcs.help_category = mcs.help_category.lower()
super(CommandMeta, mcs).__init__(*args, **kwargs)
# The Command class is the basic unit of an Evennia command; when
# defining new commands, the admin subclass this class and
# define their own parser method to handle the input. The
@ -69,15 +77,17 @@ class Command(object):
key = "command"
# alternative ways to call the command (e.g. 'l', 'glance', 'examine')
aliases = []
# a list of permission strings or comma-separated string limiting
# access to this command.
permissions = []
# a list of lock definitions on the form cmd:[NOT] func(args) [ AND|OR][ NOT] func2(args)
locks = ""
# used by the help system to group commands in lists.
help_category = "general"
# There is also the property 'obj'. This gets set by the system
# on the fly to tie this particular command to a certain in-game entity.
# self.obj should NOT be defined here since it will not be overwritten
# if it already exists.
def __init__(self):
self.lockhandler = LockHandler(self)
def __str__(self):
"Print the command"
@ -115,15 +125,15 @@ class Command(object):
"""
return (cmdname == self.key) or (cmdname in self.aliases)
def has_perm(self, srcobj):
def access(self, srcobj, access_type="cmd", default=False):
"""
This hook is called by the cmdhandler to determine if srcobj
is allowed to execute this command. It should return a boolean
value and is not normally something that need to be changed since
it's using the Evennia permission system directly.
"""
return permissions.has_perm(srcobj, self, 'cmd')
return self.lockhandler.check(srcobj, access_type, default=default)
# Common Command hooks
def at_pre_cmd(self):

View file

@ -8,8 +8,6 @@ from django.conf import settings
from django.contrib.auth.models import User
from src.players.models import PlayerDB
from src.server.sessionhandler import SESSIONS
from src.permissions.permissions import has_perm, has_perm_string
from src.permissions.models import PermissionGroup
from src.utils import utils
from src.commands.default.muxcommand import MuxCommand
@ -29,7 +27,7 @@ class CmdBoot(MuxCommand):
"""
key = "@boot"
permissions = "cmd:boot"
locks = "cmd:perm(boot) or perm(Wizard)"
help_category = "Admin"
def func(self):
@ -60,7 +58,7 @@ class CmdBoot(MuxCommand):
if not pobj:
return
if pobj.character.has_player:
if not has_perm(caller, pobj, 'can_boot'):
if not pobj.access(caller, 'boot'):
string = "You don't have the permission to boot %s."
pobj.msg(string)
return
@ -107,7 +105,7 @@ class CmdDelPlayer(MuxCommand):
"""
key = "@delplayer"
permissions = "cmd:delplayer"
locks = "cmd:perm(delplayer) or perm(Immortals)"
help_category = "Admin"
def func(self):
@ -149,7 +147,7 @@ class CmdDelPlayer(MuxCommand):
except Exception:
player = None
if not has_perm_string(caller, 'manage_players'):
if player and not player.access(caller, 'delete'):
string = "You don't have the permissions to delete this player."
caller.msg(string)
return
@ -166,20 +164,19 @@ class CmdDelPlayer(MuxCommand):
caller.msg(string)
return
elif len(players) > 1:
string = "There where multiple matches:"
elif utils.is_iter(players):
string = "There were multiple matches:"
for player in players:
string += "\n %s %s" % (player.id, player.key)
return
else:
# one single match
player = players[0]
player = players
user = player.user
character = player.character
if not has_perm(caller, player, 'manage_players'):
if not player.access(caller, 'delete'):
string = "You don't have the permissions to delete that player."
caller.msg(string)
return
@ -209,7 +206,7 @@ class CmdEmit(MuxCommand):
@pemit [<obj>, <obj>, ... =] <message>
Switches:
room : limit emits to rooms only
room : limit emits to rooms only (default)
players : limit emits to players only
contents : send to the contents of matched objects too
@ -221,7 +218,7 @@ class CmdEmit(MuxCommand):
"""
key = "@emit"
aliases = ["@pemit", "@remit"]
permissions = "cmd:emit"
locks = "cmd:perm(emit) or perm(Builders)"
help_category = "Admin"
def func(self):
@ -266,7 +263,7 @@ class CmdEmit(MuxCommand):
if players_only and not obj.has_player:
caller.msg("%s has no active player. Ignored." % objname)
continue
if has_perm(caller, obj, 'send_to'):
if obj.access(caller, 'tell'):
obj.msg(message)
if send_to_contents:
for content in obj.contents:
@ -290,7 +287,7 @@ class CmdNewPassword(MuxCommand):
"""
key = "@userpassword"
permissions = "cmd:newpassword"
locks = "cmd:perm(newpassword) or perm(Wizards)"
help_category = "Admin"
def func(self):
@ -303,14 +300,13 @@ class CmdNewPassword(MuxCommand):
return
# the player search also matches 'me' etc.
character = caller.search("*%s" % self.lhs, global_search=True)
if not character:
player = caller.search("*%s" % self.lhs, global_search=True)
if not player:
return
player = character.player
player.user.set_password(self.rhs)
player.user.save()
caller.msg("%s - new password set to '%s'." % (player.name, self.rhs))
if character != caller:
if player.character != caller:
player.msg("%s has changed your password to '%s'." % (caller.name, self.rhs))
@ -333,7 +329,7 @@ class CmdPerm(MuxCommand):
"""
key = "@perm"
aliases = "@setperm"
permissions = "cmd:perm"
locks = "cmd:perm(perm) or perm(Immortals)"
help_category = "Admin"
def func(self):
@ -434,7 +430,7 @@ class CmdPuppet(MuxCommand):
"""
key = "@puppet"
permissions = "cmd:puppet"
locks = "cmd:perm(puppet) or perm(Builders)"
help_category = "Admin"
def func(self):
@ -453,6 +449,8 @@ class CmdPuppet(MuxCommand):
if not utils.inherits_from(new_character, settings.BASE_CHARACTER_TYPECLASS):
caller.msg("%s is not a Character." % self.args)
return
if new_character.player:
caller.msg("This character is already under the control of a player.")
if player.swap_character(new_character):
new_character.msg("You now control %s." % new_character.name)
else:
@ -468,7 +466,7 @@ class CmdWall(MuxCommand):
Announces a message to all connected players.
"""
key = "@wall"
permissions = "cmd:wall"
locks = "cmd:perm(wall) or perm(Wizards)"
help_category = "Admin"
def func(self):

View file

@ -193,7 +193,7 @@ class CmdBatchCommands(MuxCommand):
"""
key = "@batchcommands"
aliases = ["@batchcommand", "@batchcmd"]
permissions = "cmd:batchcommands"
locks = "cmd:perm(batchcommands)"
help_category = "Building"
def func(self):
@ -277,7 +277,7 @@ class CmdBatchCode(MuxCommand):
"""
key = "@batchcode"
aliases = ["@batchcodes"]
permissions = "cmd:batchcodes"
locks = "cmd:perm(batchcommands)"
help_category = "Building"
def func(self):
@ -359,6 +359,7 @@ class CmdStateAbort(MuxCommand):
"""
key = "@abort"
help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)"
def func(self):
"Exit back to default."
@ -374,6 +375,7 @@ class CmdStateLL(MuxCommand):
"""
key = "ll"
help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)"
def func(self):
show_curr(self.caller, showall=True)
@ -386,6 +388,7 @@ class CmdStatePP(MuxCommand):
"""
key = "pp"
help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)"
def func(self):
"""
@ -407,6 +410,7 @@ class CmdStateRR(MuxCommand):
"""
key = "rr"
help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)"
def func(self):
caller = self.caller
@ -426,6 +430,7 @@ class CmdStateRRR(MuxCommand):
"""
key = "rrr"
help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)"
def func(self):
caller = self.caller
@ -445,6 +450,7 @@ class CmdStateNN(MuxCommand):
"""
key = "nn"
help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)"
def func(self):
caller = self.caller
@ -465,6 +471,7 @@ class CmdStateNL(MuxCommand):
"""
key = "nl"
help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)"
def func(self):
caller = self.caller
@ -485,6 +492,7 @@ class CmdStateBB(MuxCommand):
"""
key = "bb"
help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)"
def func(self):
caller = self.caller
@ -505,6 +513,7 @@ class CmdStateBL(MuxCommand):
"""
key = "bl"
help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)"
def func(self):
caller = self.caller
@ -526,6 +535,7 @@ class CmdStateSS(MuxCommand):
"""
key = "ss"
help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)"
def func(self):
caller = self.caller
@ -553,6 +563,7 @@ class CmdStateSL(MuxCommand):
"""
key = "sl"
help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)"
def func(self):
caller = self.caller
@ -579,6 +590,7 @@ class CmdStateCC(MuxCommand):
"""
key = "cc"
help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)"
def func(self):
caller = self.caller
@ -608,6 +620,7 @@ class CmdStateJJ(MuxCommand):
"""
key = "j"
help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)"
def func(self):
caller = self.caller
@ -630,6 +643,7 @@ class CmdStateJL(MuxCommand):
"""
key = "jl"
help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)"
def func(self):
caller = self.caller
@ -652,6 +666,7 @@ class CmdStateQQ(MuxCommand):
"""
key = "qq"
help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)"
def func(self):
purge_processor(self.caller)
@ -662,6 +677,7 @@ class CmdStateHH(MuxCommand):
key = "hh"
help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)"
def func(self):
string = """

View file

@ -5,7 +5,6 @@ Building and world design commands
"""
from django.conf import settings
from src.permissions.permissions import has_perm, has_perm_string
from src.objects.models import ObjectDB, ObjAttribute
from src.utils import create, utils, debug
from src.commands.default.muxcommand import MuxCommand
@ -121,7 +120,7 @@ class CmdSetObjAlias(MuxCommand):
key = "@alias"
aliases = "@setobjalias"
permissions = "cmd:setobjalias"
locks = "cmd:perm(setobjalias) or perm(Builders)"
help_category = "Building"
def func(self):
@ -143,7 +142,7 @@ class CmdSetObjAlias(MuxCommand):
else:
caller.msg("No aliases exist for '%s'." % obj.key)
return
if not has_perm(caller, obj, 'modify_attributes'):
if not obj.access(caller, 'edit'):
caller.msg("You don't have permission to do that.")
return
if not aliases or not aliases[0]:
@ -164,7 +163,7 @@ class CmdSetObjAlias(MuxCommand):
aliases = list(set(old_aliases))
# save back to object.
obj.aliases = aliases
caller.msg("Aliases for '%s' are now set to %s." % (obj.key, obj.aliases))
caller.msg("Aliases for '%s' are now set to %s." % (obj.key, ", ".join(obj.aliases)))
class CmdCopy(ObjManipCommand):
"""
@ -183,7 +182,7 @@ class CmdCopy(ObjManipCommand):
"""
key = "@copy"
permissions = "cmd:copy"
locks = "cmd:perm(copy) or perm(Builders)"
help_category = "Building"
def func(self):
@ -252,7 +251,7 @@ class CmdCpAttr(MuxCommand):
Copy the attribute one object to one or more attributes on another object.
"""
key = "@cpattr"
permissions = "cmd:cpattr"
locks = "cmd:perm(cpattr) or perm(Builders)"
help_category = "Building"
def func(self):
@ -334,7 +333,7 @@ class CmdCreate(ObjManipCommand):
"""
key = "@create"
permissions = "cmd:create"
locks = "cmd:perm(create) or perm(Builders)"
help_category = "Building"
def func(self):
@ -366,8 +365,10 @@ class CmdCreate(ObjManipCommand):
# create object (if not a valid typeclass, the default
# object typeclass will automatically be used)
lockstring = "owner:id(%s);examine:perm(Builders);delete:id(%s) or perm(Wizards);get:all()" % (caller.id, caller.id)
obj = create.create_object(typeclass, name, caller,
home=caller, aliases=aliases)
home=caller, aliases=aliases, locks=lockstring)
if not obj:
string = "Error when creating object."
continue
@ -412,7 +413,7 @@ class CmdDebug(MuxCommand):
"""
key = "@debug"
permissions = "cmd:debug"
locks = "cmd:perm(debug) or perm(Builders)"
help_category = "Building"
def func(self):
@ -462,7 +463,7 @@ class CmdDesc(MuxCommand):
"""
key = "@desc"
aliases = "@describe"
permissions = "cmd:desc"
locks = "cmd:perm(desc) or perm(Builders)"
help_category = "Building"
def func(self):
@ -504,7 +505,7 @@ class CmdDestroy(MuxCommand):
key = "@destroy"
aliases = "@delete"
permissions = "cmd:destroy"
locks = "cmd:perm(destroy) or perm(Builders)"
help_category = "Building"
def func(self):
@ -525,7 +526,7 @@ class CmdDestroy(MuxCommand):
if obj.player and not 'override' in self.switches:
string = "Object %s is a player object. Use /override to delete anyway." % objname
continue
if not has_perm(caller, obj, 'create'):
if not obj.access(caller, 'delete'):
string = "You don't have permission to delete %s." % objname
continue
# do the deletion
@ -558,7 +559,7 @@ class CmdDig(ObjManipCommand):
like to the name of the room and the exits in question; an example would be 'north;no;n'.
"""
key = "@dig"
permissions = "cmd:dig"
locks = "cmd:perm(dig) or perm(Builders)"
help_category = "Building"
def func(self):
@ -681,7 +682,7 @@ class CmdLink(MuxCommand):
"""
key = "@link"
permissions = "cmd:link"
locks = "cmd:perm(link) or perm(Builders)"
help_category = "Building"
def func(self):
@ -759,7 +760,7 @@ class CmdListCmdSets(MuxCommand):
"""
key = "@cmdsets"
aliases = "@listcmsets"
permissions = "cmd:listcmdsets"
locks = "cmd:perm(listcmdsets) or perm(Builders)"
help_category = "Building"
def func(self):
@ -789,7 +790,7 @@ class CmdMvAttr(ObjManipCommand):
and target are the same, in which case this is like a copy operation)
"""
key = "@mvattr"
permissions = "cmd:mvattr"
locks = "cmd:perm(mvattr) or perm(Builders)"
help_category = "Building"
def func(self):
@ -861,7 +862,7 @@ class CmdName(ObjManipCommand):
key = "@name"
aliases = ["@rename"]
permissions = "cmd:rename"
locks = "cmd:perm(rename) or perm(Builders)"
help_category = "Building"
def func(self):
@ -912,7 +913,7 @@ class CmdOpen(ObjManipCommand):
"""
key = "@open"
permissions = "cmd:open"
locks = "cmd:perm(open) or perm(Builders)"
help_category = "Building"
# a custom member method to chug out exits and do checks
@ -1053,7 +1054,7 @@ class CmdSetAttribute(ObjManipCommand):
"""
key = "@set"
permissions = "cmd:set"
locks = "cmd:perm(set) or perm(Builders)"
help_category = "Building"
def func(self):
@ -1128,7 +1129,7 @@ class CmdTypeclass(MuxCommand):
key = "@typeclass"
aliases = "@type, @parent"
permissions = "cmd:typeclass"
locks = "cmd:perm(typeclass) or perm(Builders)"
help_category = "Building"
def func(self):
@ -1166,7 +1167,7 @@ class CmdTypeclass(MuxCommand):
typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH,
typeclass)
if not has_perm(caller, obj, 'change_typeclass'):
if not obj.access(caller, 'edit'):
caller.msg("You are not allowed to do that.")
return
@ -1208,7 +1209,7 @@ class CmdWipe(ObjManipCommand):
matching the given attribute-wildcard search string.
"""
key = "@wipe"
permissions = "cmd:wipe"
locks = "cmd:perm(wipe) or perm(Builders)"
help_category = "Building"
def func(self):
@ -1229,6 +1230,9 @@ class CmdWipe(ObjManipCommand):
obj = caller.search(objname)
if not obj:
return
if not obj.access(caller, 'edit'):
caller.msg("You are not allowed to do that.")
return
if not attrs:
# wipe everything
for attr in obj.get_all_attributes():
@ -1241,7 +1245,90 @@ class CmdWipe(ObjManipCommand):
string = string % (",".join(attrs), obj.name)
caller.msg(string)
class CmdLock(ObjManipCommand):
"""
lock - assign a lock definition to an object
Usage:
@lock <object>[ = <lockstring>]
or
@lock[/switch] object/<access_type>
Switch:
del - delete given access type
view - view lock associated with given access type (default)
If no lockstring is given, shows all locks on
object.
Lockstring is on the form
'access_type:[NOT] func1(args)[ AND|OR][ NOT] func2(args) ...]
Where func1, func2 ... valid lockfuncs with or without arguments.
Separator expressions need not be capitalized.
For example:
'get: id(25) or perm(Wizards)'
The 'get' access_type is checked by the get command and will
an object locked with this string will only be possible to
pick up by Wizards or by object with id 25.
You can add several access_types after oneanother by separating
them by ';', i.e:
'get:id(25);delete:perm(Builders)'
"""
key = "@lock"
aliases = ["@locks", "lock", "locks"]
locks = "cmd: perm(@locks) or perm(Builders)"
help_category = "Building"
def func(self):
"Sets up the command"
caller = self.caller
if not self.args:
string = "@lock <object>[ = <lockstring>] or @lock[/switch] object/<access_type>"
caller.msg(string)
return
if '/' in self.lhs:
# call on the form @lock obj/access_type
objname, access_type = [p.strip() for p in self.lhs.split('/', 1)]
obj = caller.search(objname)
if not obj:
return
lockdef = obj.locks.get(access_type)
if lockdef:
string = lockdef[2]
if 'del' in self.switches:
if obj.access(caller, 'edit'):
caller.msg("You are not allowed to do that.")
return
obj.locks.delete(access_type)
string = "deleted lock %s" % string
else:
string = "%s has no lock of access type '%s'." % (obj, access_type)
caller.msg(string)
return
if self.rhs:
# we have a = separator, so we are assigning a new lock
objname, lockdef = self.lhs, self.rhs
obj = caller.search(objname)
if not obj:
return
if obj.access(caller, 'edit'):
caller.msg("You are not allowed to do that.")
return
ok = obj.locks.add(lockdef, caller)
if ok:
caller.msg("Added lock '%s' to %s." % (lockdef, obj))
return
# if we get here, we are just viewing all locks
obj = caller.search(self.lhs)
if not obj:
return
caller.msg(obj.locks)
class CmdExamine(ObjManipCommand):
"""
examine - detailed info on objects
@ -1255,7 +1342,7 @@ class CmdExamine(ObjManipCommand):
"""
key = "@examine"
aliases = ["@ex","ex", "exam"]
permissions = "cmd:examine"
locks = "cmd:perm(examine) or perm(Builders)"
help_category = "Building"
def crop_line(self, text, heading="", line_width=79):
@ -1324,7 +1411,10 @@ class CmdExamine(ObjManipCommand):
string += "\n{wLocation{n: %s" % obj.location
perms = obj.permissions
if perms:
string += "\n{wObj Perms/Locks{n: %s" % (", ".join(perms))
string += "\n{wPermissions{n: %s" % (", ".join(perms))
locks = str(obj.locks)
if locks:
string += "\n{wLocks{n: %s" % ("; ".join([lock for lock in locks.split(';')]))
if not (len(obj.cmdset.all()) == 1 and obj.cmdset.current.key == "Empty"):
string += "\n{wCurrent Cmdset (before permission checks){n:\n\r %s" % obj.cmdset
if obj.scripts.all():
@ -1360,7 +1450,7 @@ class CmdExamine(ObjManipCommand):
if not self.args:
# If no arguments are provided, examine the invoker's location.
obj = caller.location
if not has_perm(caller, obj, 'obj_info'):
if not obj.access(caller, 'examine'):
#If we don't have special info access, just look at the object instead.
caller.exec_cmd('look %s' % obj.name)
return
@ -1379,7 +1469,7 @@ class CmdExamine(ObjManipCommand):
obj = caller.search(obj_name)
if not obj:
continue
if not has_perm(caller, obj, 'obj_info'):
if not obj.access(caller, 'examine'):
#If we don't have special info access, just look at the object instead.
caller.exec_cmd('look %s' % obj_name)
continue
@ -1407,7 +1497,7 @@ class CmdFind(MuxCommand):
key = "@find"
aliases = "@locate, find, locate"
permissions = "cmd:find"
locks = "cmd:perm(find) or perm(Builders)"
help_category = "Building"
def func(self):
@ -1447,7 +1537,7 @@ class CmdTeleport(MuxCommand):
"""
key = "@tel"
aliases = "@teleport"
permissions = "cmd:teleport"
locks = "cmd:perm(teleport) or perm(Builders)"
help_category = "Building"
def func(self):
@ -1501,7 +1591,7 @@ class CmdUnLink(CmdLink):
# this is just a child of CmdLink
key = "@unlink"
permissions = "cmd:unlink"
locks = "cmd:perm(unlink) or perm(Builders)"
help_key = "Building"
def func(self):

View file

@ -27,7 +27,7 @@ class DefaultCmdSet(CmdSet):
self.add(general.CmdDrop())
self.add(general.CmdWho())
self.add(general.CmdSay())
self.add(general.CmdGroup())
self.add(general.CmdAccess())
self.add(general.CmdEncoding())
# The help system
@ -37,16 +37,15 @@ class DefaultCmdSet(CmdSet):
# System commands
self.add(system.CmdReload())
self.add(system.CmdPy())
self.add(system.CmdListScripts())
self.add(system.CmdListObjects())
self.add(system.CmdScripts())
self.add(system.CmdObjects())
self.add(system.CmdService())
self.add(system.CmdShutdown())
self.add(system.CmdVersion())
self.add(system.CmdTime())
self.add(system.CmdList())
self.add(system.CmdServerLoad())
self.add(system.CmdPs())
self.add(system.CmdStats())
# Admin commands
self.add(admin.CmdBoot())
self.add(admin.CmdDelPlayer())
@ -77,6 +76,7 @@ class DefaultCmdSet(CmdSet):
self.add(building.CmdDestroy())
self.add(building.CmdExamine())
self.add(building.CmdTypeclass())
self.add(building.CmdLock())
# Comm commands
self.add(comms.CmdAddCom())
@ -93,5 +93,5 @@ class DefaultCmdSet(CmdSet):
# Testing/Utility commands
self.add(utils.CmdTest())
self.add(utils.CmdTestPerms())
#self.add(utils.CmdTestPerms())
self.add(utils.TestCom())

View file

@ -5,7 +5,6 @@ Comsys command module.
from src.comms.models import Channel, Msg, ChannelConnection
from src.comms.channelhandler import CHANNELHANDLER
from src.utils import create, utils
from src.permissions.permissions import has_perm
from src.commands.default.muxcommand import MuxCommand
def find_channel(caller, channelname, silent=False):
@ -41,6 +40,7 @@ class CmdAddCom(MuxCommand):
key = "addcom"
aliases = ["aliaschan","chanalias"]
help_category = "Comms"
locks = "cmd:not perm(channel_banned)"
def func(self):
"Implement the command"
@ -67,7 +67,7 @@ class CmdAddCom(MuxCommand):
return
# check permissions
if not has_perm(player, channel, 'chan_listen'):
if not channel.access(player, 'listen'):
caller.msg("You are not allowed to listen to this channel.")
return
@ -83,7 +83,7 @@ class CmdAddCom(MuxCommand):
if alias:
# create a nick and add it to the caller.
caller.nickhandler(alias, channel.key, nick_type="channel")
caller.nicks.add(alias, channel.key, nick_type="channel")
string += "You can now refer to the channel %s with the alias '%s'."
caller.msg(string % (channel.key, alias))
else:
@ -106,6 +106,7 @@ class CmdDelCom(MuxCommand):
key = "delcom"
aliases = ["delaliaschan, delchanalias"]
help_category = "Comms"
locks = "cmd:not perm(channel_banned)"
def func(self):
"Implementing the command. "
@ -126,20 +127,20 @@ class CmdDelCom(MuxCommand):
return
chkey = channel.key.lower()
# find all nicks linked to this channel and delete them
for nick in [nick for nick in caller.nicks
if nick.db_type == "channel" and nick.db_real.lower() == chkey]:
for nick in [nick for nick in caller.nicks.get(nick_type="channel")
if nick.db_real.lower() == chkey]:
nick.delete()
channel.disconnect_from(player)
caller.msg("You stop listening to channel '%s'. Eventual aliases were removed." % channel.key)
return
else:
# we are removing a channel nick
channame = caller.nickhandler(ostring, nick_type="channel")
channame = caller.nicks.get(ostring, nick_type="channel")
channel = find_channel(caller, channame, silent=True)
if not channel:
caller.msg("No channel with alias '%s' was found." % ostring)
else:
caller.nickhandler(ostring, nick_type="channel", delete=True)
caller.nicks.delete(ostring, nick_type="channel")
caller.msg("Your alias '%s' for channel %s was cleared." % (ostring, channel.key))
# def cmd_allcom(command):
@ -249,6 +250,7 @@ class CmdChannels(MuxCommand):
key = "@channels"
aliases = ["@clist", "channels", "comlist", "chanlist", "channellist", "all channels"]
help_category = "Comms"
locks = "cmd:all()"
def func(self):
"Implement function"
@ -256,7 +258,7 @@ class CmdChannels(MuxCommand):
caller = self.caller
# all channels we have available to listen to
channels = [chan for chan in Channel.objects.get_all_channels() if has_perm(caller, chan, 'can_listen')]
channels = [chan for chan in Channel.objects.get_all_channels() if chan.access(caller, 'listen')]
if not channels:
caller.msg("No channels available")
return
@ -265,7 +267,7 @@ class CmdChannels(MuxCommand):
if self.cmdstring != "comlist":
string = "\nAll available channels:"
string = "\nChannels available:"
cols = [[" "], ["Channel"], ["Aliases"], ["Perms"], ["Description"]]
for chan in channels:
if chan in subs:
@ -274,7 +276,7 @@ class CmdChannels(MuxCommand):
cols[0].append(" ")
cols[1].append(chan.key)
cols[2].append(",".join(chan.aliases))
cols[3].append(",".join(chan.permissions))
cols[3].append(str(chan.locks))
cols[4].append(chan.desc)
# put into table
for ir, row in enumerate(utils.format_table(cols)):
@ -284,18 +286,18 @@ class CmdChannels(MuxCommand):
string += "\n" + "".join(row)
self.caller.msg(string)
string = "\nYour channel subscriptions:"
string = "\nChannel subscriptions:"
if not subs:
string += "(None)"
else:
nicks = [nick for nick in caller.nicks if nick.db_type == 'channel']
print nicks
cols = [["Channel"], ["Aliases"], ["Description"]]
nicks = [nick for nick in caller.nicks.get(nick_type="channel")]
cols = [[" "], ["Channel"], ["Aliases"], ["Description"]]
for chan in subs:
cols[0].append(chan.key)
cols[1].append(",".join([nick.db_nick for nick in nicks
cols[0].append(" ")
cols[1].append(chan.key)
cols[2].append(",".join([nick.db_nick for nick in nicks
if nick.db_real.lower() == chan.key.lower()] + chan.aliases))
cols[2].append(chan.desc)
cols[3].append(chan.desc)
# put into table
for ir, row in enumerate(utils.format_table(cols)):
if ir == 0:
@ -316,6 +318,7 @@ class CmdCdestroy(MuxCommand):
key = "@cdestroy"
help_category = "Comms"
locks = "cmd:all()"
def func(self):
"Destroy objects cleanly."
@ -328,7 +331,7 @@ class CmdCdestroy(MuxCommand):
if not channel:
caller.msg("Could not find channel %s." % self.args)
return
if not has_perm(caller, channel, 'chan_admin', default_deny=True):
if not channel.access(caller, 'admin'):
caller.msg("You are not allowed to do that.")
return
@ -572,7 +575,7 @@ class CmdChannelCreate(MuxCommand):
key = "@ccreate"
aliases = "channelcreate"
permissions = "cmd:ccreate"
locks = "cmd:not perm(channel_banned)"
help_category = "Comms"
def func(self):
@ -601,9 +604,8 @@ class CmdChannelCreate(MuxCommand):
caller.msg("A channel with that name already exists.")
return
# Create and set the channel up
permissions = "chan_send:%s,chan_listen:%s,chan_admin:has_id(%s)" % \
("Players","Players",caller.id)
new_chan = create.create_channel(channame, aliases, description, permissions)
lockstring = "send:all();listen:all();admin:id(%s)" % caller.id
new_chan = create.create_channel(channame, aliases, description, locks=lockstring)
new_chan.connect_to(caller)
caller.msg("Created channel %s and connected to it." % new_chan.key)
@ -663,7 +665,7 @@ class CmdCdesc(MuxCommand):
"""
key = "@cdesc"
permissions = "cmd:cdesc"
locks = "cmd:not perm(channel_banned)"
help_category = "Comms"
def func(self):
@ -679,7 +681,7 @@ class CmdCdesc(MuxCommand):
caller.msg("Channel '%s' not found." % self.lhs)
return
#check permissions
if not has_perm(caller, channel, 'channel_admin'):
if not caller.access(caller, 'admin'):
caller.msg("You cant admin this channel.")
return
# set the description
@ -694,19 +696,19 @@ class CmdPage(MuxCommand):
Usage:
page[/switches] [<player>,<player>,... = <message>]
tell ''
page/list <number>
page <number>
Switch:
list - show your last <number> of tells/pages.
last - shows who you last messaged
list - show your last <number> of tells/pages (default)
Send a message to target user (if online). If no
argument is given, you will instead see who was the last
person you paged to.
argument is given, you will get a list of your latest messages.
"""
key = "page"
aliases = ['tell']
permissions = "cmd:tell"
locks = "cmd:not perm(page_banned)"
help_category = "Comms"
def func(self):
@ -722,17 +724,29 @@ class CmdPage(MuxCommand):
if msg.receivers]
# get last messages we've got
pages_we_got = list(Msg.objects.get_messages_by_receiver(player))
if 'list' in self.switches:
if 'last' in self.switches:
if pages_we_sent:
string = "You last paged {c%s{n." % (", ".join([obj.name
for obj in pages_we_sent[-1].receivers]))
caller.msg(string)
return
else:
string = "You haven't paged anyone yet."
caller.msg(string)
return
if not self.args or not self.rhs:
pages = pages_we_sent + pages_we_got
pages.sort(lambda x, y: cmp(x.date_sent, y.date_sent))
number = 10
number = 5
if self.args:
try:
number = int(self.args)
except ValueError:
pass
caller.msg("Usage: tell [<player> = msg]")
return
if len(pages) > number:
lastpages = pages[-number:]
@ -744,19 +758,14 @@ class CmdPage(MuxCommand):
"{n,{c ".join([obj.name for obj in page.receivers]),
page.message)
for page in lastpages])
caller.msg("Your latest pages:\n %s" % lastpages )
return
if not self.args or not self.rhs:
if pages_we_sent:
string = "You last paged {c%s{n." % (", ".join([obj.name
for obj in pages_we_sent[-1].receivers]))
caller.msg(string)
return
if lastpages:
string = "Your latest pages:\n %s" % lastpages
else:
string = "You haven't paged anyone yet."
caller.msg(string)
return
caller.msg(string)
return
# We are sending. Build a list of targets
@ -786,7 +795,7 @@ class CmdPage(MuxCommand):
if not recobjs:
caller.msg("No players matching your target were found.")
return
header = "{wPlayer{n {c%s{n {wpages:{n" % caller.key
message = self.rhs
@ -800,12 +809,17 @@ class CmdPage(MuxCommand):
# tell the players they got a message.
received = []
rstrings = []
for pobj in recobjs:
pobj.msg("%s %s" % (header, message))
if not pobj.access(caller, 'msg'):
rstrings.append("You are not allowed to page %s." % pobj)
continue
pobj.msg("%s %s" % (header, message))
if hasattr(pobj, 'has_player') and not pobj.has_player:
received.append("{C%s{n" % pobj.name)
caller.msg("%s is offline. They will see your message if they list their pages later." % received[-1])
rstrings.append("%s is offline. They will see your message if they list their pages later." % received[-1])
else:
received.append("{c%s{n" % pobj.name)
received = ", ".join(received)
caller.msg("You paged %s with: '%s'." % (received, message))
if rstrings:
caller.msg(rstrings = "\n".join(rstrings))
caller.msg("You paged %s with: '%s'." % (", ".join(received), message))

View file

@ -5,8 +5,6 @@ now.
import time
from django.conf import settings
from src.server.sessionhandler import SESSIONS
from src.permissions.models import PermissionGroup
from src.permissions.permissions import has_perm, has_perm_string
from src.objects.models import HANDLE_SEARCH_ERRORS
from src.utils import utils
from src.objects.models import Nick
@ -23,7 +21,7 @@ class CmdHome(MuxCommand):
"""
key = "home"
permissions = "cmd:home"
locks = "cmd:perm(home) or perm(Builders)"
def func(self):
"Implement the command"
@ -48,6 +46,7 @@ class CmdLook(MuxCommand):
"""
key = "look"
aliases = ["l"]
locks = "cmd:all()"
def func(self):
"""
@ -84,6 +83,7 @@ class CmdPassword(MuxCommand):
Changes your password. Make sure to pick a safe one.
"""
key = "@password"
locks = "cmd:all()"
def func(self):
"hook function."
@ -138,7 +138,8 @@ class CmdNick(MuxCommand):
"""
key = "nick"
aliases = ["nickname", "nicks", "@nick", "alias"]
locks = "cmd:all()"
def func(self):
"Create the nickname"
@ -185,7 +186,7 @@ class CmdNick(MuxCommand):
if oldnick:
# clear the alias
string += "\nNick '%s' (= '%s') was cleared." % (nick, oldnick[0].db_real)
caller.nickhandler(nick, nick_type=switch, delete=True)
caller.nicks.delete(nick, nick_type=switch)
else:
string += "\nNo nick '%s' found, so it could not be removed." % nick
else:
@ -194,7 +195,7 @@ class CmdNick(MuxCommand):
string += "\nNick %s changed from '%s' to '%s'." % (nick, oldnick[0].db_real, real)
else:
string += "\nNick set: '%s' = '%s'." % (nick, real)
caller.nickhandler(nick, real, nick_type=switch)
caller.nicks.add(nick, real, nick_type=switch)
caller.msg(string)
class CmdInventory(MuxCommand):
@ -209,6 +210,7 @@ class CmdInventory(MuxCommand):
"""
key = "inventory"
aliases = ["inv", "i"]
locks = "cmd:all()"
def func(self):
"hook function"
@ -237,7 +239,8 @@ class CmdGet(MuxCommand):
"""
key = "get"
aliases = "grab"
locks = "cmd:all()"
def func(self):
"implements the command."
@ -256,7 +259,7 @@ class CmdGet(MuxCommand):
# don't allow picking up player objects, nor exits.
caller.msg("You can't get that.")
return
if not has_perm(caller, obj, 'get'):
if not obj.access(caller, 'get'):
if obj.db.get_err_msg:
caller.msg(obj.db.get_err_msg)
else:
@ -285,6 +288,7 @@ class CmdDrop(MuxCommand):
"""
key = "drop"
locks = "cmd:all()"
def func(self):
"Implement command"
@ -322,14 +326,15 @@ class CmdQuit(MuxCommand):
Gracefully disconnect from the game.
"""
key = "@quit"
locks = "cmd:all()"
def func(self):
"hook function"
sessions = self.caller.sessions
for session in sessions:
session.msg("Quitting. Hope to see you soon again.")
session.at_disconnect()
session.session_disconnect()
class CmdWho(MuxCommand):
"""
who
@ -357,7 +362,7 @@ class CmdWho(MuxCommand):
if self.cmdstring == "doing":
show_session_data = False
else:
show_session_data = has_perm_string(caller, "Immortals,Wizards")
show_session_data = caller.check_permstring("Immortals") or caller.check_permstring("Wizards")
if show_session_data:
table = [["Player Name"], ["On for"], ["Idle"], ["Room"], ["Cmds"], ["Host"]]
@ -411,6 +416,7 @@ class CmdSay(MuxCommand):
key = "say"
aliases = ['"']
locks = "cmd:all()"
def func(self):
"Run the say command"
@ -505,6 +511,7 @@ class CmdPose(MuxCommand):
"""
key = "pose"
aliases = [":", "emote"]
locks = "cmd:all()"
def parse(self):
"""
@ -608,6 +615,7 @@ class CmdEncoding(MuxCommand):
key = "@encoding"
aliases = "@encode"
locks = "cmd:all()"
def func(self):
"""
@ -640,39 +648,31 @@ class CmdEncoding(MuxCommand):
string = "Your custom text encoding was changed from '%s' to '%s'." % (old_encoding, encoding)
caller.msg(string)
class CmdGroup(MuxCommand):
class CmdAccess(MuxCommand):
"""
group - show your groups
access - show access groups
Usage:
group
access
This command shows you which user permission groups
you are a member of, if any.
This command shows you the permission hierarchy and
which permission groups you are a member of.
"""
key = "access"
aliases = "groups"
aliases = ["groups", "hierarchy"]
locks = "cmd:all()"
def func(self):
"Load the permission groups"
caller = self.caller
string = ""
if caller.player and caller.player.is_superuser:
string += "\n This is a SUPERUSER account! Group membership does not matter."
else:
# get permissions and determine if they are groups
perms = list(set(caller.permissions + caller.player.permissions))
for group in [group for group in PermissionGroup.objects.all()
if group.key in perms]:
string += "\n {w%s{n\n%s" % (group.key, ", ".join(group.group_permissions))
if string:
string = "\nGroup memberships for you (Player %s + Character %s): %s" % (caller.player.name,
caller.name, string)
else:
string = "\nYou are not not a member of any groups."
hierarchy_full = settings.PERMISSION_HIERARCHY
string = "\n{wPermission Hierarchy{n (climbing):\n %s" % ", ".join(hierarchy_full)
hierarchy = [p.lower() for p in hierarchy_full]
string += "\n{wYour access{n:"
string += "\nCharacter %s: %s" % (caller.key, ", ".join(caller.permissions))
if hasattr(caller, 'player'):
string += "\nPlayer %s: %s" % (caller.player.key, ", ".join(caller.player.permissions))
caller.msg(string)
## def cmd_apropos(command):

View file

@ -9,7 +9,6 @@ creation of other help topics such as RP help or game-world aides.
from src.utils.utils import fill, dedent
from src.commands.command import Command
from src.help.models import HelpEntry
from src.permissions.permissions import has_perm
from src.utils import create
from src.commands.default.muxcommand import MuxCommand
@ -65,6 +64,8 @@ class CmdHelp(Command):
topics related to the game.
"""
key = "help"
locks = "cmd:all()"
# this is a special cmdhandler flag that makes the cmdhandler also pack
# the current cmdset with the call to self.func().
return_cmdset = True
@ -73,6 +74,7 @@ class CmdHelp(Command):
"""
inp is a string containing the command or topic match.
"""
self.original_args = self.args.strip()
self.args = self.args.strip().lower()
def func(self):
@ -94,7 +96,7 @@ class CmdHelp(Command):
if query in LIST_ARGS:
# we want to list all available help entries
hdict_cmd = {}
for cmd in (cmd for cmd in cmdset if has_perm(caller, cmd, 'cmd')
for cmd in (cmd for cmd in cmdset if cmd.access(caller)
if not cmd.key.startswith('__')
and not (hasattr(cmd, 'is_exit') and cmd.is_exit)):
if hdict_cmd.has_key(cmd.help_category):
@ -103,7 +105,7 @@ class CmdHelp(Command):
hdict_cmd[cmd.help_category] = [cmd.key]
hdict_db = {}
for topic in (topic for topic in HelpEntry.objects.get_all_topics()
if has_perm(caller, topic, 'view')):
if topic.access(caller, 'view', default=True)):
if hdict_db.has_key(topic.help_category):
hdict_db[topic.help_category].append(topic.key)
else:
@ -116,7 +118,7 @@ class CmdHelp(Command):
# Cmd auto-help dynamic entries
cmdmatches = [cmd for cmd in cmdset
if query in cmd and has_perm(caller, cmd, 'cmd')]
if query in cmd and cmd.access(caller)]
if len(cmdmatches) > 1:
# multiple matches. Try to limit it down to exact match
exactmatches = [cmd for cmd in cmdmatches if cmd == query]
@ -127,12 +129,12 @@ class CmdHelp(Command):
dbmatches = \
[topic for topic in
HelpEntry.objects.find_topicmatch(query, exact=False)
if has_perm(caller, topic, 'view')]
if topic.access(caller, 'view', default=True)]
if len(dbmatches) > 1:
exactmatches = \
[topic for topic in
HelpEntry.objects.find_topicmatch(query, exact=True)
if has_perm(caller, topic, 'view')]
if topic.access(caller, 'view', default=True)]
if exactmatches:
dbmatches = exactmatches
@ -140,11 +142,11 @@ class CmdHelp(Command):
if (not cmdmatches) and (not dbmatches):
# no normal match. Check if this is a category match instead
categ_cmdmatches = [cmd.key for cmd in cmdset
if query == cmd.help_category and has_perm(caller, cmd, 'cmd')]
if query == cmd.help_category and cmd.access(caller)]
categ_dbmatches = \
[topic.key for topic in
HelpEntry.objects.find_topics_with_category(query)
if has_perm(caller, topic, 'view')]
if topic.access(caller, 'view', default=True)]
cmddict = None
dbdict = None
if categ_cmdmatches:
@ -154,7 +156,7 @@ class CmdHelp(Command):
if cmddict or dbdict:
help_entry = format_help_list(cmddict, dbdict)
else:
help_entry = "No help entry found for '%s'" % query
help_entry = "No help entry found for '%s'" % self.original_args
elif len(cmdmatches) == 1:
# we matched against a command name or alias. Show its help entry.
@ -184,7 +186,7 @@ class CmdSetHelp(MuxCommand):
@help - edit the help database
Usage:
@help[/switches] <topic>[,category[,permission,permission,...]] = <text>
@help[/switches] <topic>[,category[,locks]] = <text>
Switches:
add - add or replace a new topic with text.
@ -203,7 +205,7 @@ class CmdSetHelp(MuxCommand):
"""
key = "@help"
aliases = "@sethelp"
permissions = "cmd:sethelp"
locks = "cmd:perm(PlayerHelpers)"
help_category = "Building"
def func(self):
@ -214,36 +216,33 @@ class CmdSetHelp(MuxCommand):
lhslist = self.lhslist
rhs = self.rhs
if not self.rhs:
caller.msg("Usage: @sethelp/[add|del|append|merge] <topic>[,category[,permission,..] = <text>]")
if not self.args:
caller.msg("Usage: @sethelp/[add|del|append|merge] <topic>[,category[,locks,..] = <text>]")
return
topicstr = ""
category = ""
permissions = ""
lockstring = ""
try:
topicstr = lhslist[0]
category = lhslist[1]
permissions = ",".join(lhslist[2:])
lockstring = ",".join(lhslist[2:])
except Exception:
pass
if not topicstr:
caller.msg("You have to define a topic!")
return
string = ""
print topicstr, category, permissions
#print topicstr, category, lockstring
if switches and switches[0] in ('append', 'app','merge'):
# add text to the end of a help topic
# find the topic to append to
old_entry = None
try:
old_entry = HelpEntry.objects.get(key=topicstr)
except Exception:
pass
old_entry = HelpEntry.objects.filter(db_key__iexact=topicstr)
if not old_entry:
string = "Could not find topic '%s'. You must give an exact name." % topicstr
else:
old_entry = old_entry[0]
entrytext = old_entry.entrytext
if switches[0] == 'merge':
old_entry.entrytext = "%s %s" % (entrytext, self.rhs)
@ -255,15 +254,11 @@ class CmdSetHelp(MuxCommand):
elif switches and switches[0] in ('delete','del'):
#delete a help entry
old_entry = None
try:
old_entry = HelpEntry.objects.get(key=topicstr)
except Exception:
pass
old_entry = HelpEntry.objects.filter(db_key__iexact=topicstr)
if not old_entry:
string = "Could not find topic. You must give an exact name."
string = "Could not find topic '%s'." % topicstr
else:
old_entry.delete()
old_entry[0].delete()
string = "Deleted the help entry '%s'." % topicstr
else:
@ -279,7 +274,8 @@ class CmdSetHelp(MuxCommand):
old_entry.key = topicstr
old_entry.entrytext = self.rhs
old_entry.help_category = category
old_entry.permissions = permissions
old_entry.locks.clear()
old_entry.locks.add(lockstring)
old_entry.save()
string = "Overwrote the old topic '%s' with a new one." % topicstr
else:
@ -287,7 +283,8 @@ class CmdSetHelp(MuxCommand):
else:
# no old entry. Create a new one.
new_entry = create.create_help_entry(topicstr,
rhs, category, permissions)
rhs, category, lockstring)
if new_entry:
string = "Topic '%s' was successfully created." % topicstr
else:

View file

@ -20,7 +20,6 @@ the line is just added to the editor buffer).
from src.comms.models import Channel
from src.utils import create
from src.permissions.permissions import has_perm
# The command keys the engine is calling
# (the actual names all start with __)
@ -41,6 +40,7 @@ class SystemNoInput(MuxCommand):
This is called when there is no input given
"""
key = CMD_NOINPUT
locks = "cmd:all()"
def func(self):
"Do nothing."
@ -56,7 +56,8 @@ class SystemNoMatch(MuxCommand):
No command was found matching the given input.
"""
key = CMD_NOMATCH
locks = "cmd:all()"
def func(self):
"""
This is given the failed raw string as input.
@ -79,6 +80,7 @@ class SystemMultimatch(MuxCommand):
and cmd is an an instantiated Command object matching the candidate.
"""
key = CMD_MULTIMATCH
locks = "cmd:all()"
def format_multimatches(self, caller, matches):
"""
@ -131,6 +133,7 @@ class SystemNoPerm(MuxCommand):
correct permissions to use a particular command.
"""
key = CMD_NOPERM
locks = "cmd:all()"
def func(self):
"""
@ -154,7 +157,7 @@ class SystemSendToChannel(MuxCommand):
"""
key = CMD_CHANNEL
permissions = "cmd:use_channels"
locks = "cmd:all()"
def parse(self):
channelname, msg = self.args.split(':', 1)
@ -178,7 +181,7 @@ class SystemSendToChannel(MuxCommand):
string = "You are not connected to channel '%s'."
caller.msg(string % channelkey)
return
if not has_perm(caller, channel, 'chan_send'):
if not channel.access(caller, 'send'):
string = "You are not permitted to send to channel '%s'."
caller.msg(string % channelkey)
return
@ -199,6 +202,7 @@ class SystemUseExit(MuxCommand):
as a command. It receives the raw string as input.
"""
key = CMD_EXIT
locks = "cmd:all()"
def func(self):
"""
@ -214,7 +218,7 @@ class SystemUseExit(MuxCommand):
destination = exi.attr('_destination')
if not destination:
return
if has_perm(caller, exit, 'traverse'):
if exit.access(caller, 'traverse'):
caller.move_to(destination)
else:
caller.msg("You cannot enter")

View file

@ -9,9 +9,11 @@ import os, datetime
import django, twisted
from django.contrib.auth.models import User
from django.conf import settings
from src.server.sessionhandler import SESSIONS
from src.scripts.models import ScriptDB
from src.objects.models import ObjectDB
from src.players.models import PlayerDB
from src.config.models import ConfigValue
from src.utils import reloads, create, logger, utils, gametime
from src.commands.default.muxcommand import MuxCommand
@ -28,7 +30,7 @@ class CmdReload(MuxCommand):
re-validates all scripts.
"""
key = "@reload"
permissions = "cmd:reload"
locks = "cmd:perm(reload) or perm(Immortals)"
help_category = "System"
def func(self):
@ -84,7 +86,7 @@ class CmdPy(MuxCommand):
"""
key = "@py"
aliases = ["!"]
permissions = "cmd:py"
locks = "cmd:perm(py) or perm(Immortals)"
help_category = "System"
def func(self):
@ -130,7 +132,7 @@ class CmdPy(MuxCommand):
obj.delete()
script.delete()
class CmdListScripts(MuxCommand):
class CmdScripts(MuxCommand):
"""
Operate on scripts.
@ -149,7 +151,7 @@ class CmdListScripts(MuxCommand):
"""
key = "@scripts"
aliases = "@listscripts"
permissions = "cmd:listscripts"
locks = "cmd:perm(listscripts) or perm(Wizards)"
help_category = "System"
def format_script_list(self, scripts):
@ -255,7 +257,7 @@ class CmdListScripts(MuxCommand):
class CmdListObjects(MuxCommand):
class CmdObjects(MuxCommand):
"""
Give a summary of object types in database
@ -267,8 +269,8 @@ class CmdListObjects(MuxCommand):
given, <nr> defaults to 10.
"""
key = "@objects"
aliases = ["@listobjects", "@listobjs"]
permissions = "cmd:listobjects"
aliases = ["@listobjects", "@listobjs", '@stats', '@db']
locks = "cmd:perm(listobjects) or perm(Builders)"
help_category = "System"
def func(self):
@ -280,9 +282,24 @@ class CmdListObjects(MuxCommand):
nlim = int(self.args)
else:
nlim = 10
string = "\n{wDatabase totals:{n"
nplayers = PlayerDB.objects.count()
nobjs = ObjectDB.objects.count()
base_typeclass = settings.BASE_CHARACTER_TYPECLASS
nchars = ObjectDB.objects.filter(db_typeclass_path=base_typeclass).count()
nrooms = ObjectDB.objects.filter(db_location=None).exclude(db_typeclass_path=base_typeclass).count()
nexits = sum([1 for obj in ObjectDB.objects.filter(db_location=None) if obj.get_attribute('_destination')])
string += "\n{wPlayers:{n %i" % nplayers
string += "\n{wObjects:{n %i" % nobjs
string += "\n{w Characters (base type):{n %i" % nchars
string += "\n{w Rooms (location==None):{n %i" % nrooms
string += "\n{w Exits (.db._destination!=None):{n %i" % nexits
string += "\n{w Other:{n %i\n" % (nobjs - nchars - nrooms - nexits)
dbtotals = ObjectDB.objects.object_totals()
#print dbtotals
string = "\n{wDatase Object totals:{n"
table = [["Count"], ["Typeclass"]]
for path, count in dbtotals.items():
table[0].append(count)
@ -332,7 +349,7 @@ class CmdService(MuxCommand):
"""
key = "@service"
permissions = "cmd:service"
locks = "cmd:perm(service) or perm(Immortals)"
help_category = "System"
def func(self):
@ -351,7 +368,7 @@ class CmdService(MuxCommand):
sessions = caller.sessions
if not sessions:
return
service_collection = sessions[0].server.service_collection
service_collection = SESSIONS.server.services
if switch == "list":
# Just display the list of installed services and their
@ -417,7 +434,7 @@ class CmdShutdown(MuxCommand):
Shut the game server down gracefully.
"""
key = "@shutdown"
permissions = "cmd:shutdown"
locks = "cmd:perm(shutdown) or perm(Immortals)"
help_category = "System"
def func(self):
@ -468,159 +485,124 @@ class CmdTime(MuxCommand):
"""
key = "@time"
aliases = "@uptime"
permissions = "cmd:time"
locks = "cmd:perm(time) or perm(Players)"
help_category = "System"
def func(self):
"Show times."
string1 = "\nCurrent server uptime: \t"
string1 += "{w%s{n" % (utils.time_format(gametime.uptime(format=False), 2))
string2 = "\nTotal server running time: \t"
string2 += "{w%s{n" % (utils.time_format(gametime.runtime(format=False), 2))
string3 = "\nTotal in-game time (realtime x %g):\t" % (gametime.TIMEFACTOR)
string3 += "{w%s{n" % (utils.time_format(gametime.gametime(format=False), 2))
string4 = "\nServer time stamp: \t"
string4 += "{w%s{n" % (str(datetime.datetime.now()))
string5 = ""
if not utils.host_os_is('nt'):
# os.getloadavg() is not available on Windows.
table = [["Current server uptime:",
"Total server running time:",
"Total in-game time (realtime x %g):" % (gametime.TIMEFACTOR),
"Server time stamp:"
],
[utils.time_format(gametime.uptime(format=False), 2),
utils.time_format(gametime.runtime(format=False), 2),
utils.time_format(gametime.gametime(format=False), 2),
datetime.datetime.now()
]]
if utils.host_os_is('posix'):
loadavg = os.getloadavg()
string5 += "\nServer load (per minute): \t"
string5 += "{w%g%%{n" % (100 * loadavg[0])
string = "%s%s%s%s%s" % (string1, string2, string3, string4, string5)
table[0].append("Server load (per minute):")
table[1].append("{w%g%%{n" % (100 * loadavg[0]))
stable = []
for col in table:
stable.append([str(val).strip() for val in col])
ftable = utils.format_table(stable, 5)
string = ""
for row in ftable:
string += "\n " + "{w%s{n" % row[0] + "".join(row[1:])
self.caller.msg(string)
class CmdList(MuxCommand):
class CmdServerLoad(MuxCommand):
"""
@list - list info
server load statistics
Usage:
@list <option>
@serverload
Options:
process - list processes
objects - list objects
scripts - list scripts
perms - list permission keys and groups
Shows game related information depending
on which argument is given.
Show server load statistics in a table.
"""
key = "@list"
permissions = "cmd:list"
key = "@serverload"
locks = "cmd:perm(list) or perm(Immortals)"
help_category = "System"
def func(self):
"Show list."
caller = self.caller
if not self.args:
caller.msg("Usage: @list process|objects|scripts|perms")
return
string = ""
if self.arglist[0] in ["proc","process"]:
# display active processes
# display active processes
if utils.host_os_is('nt'):
string = "Feature not available on Windows."
else:
import resource
loadavg = os.getloadavg()
psize = resource.getpagesize()
rusage = resource.getrusage(resource.RUSAGE_SELF)
table = [["Server load (1 min):",
"Process ID:",
"Bytes per page:",
"Time used:",
"Integral memory:",
"Max res memory:",
"Page faults:",
"Disk I/O:",
"Network I/O",
"Context switching:"
],
["%g%%" % (100 * loadavg[0]),
"%10d" % os.getpid(),
"%10d " % psize,
"%10d" % rusage[0],
"%10d shared" % rusage[3],
"%10d pages" % rusage[2],
"%10d hard" % rusage[7],
"%10d reads" % rusage[9],
"%10d in" % rusage[12],
"%10d vol" % rusage[14]
],
["", "", "",
"(user: %g)" % rusage[1],
"%10d private" % rusage[4],
"%10d bytes" % (rusage[2] * psize),
"%10d soft" % rusage[6],
"%10d writes" % rusage[10],
"%10d out" % rusage[11],
"%10d forced" % rusage[15]
],
["", "", "", "",
"%10d stack" % rusage[5],
"",
"%10d swapouts" % rusage[8],
"", "",
"%10d sigs" % rusage[13]
]
]
stable = []
for col in table:
stable.append([str(val).strip() for val in col])
ftable = utils.format_table(stable, 5)
string = ""
for row in ftable:
string += "\n " + "{w%s{n" % row[0] + "".join(row[1:])
# string = "\n Server load (1 min) : %.2f " % loadavg[0]
# string += "\n Process ID: %10d" % os.getpid()
# string += "\n Bytes per page: %10d" % psize
# string += "\n Time used: %10d, user: %g" % (rusage[0], rusage[1])
# string += "\n Integral mem: %10d shared, %10d, private, %10d stack " % \
# (rusage[3], rusage[4], rusage[5])
# string += "\n Max res mem: %10d pages %10d bytes" % \
# (rusage[2],rusage[2] * psize)
# string += "\n Page faults: %10d hard %10d soft %10d swapouts " % \
# (rusage[7], rusage[6], rusage[8])
# string += "\n Disk I/O: %10d reads %10d writes " % \
# (rusage[9], rusage[10])
# string += "\n Network I/O: %10d in %10d out " % \
# (rusage[12], rusage[11])
# string += "\n Context swi: %10d vol %10d forced %10d sigs " % \
# (rusage[14], rusage[15], rusage[13])
elif self.arglist[0] in ["obj", "objects"]:
caller.execute_cmd("@objects")
elif self.arglist[0] in ["scr", "scripts"]:
caller.execute_cmd("@scripts")
elif self.arglist[0] in ["perm", "perms","permissions"]:
caller.execute_cmd("@perm/list")
if not utils.host_os_is('posix'):
string = "Process listings are only available under Linux/Unix."
else:
string = "'%s' is not a valid option." % self.arglist[0]
# send info
import resource
loadavg = os.getloadavg()
psize = resource.getpagesize()
rusage = resource.getrusage(resource.RUSAGE_SELF)
table = [["Server load (1 min):",
"Process ID:",
"Bytes per page:",
"Time used:",
"Integral memory:",
"Max res memory:",
"Page faults:",
"Disk I/O:",
"Network I/O",
"Context switching:"
],
["%g%%" % (100 * loadavg[0]),
"%10d" % os.getpid(),
"%10d " % psize,
"%10d" % rusage[0],
"%10d shared" % rusage[3],
"%10d pages" % rusage[2],
"%10d hard" % rusage[7],
"%10d reads" % rusage[9],
"%10d in" % rusage[12],
"%10d vol" % rusage[14]
],
["", "", "",
"(user: %g)" % rusage[1],
"%10d private" % rusage[4],
"%10d bytes" % (rusage[2] * psize),
"%10d soft" % rusage[6],
"%10d writes" % rusage[10],
"%10d out" % rusage[11],
"%10d forced" % rusage[15]
],
["", "", "", "",
"%10d stack" % rusage[5],
"",
"%10d swapouts" % rusage[8],
"", "",
"%10d sigs" % rusage[13]
]
]
stable = []
for col in table:
stable.append([str(val).strip() for val in col])
ftable = utils.format_table(stable, 5)
string = ""
for row in ftable:
string += "\n " + "{w%s{n" % row[0] + "".join(row[1:])
caller.msg(string)
#TODO - expand @ps as we add irc/imc2 support.
class CmdPs(MuxCommand):
"""
@ps - list processes
list processes
Usage
@ps
Shows the process/event table.
"""
key = "@ps"
permissions = "cmd:ps"
locks = "cmd:perm(ps) or perm(Builders)"
help_category = "System"
def func(self):
@ -651,36 +633,4 @@ class CmdPs(MuxCommand):
string += "\n{wTotal{n: %d scripts." % len(all_scripts)
self.caller.msg(string)
class CmdStats(MuxCommand):
"""
@stats - show object stats
Usage:
@stats
Shows stats about the database.
"""
key = "@stats"
aliases = "@db"
permissions = "cmd:stats"
help_category = "System"
def func(self):
"Show all stats"
# get counts for all typeclasses
stats_dict = ObjectDB.objects.object_totals()
# get all objects
stats_allobj = ObjectDB.objects.all().count()
# get all rooms
stats_room = ObjectDB.objects.filter(db_location=None).count()
# get all players
stats_users = User.objects.all().count()
string = "\n{wNumber of users:{n %i" % stats_users
string += "\n{wTotal number of objects:{n %i" % stats_allobj
string += "\n{wNumber of rooms (location==None):{n %i" % stats_room
string += "\n (Use @objects for detailed info)"
self.caller.msg(string)

View file

@ -19,8 +19,9 @@ try:
except ImportError:
from django.test import TestCase
from django.conf import settings
from src.utils import create
from src.utils import create, ansi
from src.server import session, sessionhandler
from src.locks.lockhandler import LockHandler
from src.config.models import ConfigValue
#------------------------------------------------------------
@ -29,6 +30,7 @@ from src.config.models import ConfigValue
# print all feedback from test commands (can become very verbose!)
VERBOSE = False
NOMANGLE = False
class FakeSession(session.Session):
"""
@ -55,8 +57,13 @@ class FakeSession(session.Session):
else:
rstring = return_list
self.player.character.ndb.return_string = None
if not message.startswith(rstring):
retval = "Returned message ('%s') != desired message ('%s')" % (message, rstring)
message_noansi = ansi.parse_ansi(message, strip_ansi=True).strip()
rstring = rstring.strip()
if not message_noansi.startswith(rstring):
sep1 = "\n" + "="*30 + "Wanted message" + "="*34 + "\n"
sep2 = "\n" + "="*30 + "Returned message" + "="*32 + "\n"
sep3 = "\n" + "="*78
retval = sep1 + rstring + sep2 + message_noansi + sep3
raise AssertionError(retval)
if VERBOSE:
print message
@ -77,17 +84,28 @@ class CommandTest(TestCase):
self.room2 = create.create_object(settings.BASE_ROOM_TYPECLASS, key="room2")
# create a faux player/character for testing.
self.char1 = create.create_player("TestingPlayer", "testplayer@test.com", "testpassword", location=self.room1)
self.char1 = create.create_player("TestChar", "testplayer@test.com", "testpassword", location=self.room1)
self.char1.player.user.is_superuser = True
self.char1.lock_storage = ""
self.char1.locks = LockHandler(self.char1)
self.char1.ndb.return_string = None
sess = FakeSession()
sess.connectionMade()
sess.session_login(self.char1.player)
# create second player and some objects
self.char2 = create.create_object(settings.BASE_CHARACTER_TYPECLASS, key="char2", location=self.room1)
# create second player
self.char2 = create.create_player("TestChar2", "testplayer2@test.com", "testpassword2", location=self.room1)
self.char2.player.user.is_superuser = False
self.char2.lock_storage = ""
self.char2.locks = LockHandler(self.char2)
self.char2.ndb.return_string = None
sess2 = FakeSession()
sess2.connectionMade()
sess2.session_login(self.char2.player)
# A non-player-controlled character
self.char3 = create.create_object(settings.BASE_CHARACTER_TYPECLASS, key="TestChar3", location=self.room1)
# create some objects
self.obj1 = create.create_object(settings.BASE_OBJECT_TYPECLASS, key="obj1", location=self.room1)
self.obj2 = create.create_object(settings.BASE_OBJECT_TYPECLASS, key="obj2", location=self.room1)
self.obj2 = create.create_object(settings.BASE_OBJECT_TYPECLASS, key="obj2", location=self.room1)
self.exit1 = create.create_object(settings.BASE_EXIT_TYPECLASS, key="exit1", location=self.room1)
self.exit2 = create.create_object(settings.BASE_EXIT_TYPECLASS, key="exit2", location=self.room2)
@ -110,7 +128,7 @@ class CommandTest(TestCase):
This also mangles the input in various ways to test if the command
will be fooled.
"""
if not VERBOSE:
if not VERBOSE and not NOMANGLE:
# only mangle if not VERBOSE, to make fewer return lines
test1 = re.sub(r'\s', '', raw_string) # remove all whitespace inside it
test2 = "%s/åäö öäö;-:$£@*~^' 'test" % raw_string # inserting weird characters in call
@ -129,52 +147,129 @@ class CommandTest(TestCase):
# Default set Command testing
#------------------------------------------------------------
# general.py tests
class TestLook(CommandTest):
def test_call(self):
self.execute_cmd("look here")
class TestHome(CommandTest):
def test_call(self):
self.char1.location = self.room1
self.char1.home = self.room2
self.execute_cmd("home")
self.assertEqual(self.char1.location, self.room2)
class TestLook(CommandTest):
def test_call(self):
self.execute_cmd("look here")
class TestPassword(CommandTest):
def test_call(self):
self.execute_cmd("@password testpassword = newpassword")
class TestInventory(CommandTest):
def test_call(self):
self.execute_cmd("inv")
class TestQuit(CommandTest):
def test_call(self):
self.execute_cmd("@quit")
class TestPose(CommandTest):
def test_call(self):
self.execute_cmd("pose is testing","TestChar is testing")
class TestNick(CommandTest):
def test_call(self):
self.char1.player.user.is_superuser = False
self.execute_cmd("nickname testalias = testaliasedstring1")
self.execute_cmd("nickname/player testalias = testaliasedstring2")
self.execute_cmd("nickname/object testalias = testaliasedstring3")
self.assertEquals(u"testaliasedstring1", self.char1.nickhandler("testalias"))
self.assertEquals(u"testaliasedstring2", self.char1.nickhandler("testalias",nick_type="player"))
self.assertEquals(u"testaliasedstring3", self.char1.nickhandler("testalias",nick_type="object"))
self.assertEquals(u"testaliasedstring1", self.char1.nicks.get("testalias"))
self.assertEquals(u"testaliasedstring2", self.char1.nicks.get("testalias",nick_type="player"))
self.assertEquals(u"testaliasedstring3", self.char1.nicks.get("testalias",nick_type="object"))
class TestGet(CommandTest):
def test_call(self):
self.obj1.location = self.room1
self.execute_cmd("get obj1", "You pick up obj1.")
class TestDrop(CommandTest):
def test_call(self):
self.obj1.location = self.char1
self.execute_cmd("drop obj1", "You drop obj1.")
class TestWho(CommandTest):
def test_call(self):
self.execute_cmd("who")
class TestSay(CommandTest):
def test_call(self):
self.execute_cmd("say Hello", 'You say, "Hello')
class TestAccess(CommandTest):
def test_call(self):
self.execute_cmd("access")
class TestEncoding(CommandTest):
def test_call(self):
self.execute_cmd("@encoding", "Supported encodings")
# help.py command tests
class TestHelpSystem(CommandTest):
def test_call(self):
global NOMANGLE
NOMANGLE = True
sep = "-"*70 + "\n"
self.execute_cmd("@help/add TestTopic,TestCategory = Test1", )
self.execute_cmd("help TestTopic",sep + "Help topic for Testtopic\nTest1")
self.execute_cmd("@help/merge TestTopic = Test2", "Added the new text right after")
self.execute_cmd("help TestTopic", sep + "Help topic for Testtopic\nTest1 Test2")
self.execute_cmd("@help/append TestTopic = Test3", "Added the new text as a")
self.execute_cmd("help TestTopic",sep + "Help topic for Testtopic\nTest1 Test2\n\nTest3")
self.execute_cmd("@help/delete TestTopic","Deleted the help entry")
self.execute_cmd("help TestTopic","No help entry found for 'TestTopic'")
NOMANGLE = False
# system.py command tests
class TestPy(CommandTest):
def test_call(self):
self.execute_cmd("@py 1+2", [">>> 1+2", "<<< 3"])
class TestListScripts(CommandTest):
class TestScripts(CommandTest):
def test_call(self):
self.execute_cmd("@scripts")
class TestListObjects(CommandTest):
self.execute_cmd("@scripts", "id")
class TestObjects(CommandTest):
def test_call(self):
self.execute_cmd("@objects")
class TestListService(CommandTest):
def test_call(self):
self.execute_cmd("@service")
self.execute_cmd("@objects", "Database totals")
# Cannot be tested since we don't have an active server running at this point.
# class TestListService(CommandTest):
# def test_call(self):
# self.execute_cmd("@service/list", "---")
class TestVersion(CommandTest):
def test_call(self):
self.execute_cmd("@version")
self.execute_cmd("@version", '---')
class TestTime(CommandTest):
def test_call(self):
self.execute_cmd("@time")
class TestList(CommandTest):
self.execute_cmd("@time", "Current server uptime")
class TestServerLoad(CommandTest):
def test_call(self):
self.execute_cmd("@list")
self.execute_cmd("@serverload", "Server load")
class TestPs(CommandTest):
def test_call(self):
self.execute_cmd("@ps","\n{wNon-timed scripts")
class TestStats(CommandTest):
self.execute_cmd("@ps","Non-timed scripts")
# admin.py command tests
class TestBoot(CommandTest):
def test_call(self):
self.execute_cmd("@boot TestChar2","You booted TestChar2.")
class TestDelPlayer(CommandTest):
def test_call(self):
self.execute_cmd("@delplayer TestChar2","Booting and informing player ...")
class TestEmit(CommandTest):
def test_call(self):
self.execute_cmd("@stats")
self.execute_cmd("@emit Test message", "Emitted to room1.")
class TestUserPassword(CommandTest):
def test_call(self):
self.execute_cmd("@userpassword TestChar2 = newpass", "TestChar2 - new password set to 'newpass'.")
class TestPerm(CommandTest):
def test_call(self):
self.execute_cmd("@perm TestChar2 = Builders", "Permission 'Builders' given to")
# cannot test this at the moment, screws up the test suite
#class TestPuppet(CommandTest):
# def test_call(self):
# self.execute_cmd("@puppet TestChar3", "You now control TestChar3.")
# self.execute_cmd("@puppet TestChar", "You now control TestChar.")
class TestWall(CommandTest):
def test_call(self):
self.execute_cmd("@wall = This is a test message", "TestChar shouts")
# building.py command tests
#TODO

View file

@ -25,6 +25,7 @@ class CmdConnect(MuxCommand):
"""
key = "connect"
aliases = ["conn", "con", "co"]
locks = "cmd:all()" # not really needed
def func(self):
"""
@ -116,6 +117,7 @@ class CmdCreate(MuxCommand):
"""
key = "create"
aliases = ["cre", "cr"]
locks = "cmd:all()"
def parse(self):
"""
@ -193,6 +195,9 @@ class CmdCreate(MuxCommand):
location=default_home,
typeclass=typeclass,
home=default_home)
# character safety features
new_character.locks.delete("get")
new_character.locks.add("get:perm(Wizards)")
# set a default description
new_character.db.desc = "This is a Player."
@ -227,6 +232,7 @@ class CmdQuit(MuxCommand):
"""
key = "quit"
aliases = ["q", "qu"]
locks = "cmd:all()"
def func(self):
"Simply close the connection."
@ -241,6 +247,7 @@ class CmdUnconnectedLook(MuxCommand):
"""
key = "look"
aliases = "l"
locks = "cmd:all()"
def func(self):
"Show the connect screen."
@ -259,6 +266,7 @@ class CmdUnconnectedHelp(MuxCommand):
"""
key = "help"
aliases = ["h", "?"]
locks = "cmd:all()"
def func(self):
"Shows help"

View file

@ -6,7 +6,6 @@ This defines some test commands for use while testing the MUD and its components
from django.conf import settings
from django.db import IntegrityError
from src.comms.models import Msg
from src.permissions import permissions
from src.utils import create, debug, utils
from src.commands.default.muxcommand import MuxCommand
@ -29,7 +28,7 @@ class CmdTest(MuxCommand):
key = "@test"
aliases = ["@te", "@test all"]
help_category = "Utils"
permissions = "cmd:Immortals" #Wizards
locks = "cmd:perm(Wizards)"
# the muxcommand class itself handles the display
# so we just defer to it by not adding any function.
@ -62,131 +61,6 @@ class CmdTest(MuxCommand):
#self.caller.msg("Imported %s" % cmdsetname)
#self.caller.msg(cmdsethandler.CACHED_CMDSETS)
class CmdTestPerms(MuxCommand):
"""
Test command - test permissions
Usage:
@testperm [[lockstring] [=permstring]]
With no arguments, runs a sequence of tests for the
permission system using the calling player's permissions.
If <lockstring> is given, match caller's permissions
against these locks. If also <permstring> is given,
match this against the given locks instead.
"""
key = "@testperm"
permissions = "cmd:Immortals Wizards"
help_category = "Utils"
def func(self):
"""
Run tests
"""
caller = self.caller
if caller.user.is_superuser:
caller.msg("You are a superuser. Permission tests are pointless.")
return
# create a test object
obj = create.create_object(None, "accessed_object") # this will use default typeclass
obj_id = obj.id
caller.msg("obj_attr: %s" % obj.attr("testattr"))
# perms = ["has_permission", "has permission", "skey:has_permission",
# "has_id(%s)" % obj_id, "has_attr(testattr)",
# "has_attr(testattr, testattr_value)"]
# test setting permissions
uprofile = caller.user.get_profile()
# do testing
caller.msg("----------------")
permissions.set_perm(obj, "has_permission")
permissions.add_perm(obj, "skey:has_permission")
caller.msg(" keys:[%s] locks:[%s]" % (uprofile.permissions, obj.permissions))
caller.msg("normal permtest: %s" % permissions.has_perm(uprofile, obj))
caller.msg("skey permtest: %s" % permissions.has_perm(uprofile, obj, 'skey'))
permissions.set_perm(uprofile, "has_permission")
caller.msg(" keys:[%s] locks:[%s]" % (uprofile.permissions, obj.permissions))
caller.msg("normal permtest: %s" % permissions.has_perm(uprofile, obj))
caller.msg("skey permtest: %s" % permissions.has_perm(uprofile, obj, 'skey'))
# function tests
permissions.set_perm(obj, "has_id(%s)" % (uprofile.id))
caller.msg(" keys:[%s] locks:[%s]" % (uprofile.permissions, obj.permissions))
caller.msg("functest: %s" % permissions.has_perm(uprofile, obj))
uprofile.attr("testattr", "testattr_value")
permissions.set_perm(obj, "has_attr(testattr, testattr_value)")
caller.msg(" keys:[%s] locks:[%s]" % (uprofile.permissions, obj.permissions))
caller.msg("functest: %s" % permissions.has_perm(uprofile, obj))
# cleanup of test permissions
permissions.del_perm(uprofile, "has_permission")
caller.msg(" cleanup: keys:[%s] locks:[%s]" % (uprofile.permissions, obj.permissions))
obj.delete()
uprofile.attr("testattr", delete=True)
# # Add/remove states (removed; not valid.)
# EXAMPLE_STATE="game.gamesrc.commands.examples.example.EXAMPLESTATE"
# class CmdTestState(MuxCommand):
# """
# Test command - add a state.
# Usage:
# @teststate[/switch] [<python path to state instance>]
# Switches:
# add - add a state
# clear - remove all added states.
# list - view current state stack
# reload - reload current state stack
# If no python path is given, an example state will be added.
# You will know it worked if you can use the commands '@testcommand'
# and 'smile'.
# """
# key = "@teststate"
# alias = "@testingstate"
# permissions = "cmd:Immortals Wizards"
# def func(self):
# """
# inp is the dict returned from MuxCommand's parser.
# """
# caller = self.caller
# switches = self.switches
# if not switches or switches[0] not in ["add", "clear", "list", "reload"]:
# string = "Usage: @teststate[/add|clear|list|reload] [<python path>]"
# caller.msg(string)
# elif "clear" in switches:
# caller.cmdset.clear()
# caller.msg("All cmdset cleared.")
# return
# elif "list" in switches:
# string = "%s" % caller.cmdset
# caller.msg(string)
# elif "reload" in switches:
# caller.cmdset.load()
# caller.msg("Cmdset reloaded.")
# else: #add
# arg = inp["raw"]
# if not arg:
# arg = EXAMPLE_STATE
# caller.cmdset.add(arg)
# string = "Added state '%s'." % caller.cmdset.state.key
# caller.msg(string)
class TestCom(MuxCommand):
"""
Test the command system
@ -195,7 +69,7 @@ class TestCom(MuxCommand):
@testcom/create/list [channel]
"""
key = "@testcom"
permissions = "cmd:Immortals Wizards"
locks = "cmd:perm(Wizards)"
help_category = "Utils"
def func(self):
"Run the test program"

View file

@ -25,14 +25,14 @@ does this for you.
"""
from src.comms.models import Channel, Msg
from src.commands import cmdset, command
from src.permissions.permissions import has_perm
from src.utils import utils
class ChannelCommand(command.Command):
"""
Channel
Usage:
<channel name or alias> <message>
<channel name or alias> <message>
This is a channel. If you have subscribed to it, you can send to
it by entering its name or alias, followed by the text you want to
@ -41,17 +41,17 @@ class ChannelCommand(command.Command):
# this flag is what identifies this cmd as a channel cmd
# and branches off to the system send-to-channel command
# (which is customizable by admin)
is_channel = True
key = "general"
help_category = "Channel Names"
permissions = "cmd:use_channels"
is_channel = True
locks = "cmd:all()"
obj = None
def parse(self):
"""
Simple parser
"""
channelname, msg = self.args.split(":", 1)
channelname, msg = self.args.split(":", 1) # cmdhandler sends channame:msg here.
self.args = (channelname.strip(), msg.strip())
def func(self):
@ -73,7 +73,7 @@ class ChannelCommand(command.Command):
string = "You are not connected to channel '%s'."
caller.msg(string % channelkey)
return
if not has_perm(caller, channel, 'chan_send'):
if not channel.access(caller, 'send'):
string = "You are not permitted to send to channel '%s'."
caller.msg(string % channelkey)
return
@ -102,6 +102,25 @@ class ChannelHandler(object):
"""
self.cached_channel_cmds = []
def _format_help(self, channel):
"builds a doc string"
key = channel.key
aliases = channel.aliases
if not utils.is_iter(aliases):
aliases = [aliases]
ustring = "%s <message>" % key.lower() + "".join(["\n %s <message>" % alias.lower() for alias in aliases])
desc = channel.desc
string = \
"""
Channel '%s'
Usage (not including your personal aliases):
%s
%s
""" % (key, ustring, desc)
return string
def add_channel(self, channel):
"""
Add an individual channel to the handler. This should be
@ -113,8 +132,12 @@ class ChannelHandler(object):
cmd = ChannelCommand()
cmd.key = channel.key.strip().lower()
cmd.obj = channel
cmd.__doc__= self._format_help(channel)
if channel.aliases:
cmd.aliases = channel.aliases
cmd.lock_storage = "cmd:all();%s" % channel.locks
cmd.lockhandler.reset()
self.cached_channel_cmds.append(cmd)
def update(self):
@ -133,8 +156,9 @@ class ChannelHandler(object):
chan_cmdset.key = '_channelset'
chan_cmdset.priority = 10
chan_cmdset.duplicates = True
for cmd in [cmd for cmd in self.cached_channel_cmds
if has_perm(source_object, cmd, 'chan_send')]:
for cmd in [cmd for cmd in self.cached_channel_cmds
if cmd.access(source_object, 'listen')]:
chan_cmdset.add(cmd)
return chan_cmdset

View file

@ -56,7 +56,7 @@ class MsgManager(models.Manager):
try:
idnum = int(idnum)
return self.get(id=id)
except:
except Exception:
return None
def get_messages_by_sender(self, player):
@ -259,7 +259,10 @@ class ChannelManager(models.Manager):
pass
if not channels:
# no id match. Search on the key.
channels = self.filter(db_key=ostring)
channels = self.filter(db_key__iexact=ostring)
if not channels:
# still no match. Search by alias.
channels = [channel for channel in self.all() if ostring.lower in [a.lower for a in channel.aliases]]
return channels
#

View file

@ -16,10 +16,9 @@ be able to delete connections on the fly).
from django.db import models
from src.utils.idmapper.models import SharedMemoryModel
from src.players.models import PlayerDB
from src.server.sessionhandler import SESSIONS
from src.comms import managers
from src.permissions.permissions import has_perm
from src.locks.lockhandler import LockHandler
from src.utils.utils import is_iter
from src.utils.utils import dbref as is_dbref
@ -81,7 +80,6 @@ class Msg(SharedMemoryModel):
permissions - perm strings
"""
from src.players.models import PlayerDB
#
# Msg database model setup
#
@ -90,7 +88,7 @@ class Msg(SharedMemoryModel):
# named same as the field, but withtout the db_* prefix.
# There must always be one sender of the message.
db_sender = models.ForeignKey(PlayerDB, related_name='sender_set')
db_sender = models.ForeignKey("players.PlayerDB", related_name='sender_set')
# The destination objects of this message. Stored as a
# comma-separated string of object dbrefs. Can be defined along
# with channels below.
@ -103,6 +101,8 @@ class Msg(SharedMemoryModel):
# should itself handle eventual headers etc.
db_message = models.TextField()
db_date_sent = models.DateTimeField(editable=False, auto_now_add=True)
# lock storage
db_lock_storage = models.TextField(blank=True)
# These are settable by senders/receivers/channels respectively.
# Stored as a comma-separated string of dbrefs. Can be used by the
# game to mask out messages from being visible in the archive (no
@ -110,12 +110,16 @@ class Msg(SharedMemoryModel):
db_hide_from_sender = models.BooleanField(default=False)
db_hide_from_receivers = models.CharField(max_length=255, null=True, blank=True)
db_hide_from_channels = models.CharField(max_length=255, null=True, blank=True)
# permission strings, separated by commas
db_permissions = models.CharField(max_length=255, blank=True)
# Storage of lock strings
db_lock_storage = models.TextField(null=True)
# Database manager
objects = managers.MsgManager()
def __init__(self, *args, **kwargs):
SharedMemoryModel.__init__(self, *args, **kwargs)
self.locks = LockHandler(self)
class Meta:
"Define Django meta options"
verbose_name = "Message"
@ -278,29 +282,25 @@ class Msg(SharedMemoryModel):
self.save()
hide_from_channels = property(hide_from_channels_get, hide_from_channels_set, hide_from_channels_del)
# permissions property
#@property
def permissions_get(self):
"Getter. Allows for value = self.permissions. Returns a list of permissions."
if self.db_permissions:
return [perm.strip() for perm in self.db_permissions.split(',')]
return []
#@permissions.setter
def permissions_set(self, value):
"Setter. Allows for self.permissions = value. Stores as a comma-separated string."
if is_iter(value):
value = ",".join([str(val).strip().lower() for val in value])
self.db_permissions = value
self.save()
#@permissions.deleter
def permissions_del(self):
"Deleter. Allows for del self.permissions"
self.db_permissions = ""
# lock_storage property (wraps db_lock_storage)
#@property
def lock_storage_get(self):
"Getter. Allows for value = self.lock_storage"
return self.db_lock_storage
#@nick.setter
def lock_storage_set(self, value):
"""Saves the lock_storagetodate. This is usually not called directly, but through self.lock()"""
self.db_lock_storage = value
self.save()
permissions = property(permissions_get, permissions_set, permissions_del)
#@nick.deleter
def lock_storage_del(self):
"Deleter is disabled. Use the lockhandler.delete (self.lock.delete) instead"""
logger.log_errmsg("Lock_Storage (on %s) cannot be deleted. Use obj.lock.delete() instead." % self)
lock_storage = property(lock_storage_get, lock_storage_set, lock_storage_del)
#
# Msg class method
# Msg class methods
#
def __str__(self):
@ -313,6 +313,15 @@ class Msg(SharedMemoryModel):
return "%s -> %s: %s" % (self.sender.key,
", ".join([rec.key for rec in self.receivers]),
self.message)
def access(self, accessing_obj, access_type='read', default=False):
"""
Determines if another object has permission to access.
accessing_obj - object trying to access this one
access_type - type of access sought
default - what to return if no lock of access_type was found
"""
return self.locks.check(accessing_obj, access_type=access_type, default=default)
#------------------------------------------------------------
#
@ -351,12 +360,16 @@ class Channel(SharedMemoryModel):
db_aliases = models.CharField(max_length=255)
# Whether this channel should remember its past messages
db_keep_log = models.BooleanField(default=True)
# Permission strings, separated by commas
db_permissions = models.CharField(max_length=255, blank=True)
# Storage of lock definitions
db_lock_storage = models.TextField(blank=True)
# Database manager
objects = managers.ChannelManager()
def __init__(self, *args, **kwargs):
SharedMemoryModel.__init__(self, *args, **kwargs)
self.locks = LockHandler(self)
# Wrapper properties to easily set database fields. These are
# @property decorators that allows to access these fields using
# normal python operations (without having to remember to save()
@ -436,26 +449,21 @@ class Channel(SharedMemoryModel):
self.save()
keep_log = property(keep_log_get, keep_log_set, keep_log_del)
# permissions property
#@property
def permissions_get(self):
"Getter. Allows for value = self.permissions. Returns a list of permissions."
if self.db_permissions:
return [perm.strip() for perm in self.db_permissions.split(',')]
return []
#@permissions.setter
def permissions_set(self, value):
"Setter. Allows for self.permissions = value. Stores as a comma-separated string."
if is_iter(value):
value = ",".join([str(val).strip().lower() for val in value])
self.db_permissions = value
self.save()
#@permissions.deleter
def permissions_del(self):
"Deleter. Allows for del self.permissions"
self.db_permissions = ""
# lock_storage property (wraps db_lock_storage)
#@property
def lock_storage_get(self):
"Getter. Allows for value = self.lock_storage"
return self.db_lock_storage
#@nick.setter
def lock_storage_set(self, value):
"""Saves the lock_storagetodate. This is usually not called directly, but through self.lock()"""
self.db_lock_storage = value
self.save()
permissions = property(permissions_get, permissions_set, permissions_del)
#@nick.deleter
def lock_storage_del(self):
"Deleter is disabled. Use the lockhandler.delete (self.lock.delete) instead"""
logger.log_errmsg("Lock_Storage (on %s) cannot be deleted. Use obj.lock.delete() instead." % self)
lock_storage = property(lock_storage_get, lock_storage_set, lock_storage_del)
class Meta:
"Define Django meta options"
@ -515,7 +523,7 @@ class Channel(SharedMemoryModel):
def connect_to(self, player):
"Connect the user to this channel"
if not has_perm(player, self, 'chan_listen'):
if not self.access(player, 'listen'):
return False
conn = ChannelConnection.objects.create_connection(player, self)
if conn:
@ -531,7 +539,14 @@ class Channel(SharedMemoryModel):
for connection in Channel.objects.get_all_connections(self):
connection.delete()
super(Channel, self).delete()
def access(self, accessing_obj, access_type='listen', default=False):
"""
Determines if another object has permission to access.
accessing_obj - object trying to access this one
access_type - type of access sought
default - what to return if no lock of access_type was found
"""
return self.locks.check(accessing_obj, access_type=access_type, default=default)
class ChannelConnection(SharedMemoryModel):
"""
@ -539,9 +554,8 @@ class ChannelConnection(SharedMemoryModel):
The advantage of making it like this is that one can easily
break the connection just by deleting this object.
"""
from src.players.models import PlayerDB
# Player connected to a channel
db_player = models.ForeignKey(PlayerDB)
db_player = models.ForeignKey("players.PlayerDB")
# Channel the player is connected to
db_channel = models.ForeignKey(Channel)

View file

@ -13,6 +13,7 @@ from django.db import models
from src.utils.idmapper.models import SharedMemoryModel
from src.help.manager import HelpEntryManager
from src.utils import ansi
from src.locks.lockhandler import LockHandler
from src.utils.utils import is_iter
#------------------------------------------------------------
@ -48,13 +49,18 @@ class HelpEntry(SharedMemoryModel):
db_entrytext = models.TextField(blank=True)
# a string of permissionstrings, separated by commas.
db_permissions = models.CharField(max_length=255, blank=True)
# lock string storage
db_lock_storage = models.TextField(blank=True)
# (deprecated, only here to allow MUX helpfile load (don't use otherwise)).
# TODO: remove this when not needed anymore.
db_staff_only = models.BooleanField(default=False)
# Database manager
objects = HelpEntryManager()
def __init__(self, *args, **kwargs):
SharedMemoryModel.__init__(self, *args, **kwargs)
self.locks = LockHandler(self)
class Meta:
"Define Django meta options"
@ -138,6 +144,23 @@ class HelpEntry(SharedMemoryModel):
self.save()
permissions = property(permissions_get, permissions_set, permissions_del)
# lock_storage property (wraps db_lock_storage)
#@property
def lock_storage_get(self):
"Getter. Allows for value = self.lock_storage"
return self.db_lock_storage
#@nick.setter
def lock_storage_set(self, value):
"""Saves the lock_storagetodate. This is usually not called directly, but through self.lock()"""
self.db_lock_storage = value
self.save()
#@nick.deleter
def lock_storage_del(self):
"Deleter is disabled. Use the lockhandler.delete (self.lock.delete) instead"""
logger.log_errmsg("Lock_Storage (on %s) cannot be deleted. Use obj.lock.delete() instead." % self)
lock_storage = property(lock_storage_get, lock_storage_set, lock_storage_del)
#
#
# HelpEntry main class methods
@ -149,3 +172,12 @@ class HelpEntry(SharedMemoryModel):
def __unicode__(self):
return u'%s' % self.key
def access(self, accessing_obj, access_type='read', default=False):
"""
Determines if another object has permission to access.
accessing_obj - object trying to access this one
access_type - type of access sought
default - what to return if no lock of access_type was found
"""
return self.locks.check(accessing_obj, access_type=access_type, default=default)

View file

@ -3,18 +3,18 @@ This module provides a set of permission lock functions for use
with Evennia's permissions system.
To call these locks, make sure this module is included in the
settings tuple PERMISSION_FUNC_MODULES then define a permission
string of the form 'myfunction(myargs)' and store it in the
'permissions' field or variable on your object/command/channel/whatever.
As part of the permission check, such permission strings will be
evaluated to call myfunction(checking_obj, checked_obj, *yourargs) in
this module. A boolean value is expected back.
settings tuple PERMISSION_FUNC_MODULES then define a lock on the form
'<access_type>:func(args)' and add it to the object's lockhandler.
Run the check method of the handler to execute the lock check.
Note that checking_obj and checked_obj can be any object type
with a permissions variable/field, so be careful to not expect
Note that accessing_obj and accessed_obj can be any object type
with a lock variable/field, so be careful to not expect
a certain object type.
MUX locks
Below is a list nicked from the MUX docs on the locks available
@ -100,42 +100,64 @@ DefaultLock: Exits: controls who may traverse the exit to
"""
from src.permissions.permissions import get_types, has_perm, has_perm_string
from django.conf import settings
from src.utils import search
PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
def noperm(checking_obj, checked_obj, *args):
"""
Usage:
noperm(mypermstring)
noperm(perm1, perm2, perm3, ...)
A negative permission; this will return False only if
the checking object *has any* of the given permission(s), True
otherwise. The searched permission cannot itself be a
function-permission (i.e. you cannot wrap functions in
functions).
"""
if not args:
# this is an always-false permission
return False
return not has_perm_string(checking_obj, args)
def is_superuser(checking_obj, checked_obj, *args):
"""
Usage:
is_superuser()
Determines if the checking object is superuser.
"""
if hasattr(checking_obj, 'is_superuser'):
return checking_obj.is_superuser
def true(*args, **kwargs):
"Always returns True."
return True
def all(*args, **kwargs):
return True
def false(*args, **kwargs):
"Always returns False"
return False
def none(*args, **kwargs):
return False
def has_id(checking_obj, checked_obj, *args):
def perm(accessing_obj, accessed_obj, *args, **kwargs):
"""
The basic permission-checker. Ignores case.
Usage:
perm(<permission>)
where <permission> is the permission accessing_obj must
have in order to pass the lock. If the given permission
is part of PERMISSION_HIERARCHY, permission is also granted
to all ranks higher up in the hierarchy.
"""
if not args:
return False
perm = args[0].lower()
if hasattr(accessing_obj, 'permissions'):
if perm in [p.lower() for p in accessing_obj.permissions]:
# simplest case - we have a direct match
return True
if perm in PERMISSION_HIERARCHY:
# check if we have a higher hierarchy position
ppos = PERMISSION_HIERARCHY.index(perm)
return any(True for hpos, hperm in enumerate(PERMISSION_HIERARCHY)
if hperm in [p.lower() for p in accessing_obj.permissions] and hpos > ppos)
return False
def perm_above(accessing_obj, accessed_obj, *args, **kwargs):
"""
Only allow objects with a permission *higher* in the permission
hierarchy than the one given. If there is no such higher rank,
it's assumed we refer to superuser. If no hierarchy is defined,
this function has no meaning and returns False.
"""
if args and args[0].lower() in PERMISSION_HIERARCHY:
ppos = PERMISSION_HIERARCHY.index(args[0].lower())
return any(True for hpos, hperm in enumerate(PERMISSION_HIERARCHY)
if hperm in [p.lower() for p in accessing_obj.permissions] and hpos > ppos)
def dbref(accessing_obj, accessed_obj, *args, **kwargs):
"""
Usage:
has_id(3)
dbref(3)
This lock type checks if the checking object
has a particular dbref. Note that this only
@ -145,19 +167,29 @@ def has_id(checking_obj, checked_obj, *args):
if not args:
return False
try:
dbref = int(args[0].strip())
dbref = int(args[0].strip().strip('#'))
except ValueError:
return False
if hasattr(checking_obj, 'id'):
return dbref == checking_obj.id
if hasattr(accessing_obj, 'id'):
return dbref == accessing_obj.id
return False
def has_attr(checking_obj, checked_obj, *args):
def id(accessing_obj, accessed_obj, *args, **kwargs):
"Alias to dbref"
return dbref(accessing_obj, accessed_obj, *args, **kwargs)
def attr(accessing_obj, accessed_obj, *args, **kwargs):
"""
Usage:
has_attr(attrname)
has_attr(attrname, value)
has_attr(attrname, value, compare=type)
where compare's type is one of (eq,gt,lt,ge,le,ne) and signifies
how the value should be compared with one on accessing_obj (so
compare=gt means the accessing_obj must have a value greater than
the one given).
Searches attributes *and* properties stored on the checking
object. The first form works like a flag - if the attribute/property
exists on the object, it returns True. The second form also requires
@ -171,18 +203,101 @@ def has_attr(checking_obj, checked_obj, *args):
value = None
if len(args) > 1:
value = args[1].strip()
# first, look for normal properties on the object trying to gain access
if hasattr(checking_obj, attrname):
compare = 'eq'
if kwargs:
compare = kwargs.get('compare', 'eq')
def valcompare(val1, val2, typ='eq'):
"compare based on type"
try:
if typ == 'eq':
return val1 == val2 or int(val1) == int(val2)
elif typ == 'gt':
return int(val1) > int(val2)
elif typ == 'lt':
return int(val1) < int(val2)
elif typ == 'ge':
return int(val1) >= int(val2)
elif typ == 'le':
return int(val1) <= int(val2)
elif typ == 'ne':
return int(val1) != int(val2)
else:
return False
except Exception, e:
print e
# this might happen if we try to compare two things that cannot be compared
return False
# first, look for normal properties on the object trying to gain access
if hasattr(accessing_obj, attrname):
if value:
return str(getattr(checking_obj, attrname)) == value
return valcompare(str(getattr(accessing_obj, attrname)), value, compare)
return True
# check attributes, if they exist
#print "lockfunc default: %s (%s)" % (checking_obj, attrname)
if hasattr(checking_obj, 'has_attribute') \
and checking_obj.has_attribute(attrname):
# check attributes, if they exist
if (hasattr(accessing_obj, 'has_attribute')
and accessing_obj.has_attribute(attrname)):
if value:
return hasattr(checking_obj, 'attr') \
and checking_obj.attr(attrname) == value
return (hasattr(accessing_obj, 'get_attribute')
and valcompare(accessing_obj.get_attribute(attrname), value, compare))
return True
return False
def attr_eq(accessing_obj, accessed_obj, *args, **kwargs):
"""
Usage:
attr_gt(attrname, 54)
"""
return attr(accessing_obj, accessed_obj, *args, **kwargs)
def attr_gt(accessing_obj, accessed_obj, *args, **kwargs):
"""
Usage:
attr_gt(attrname, 54)
Only true if access_obj's attribute > the value given.
"""
return attr(accessing_obj, accessed_obj, *args, **{'compare':'gt'})
def attr_ge(accessing_obj, accessed_obj, *args, **kwargs):
"""
Usage:
attr_gt(attrname, 54)
Only true if access_obj's attribute >= the value given.
"""
return attr(accessing_obj, accessed_obj, *args, **{'compare':'ge'})
def attr_lt(accessing_obj, accessed_obj, *args, **kwargs):
"""
Usage:
attr_gt(attrname, 54)
Only true if access_obj's attribute < the value given.
"""
return attr(accessing_obj, accessed_obj, *args, **{'compare':'lt'})
def attr_le(accessing_obj, accessed_obj, *args, **kwargs):
"""
Usage:
attr_gt(attrname, 54)
Only true if access_obj's attribute <= the value given.
"""
return attr(accessing_obj, accessed_obj, *args, **{'compare':'le'})
def attr_ne(accessing_obj, accessed_obj, *args, **kwargs):
"""
Usage:
attr_gt(attrname, 54)
Only true if access_obj's attribute != the value given.
"""
return attr(accessing_obj, accessed_obj, *args, **{'compare':'ne'})
def superuser(*args, **kwargs):
"""
Only accepts an accesing_obj that is superuser (e.g. user #1)
Since a superuser would not ever reach this check (superusers
bypass the lock entirely), any user who gets this far cannot be a
superuser, hence we just return False. :)
"""
return False

367
src/locks/lockhandler.py Normal file
View file

@ -0,0 +1,367 @@
"""
Locks
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 two three parts:
- access_type - this defines what kind of access this lock regulates. This
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
set of allowed arguments. They should always return a boolean depending
on if they allow access or not.
# Lock function
A lock function is defined by existing in one of the modules
listed by settings.LOCK_FUNC_MODULES. It should also always
take four arguments looking like this:
funcname(accessing_obj, accessed_obj, *args, **kwargs):
[...]
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
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]
return desired_perm in accessing_obj.permissions
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.
# Lock definition
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:
'access_type:[NOT] func1(args)[ AND|OR][NOT] func2() ...'
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
for 'edit', it depends on what the command is looking for). We want this to
only work for those with the Permission 'Builder'. So we use our lock
function above and call it like this:
'edit:perm(Builder)'
Here, the lock-function perm() will be called (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 Builder and a GoodGuy, we
could use AND:
'edit:perm(Builder) 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
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:
obj.lockhandler.add('edit:perm(Builder)')
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'):
caller.msg("Sorry you cannot edit that.")
# Permissions
Permissions are just text strings stored in a comma-separated list on
typeclassed objects. The default perm() lock function uses them,
taking into account settings.PERMISSION_HIERARCHY. Also, the
restricted @perm command sets them, but otherwise they are identical
to any other identifier you can use.
"""
import re, inspect
from django.conf import settings
from src.utils import logger, utils
#
# Cached lock functions
#
LOCKFUNCS = {}
def cache_lockfuncs():
"Updates the cache."
global LOCKFUNCS
LOCKFUNCS = {}
for modulepath in settings.LOCK_FUNC_MODULES:
modulepath = utils.pypath_to_realpath(modulepath)
mod = utils.mod_import(modulepath)
if mod:
for tup in (tup for tup in inspect.getmembers(mod) if callable(tup[1])):
LOCKFUNCS[tup[0]] = tup[1]
else:
logger.log_errmsg("Couldn't load %s from PERMISSION_FUNC_MODULES." % modulepath)
#
# 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")
#
#
# Lock handler
#
#
class LockHandler(object):
"""
This handler should be attached to all objects implementing
permission checks, under the property 'lockhandler'.
"""
def __init__(self, obj):
"""
Loads and pre-caches all relevant locks and their
functions.
"""
if not LOCKFUNCS:
cache_lockfuncs()
self.obj = obj
self.locks = {}
self.log_obj = None
self.no_errors = True
self.reset_flag = False
self._cache_locks(self.obj.lock_storage)
def __str__(self):
return ";".join(self.locks[key][2] for key in sorted(self.locks))
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)
elif hasattr(self.obj, 'msg'):
self.obj.msg(message)
else:
logger.log_trace("%s: %s" % (self.obj, message))
def _parse_lockstring(self, storage_lockstring):
"""
Helper function.
locks are stored as a string 'atype:[NOT] lock()[[ AND|OR [NOT] lock() [...]];atype...
"""
locks = {}
if not storage_lockstring:
return locks
nlocks = storage_lockstring.count(';') + 1
duplicates = 0
elist = []
for raw_lockstring in storage_lockstring.split(';'):
lock_funcs = []
access_type, rhs = (part.strip() for part in raw_lockstring.split(':', 1))
# parse the lock functions and separators
funclist = RE_FUNCS.findall(rhs)
evalstring = rhs.replace('AND','and').replace('OR','or').replace('NOT','not')
nfuncs = len(funclist)
for funcstring in funclist:
funcname, rest = [part.strip().strip(')') for part in funcstring.split('(', 1)]
func = LOCKFUNCS.get(funcname, None)
if not callable(func):
elist.append("Lock: function '%s' is not available." % funcstring)
continue
args = [arg.strip() for arg in rest.split(',') if not '=' 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:
continue
try:
# purge the eval string of any superfluos items, then test it
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:
duplicates += 1
elist.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)
if elist:
self._log_error("\n".join(elist))
self.no_errors = False
return locks
def _cache_locks(self, storage_lockstring):
"""Store data"""
self.locks = self._parse_lockstring(storage_lockstring)
def _save_locks(self):
"Store locks to obj"
self.obj.lock_storage = ";".join([tup[2] for tup in self.locks.values()])
def add(self, lockstring, log_obj=None):
"""
Add a new, single lockstring on the form '<access_type>:<functions>'
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(';'):
if not ':' in lockstring:
self._log_error("Lock: '%s' contains no colon (:)." % lockdef)
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
if rhs.count('(') != rhs.count(')'):
self._log_error("Lock: '%s' has mismatched parentheses." % lockdef)
return False
if not RE_FUNCS.findall(rhs):
self._log_error("Lock: '%s' has no valid lock functions." % lockdef)
return False
# get the lock string
storage_lockstring = self.obj.lock_storage
if storage_lockstring:
storage_lockstring = storage_lockstring + ";" + lockstring
else:
storage_lockstring = lockstring
# 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
def get(self, access_type):
"get the lockstring of a particular type"
return self.locks.get(access_type, None)
def delete(self, access_type):
"Remove a lock from the handler"
if access_type in self.locks:
del self.locks[access_type]
self._save_locks()
return True
return False
def clear(self):
"Remove all locks"
self.locks = {}
self.lock_storage = ""
def reset(self):
"""
Set the reset flag, so the the lock will be re-cached at next checking.
This is usually set by @reload.
"""
self.reset_flag = True
def check(self, accessing_obj, access_type, default=False):
"""
Checks a lock of the correct type by passing execution
off to the lock function(s).
accessing_obj - the object seeking access
access_type - the type of access wanted
default - if no suitable lock type is found, use this
"""
if self.reset_flag:
# rebuild cache
self._cache_locks(self.obj.lock_storage)
self.reset_flag = False
if (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)):
# we grant access to superusers and also to protocol instances that not yet has any player assigned to them (the
# latter is a safety feature since superuser cannot be authenticated at some point during the connection).
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 previously stored the function object and all the args/kwargs as list/dict,
# now we just execute them all in sequence. The result will be a list of True/False
# statements. Note that there is no eval here, these are normal command calls!
true_false = (tup[0](accessing_obj, self.obj, *tup[1], **tup[2]) for tup in func_tup)
# we now input these True/False list into the evalstring, which combines them with
# AND/OR/NOT in order to get the final result
return eval(evalstring % tuple(true_false))
else:
return default
def check_lockstring(self, accessing_obj, accessed_obj, lockstring):
"""
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
put to a dummy value since no lock selection is made.
"""
if (hasattr(accessing_obj, 'player') and hasattr(accessing_obj.player, 'user')
and hasattr(accessing_obj.player.user, 'is_superuser')
and accessing_obj.player.user.is_superuser):
return True # always grant access to the superuser.
locks = self. _parse_lockstring(lockstring)
for access_type in locks:
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)
return eval(evalstring % tuple(true_false))
def test():
# testing
class TestObj(object):
pass
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()"
pdb.set_trace()
obj1.locks = LockHandler(obj1)
obj2.permissions = ["Wizards"]
obj2.id = 4
#obj1.locks.add("edit:attr(test)")
print "comparing obj2.permissions (%s) vs obj1.locks (%s)" % (obj2.permissions, obj1.locks)
print obj1.locks.check(obj2, 'owner')
print obj1.locks.check(obj2, 'edit')
print obj1.locks.check(obj2, 'examine')
print obj1.locks.check(obj2, 'delete')
print obj1.locks.check(obj2, 'get')
print obj1.locks.check(obj2, 'listen')

63
src/locks/tests.py Normal file
View file

@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
"""
This is part of Evennia's unittest framework, for testing
the stability and integrrity of the codebase during updates.
This module tests the lock functionality of Evennia.
"""
try:
# this is a special optimized Django version, only available in current Django devel
from django.utils.unittest import TestCase
except ImportError:
from django.test import TestCase
from django.conf import settings
from src.locks import lockhandler, lockfuncs
from src.utils import create
#------------------------------------------------------------
#
# Lock testing
#
#------------------------------------------------------------
class LockTest(TestCase):
"Defines the lock test base"
def setUp(self):
"sets up the testing environment"
self.obj1 = create.create_object(settings.BASE_OBJECT_TYPECLASS, key="obj1")
self.obj2 = create.create_object(settings.BASE_OBJECT_TYPECLASS, key="obj2")
class TestLockCheck(LockTest):
def testrun(self):
dbref = self.obj2.dbref
self.obj1.locks.add("owner:dbref(%s);edit:dbref(%s) or perm(Wizards);examine:perm(Builders) and id(%s);delete:perm(Wizards);get:all()" % (dbref, dbref, dbref))
self.obj2.permissions = ['Wizards']
self.assertEquals(True, self.obj1.locks.check(self.obj2, 'owner'))
self.assertEquals(True, self.obj1.locks.check(self.obj2, 'edit'))
self.assertEquals(True, self.obj1.locks.check(self.obj2, 'examine'))
self.assertEquals(True, self.obj1.locks.check(self.obj2, 'delete'))
self.assertEquals(True, self.obj1.locks.check(self.obj2, 'get'))
self.obj1.locks.add("get:false()")
self.assertEquals(False, self.obj1.locks.check(self.obj2, 'get'))
self.assertEquals(True, self.obj1.locks.check(self.obj2, 'not_exist', default=True))
class TestLockfuncs(LockTest):
def testrun(self):
self.obj2.permissions = ['Wizards']
self.assertEquals(True, lockfuncs.true(self.obj2, self.obj1))
self.assertEquals(False, lockfuncs.false(self.obj2, self.obj1))
self.assertEquals(True, lockfuncs.perm(self.obj2, self.obj1, 'Wizards'))
self.assertEquals(True, lockfuncs.perm_above(self.obj2, self.obj1, 'Builders'))
dbref = self.obj2.dbref
self.assertEquals(True, lockfuncs.dbref(self.obj2, self.obj1, '%s' % dbref))
self.obj2.db.testattr = 45
self.assertEquals(True, lockfuncs.attr(self.obj2, self.obj1, 'testattr', '45'))
self.assertEquals(False, lockfuncs.attr_gt(self.obj2, self.obj1, 'testattr', '45'))
self.assertEquals(True, lockfuncs.attr_ge(self.obj2, self.obj1, 'testattr', '45'))
self.assertEquals(False, lockfuncs.attr_lt(self.obj2, self.obj1, 'testattr', '45'))
self.assertEquals(True, lockfuncs.attr_le(self.obj2, self.obj1, 'testattr', '45'))
self.assertEquals(False, lockfuncs.attr_ne(self.obj2, self.obj1, 'testattr', '45'))

View file

@ -4,18 +4,19 @@ an object's location for valid exit objects.
"""
from src.commands import cmdset, command
from src.permissions.permissions import has_perm
class ExitCommand(command.Command):
"Simple identifier command"
is_exit = True
locks = "cmd:all()" # should always be set to this.
destination = None
obj = None
def func(self):
"Default exit traverse if no syscommand is defined."
if has_perm(self.caller, self.obj, 'traverse'):
if self.obj.access(self.caller, 'traverse'):
self.caller.move_to(self.destination)
else:
self.caller.msg("You cannot enter.")

View file

@ -22,7 +22,7 @@ from src.typeclasses.models import Attribute, TypedObject
from src.typeclasses.typeclass import TypeClass
from src.objects.manager import ObjectManager
from src.config.models import ConfigValue
from src.permissions.permissions import has_perm
from src.utils import logger
from src.utils.utils import is_iter
@ -30,7 +30,7 @@ FULL_PERSISTENCE = settings.FULL_PERSISTENCE
try:
HANDLE_SEARCH_ERRORS = __import__(
settings.ALTERNATE_OBJECT_SEARCH_ERROR_HANDLER).handle_search_errors
settings.ALTERNATE_OBJECT_SEARCH_ERROR_HANDLER).handle_search_errors, fromlist=[None]
except Exception:
from src.objects.object_search_funcs \
import handle_search_errors as HANDLE_SEARCH_ERRORS
@ -70,6 +70,12 @@ class Alias(SharedMemoryModel):
"Define Django meta options"
verbose_name = "Object alias"
verbose_name_plural = "Object aliases"
def __unicode__(self):
return u"%s" % self.db_key
def __str__(self):
return str(self.db_key)
#------------------------------------------------------------
#
@ -104,6 +110,47 @@ class Nick(SharedMemoryModel):
verbose_name_plural = "Nicknames"
unique_together = ("db_nick", "db_type", "db_obj")
class NickHandler(object):
"""
Handles nick access and setting. Accessed through ObjectDB.nicks
"""
def __init__(self, obj):
"Setup"
self.obj = obj
def add(self, nick, realname, nick_type="inputline"):
"We want to assign a new nick"
if not nick or not nick.strip():
return
nick = nick.strip()
real = realname.strip()
query = Nick.objects.filter(db_obj=self.obj, db_nick__iexact=nick, db_type__iexact=nick_type)
if query.count():
old_nick = query[0]
old_nick.db_real = real
old_nick.save()
else:
new_nick = Nick(db_nick=nick, db_real=real, db_type=nick_type, db_obj=self.obj)
new_nick.save()
def delete(self, nick, nick_type="inputline"):
"Removes a nick"
nick = nick.strip()
query = Nick.objects.filter(db_obj=self.obj, db_nick__iexact=nick, db_type__iexact=nick_type)
if query.count():
# remove the found nick(s)
query.delete()
def get(self, nick=None, nick_type="inputline"):
if nick:
query = Nick.objects.filter(db_obj=self.obj, db_nick__iexact=nick, db_type__iexact=nick_type)
query = query.values_list("db_real", flat=True)
if query.count():
return query[0]
else:
return nick
else:
return Nick.objects.filter(db_obj=self.obj)
#------------------------------------------------------------
#
# ObjectDB
@ -127,20 +174,24 @@ class ObjectDB(TypedObject):
typeclass - auto-linked typeclass
date_created - time stamp of object creation
permissions - perm strings
Dbref - #id of object
locks - lock definitions (handler)
dbref - #id of object
db - persistent attribute storage
ndb - non-persistent attribute storage
The ObjectDB adds the following properties:
aliases - alternative names for object
player - optional connected player
location - in-game location of object
home - safety location for object
nicks - this objects nicknames for *other* objects
home - safety location for object (handler)
scripts - scripts assigned to object (handler from typeclass)
cmdset - active cmdset on object (handler from typeclass)
aliases - aliases for this object (property)
nicks - nicknames for *other* things in Evennia (handler)
sessions - sessions connected to this object (see also player)
has_player - bool if an active player is currently connected
contents - other objects having this object as location
exits - exits from this object
"""
#
@ -155,9 +206,6 @@ class ObjectDB(TypedObject):
# using their corresponding properties, named same as the field,
# but withtout the db_* prefix.
# comma-separated list of alias-names of this object. Note that default
# searches only search aliases in the same location as caller.
db_aliases = models.ForeignKey(Alias, blank=True, null=True, db_index=True)
# If this is a character object, the player is connected here.
db_player = models.ForeignKey("players.PlayerDB", blank=True, null=True)
# The location in the game world. Since this one is likely
@ -168,13 +216,18 @@ class ObjectDB(TypedObject):
# a safety location, this usually don't change much.
db_home = models.ForeignKey('self', related_name="homes_set",
blank=True, null=True)
# pickled dictionary storing the object's assigned nicknames
# (use the 'nicks' property to access)
db_nicks = models.ForeignKey(Nick, blank=True, null=True, db_index=True)
# Database manager
objects = ObjectManager()
# Add the object-specific handlers
# (scripts and cmdset must be added from
# typeclass, so not added here)
def __init__(self, *args, **kwargs):
"Parent must be initialized first."
TypedObject.__init__(self, *args, **kwargs)
self.nicks = NickHandler(self)
# Wrapper properties to easily set database fields. These are
# @property decorators that allows to access these fields using
# normal python operations (without having to remember to save()
@ -304,22 +357,29 @@ class ObjectDB(TypedObject):
self.db_home = None
home = property(home_get, home_set, home_del)
# nicks property (wraps db_nicks)
#@property
def nicks_get(self):
"Getter. Allows for value = self.aliases"
return list(Nick.objects.filter(db_obj=self))
#@nick.setter
def nicks_set(self, nicks):
"""Setter is disabled. Use the nickhandler instead."""
logger.log_errmsg("Nicks (%s) cannot be set through obj.nicks. Use obj.nickhandler instead." % nicks)
#@nick.deleter
def nicks_del(self):
"Deleter. Allows for del self.aliases"
for nick in Nick.objects.filter(db_obj=self):
nick.delete()
nicks = property(nicks_get, nicks_set, nicks_del)
#@property for consistent aliases access throughout Evennia
#@aliases.setter
def aliases_set(self, aliases):
"Adds an alias to object"
if not is_iter(aliases):
aliases = [aliases]
for alias in aliases:
query = Alias.objects.filter(db_obj=self, db_key__iexact=alias)
if query.count():
continue
new_alias = Alias(db_key=alias, db_obj=self)
new_alias.save()
#@aliases.getter
def aliases_get(self):
"Return a list of all aliases defined on this object."
return list(Alias.objects.filter(db_obj=self).values_list("db_key", flat=True))
#@aliases.deleter
def aliases_del(self):
"Removes aliases from object"
query = Alias.objects.filter(db_obj=self)
if query:
query.delete()
aliases = property(aliases_get, aliases_set, aliases_del)
class Meta:
"Define Django meta options"
@ -378,55 +438,8 @@ class ObjectDB(TypedObject):
return [exi for exi in self.contents
if exi.has_attribute('_destination')]
exits = property(exits_get)
def nickhandler(self, nick, realname=None, nick_type="inputline", startswith=False, delete=False):
"""
This is a central method for getting and setting nicks
- mappings of nicks to strings, objects etc. This is the main
access method, use it in preference over the 'nicks' property.
Map a nick to a realname. Be careful if mapping an
existing realname into a nick - you could make that
realname inaccessible until you deleted the alias.
To delete - don't set realname.
nick_types can be defined freely depending on implementation.
The default nick_types used in Evennia are:
inputline (default) - match nick against all input
player - match nick against player searches
obj - match nick against object searches
channel - match nick when checking for channel aliases
the delete keyword will delete the given nick.
"""
if not nick or not nick.strip():
return
nick = nick.strip()
query = Nick.objects.filter(db_obj=self, db_nick__iexact=nick)
if nick_type:
query = query.filter(db_type__iexact=nick_type)
if delete:
# remove the found nick(s)
query.delete()
elif realname == None:
# we want to get the real name for the nick. If none is
# found, we return the nick again
query = query.values_list("db_real", flat=True)
if query:
return query[0]
else:
return nick
else:
# we want to assign a new nick
real = realname.strip()
if query:
old_nick = query[0]
old_nick.db_real = real
old_nick.save()
else:
new_nick = Nick(db_nick=nick, db_real=real, db_type=nick_type, db_obj=self)
new_nick.save()
#
# Main Search method
#
@ -467,10 +480,10 @@ class ObjectDB(TypedObject):
if use_nicks:
if ostring.startswith('*'):
# player nick replace
ostring = "*%s" % self.nickhandler(ostring.lstrip('*'), nick_type="player")
ostring = "*%s" % self.nicks.get(ostring.lstrip('*'), nick_type="player")
else:
# object nick replace
ostring = self.nickhandler(ostring, nick_type="object")
ostring = self.nicks.get(ostring, nick_type="object")
results = ObjectDB.objects.object_search(self, ostring,
global_search=global_search,
@ -485,25 +498,7 @@ class ObjectDB(TypedObject):
#
# Execution/action methods
#
def has_perm(self, accessing_obj, lock_type):
"""
Determines if another object has permission to access
this object.
accessing_obj - the object trying to gain access.
lock_type : type of access checked for
"""
return has_perm(accessing_obj, self, lock_type)
def has_perm_on(self, accessed_obj, lock_type):
"""
Determines if *this* object has permission to access
another object.
accessed_obj - the object being accessed by this one
lock_type : type of access checked for
"""
return has_perm(self, accessed_obj, lock_type)
def execute_cmd(self, raw_string):
"""
Do something as this object. This command transparently
@ -536,6 +531,7 @@ class ObjectDB(TypedObject):
def emit_to(self, message, from_obj=None, data=None):
"Deprecated. Alias for msg"
logger.log_depmsg("emit_to() is deprecated. Use msg() instead.")
self.msg(message, from_obj, data)
def msg_contents(self, message, exclude=None, from_obj=None, data=None):
@ -555,6 +551,7 @@ class ObjectDB(TypedObject):
def emit_to_contents(self, message, exclude=None, from_obj=None, data=None):
"Deprecated. Alias for msg_contents"
logger.log_depmsg("emit_to_contents() is deprecated. Use msg_contents() instead.")
self.msg_contents(message, exclude=exclude, from_obj=from_obj, data=data)
def move_to(self, destination, quiet=False,

View file

@ -11,9 +11,6 @@ Both the replacing functions must have the same name and same input/output
as the ones in this module.
"""
from src.permissions.permissions import has_perm_string
def handle_search_errors(emit_to_obj, ostring, results, global_search=False):
"""
Takes a search result (a list) and
@ -35,7 +32,7 @@ def handle_search_errors(emit_to_obj, ostring, results, global_search=False):
# check if the emit_to_object may se dbrefs
show_dbref = global_search and \
has_perm_string(emit_to_obj, 'see_dbref')
emit_to_obj.check_permstring('Builders')
string = "More than one match for '%s'" % ostring
string += " (please narrow target):"

View file

@ -49,10 +49,10 @@ class Object(TypeClass):
create_cmdset = True
try:
dummy = object.__getattribute__(dbobj, 'scripts')
create_scripts = type(dbobj.scripts) != ScriptHandler
create_scripts = type(dbobj.scripts) != ScriptHandler
except AttributeError:
create_scripts = True
if create_cmdset:
dbobj.cmdset = CmdSetHandler(dbobj)
if utils.inherits_from(self, settings.BASE_CHARACTER_TYPECLASS):
@ -79,8 +79,18 @@ class Object(TypeClass):
"""
Called once, when this object is first
created.
"""
pass
"""
# the default security setup fallback for a generic
# object. Overload in child for a custom setup. Also creation
# commands may set this (create an item and you should its
# owner, for example)
dbref = self.dbobj.dbref
self.locks.add("owner:id(%s)" % dbref)
self.locks.add("examine:perm(Builders)")
self.locks.add("edit:perm(Wizards)")
self.locks.add("delete:perm(Wizards)")
self.locks.add("get:all()")
def at_first_login(self):
"""
@ -326,11 +336,16 @@ class Character(Object):
"""
from settings import CMDSET_DEFAULT
self.cmdset.add_default(CMDSET_DEFAULT, permanent=True)
# setup security
super(Character, self).at_object_creation()
self.locks.add("puppet:id(%s) or perm(Immortals)" % self.dbobj.dbref)
def at_after_move(self, source_location):
"Default is to look around after a move."
self.execute_cmd('look')
#
# Base Room object
#
@ -345,6 +360,7 @@ class Room(Object):
Simple setup, shown as an example
(since default is None anyway)
"""
super(Room, self).at_object_creation()
self.location = None
class Exit(Object):
@ -366,7 +382,13 @@ class Exit(Object):
definition (unless you want an entire class of exits
all leadning to the same hard-coded place ...)
"""
# this is what makes it an exit
self.attr("_destination", "None")
# the lock is open to all by default
super(Exit, self).at_object_creation()
self.locks.add("traverse:all()")
def at_object_delete(self):
"""
We have to make sure to clean the exithandler cache

View file

@ -28,6 +28,7 @@ from django.conf import settings
from src.objects import models, objects
from src.utils import create
from src.commands.default import tests as commandtests
from src.locks import tests as locktests
class TestObjAttrs(TestCase):
"""
@ -55,4 +56,5 @@ def suite():
tsuite = unittest.TestSuite()
tsuite.addTest(unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__]))
tsuite.addTest(unittest.defaultTestLoader.loadTestsFromModule(commandtests))
tsuite.addTest(unittest.defaultTestLoader.loadTestsFromModule(locktests))
return tsuite

View file

@ -1,18 +0,0 @@
#
# This sets up how models are displayed
# in the web admin interface.
#
from src.permissions.models import PermissionGroup
from django.contrib import admin
class PermissionGroupAdmin(admin.ModelAdmin):
list_display = ('id', 'db_key', 'db_group_permissions')
list_display_links = ('id', "db_key")
ordering = ['db_key', 'db_group_permissions']
readonly_fields = ['db_key', 'db_group_permissions', 'db_permissions']
search_fields = ['db_key']
save_as = True
save_on_top = True
list_select_related = True
admin.site.register(PermissionGroup, PermissionGroupAdmin)

View file

@ -1,104 +0,0 @@
"""
Setup the permission hierarchy and groups. This is
read once during server startup. Further groups and
permissions have to be added manually.
To set up your own permission scheme, have
PERMISSION_SETUP_MODULE in game/settings point to
a module of your own. This module must define two global
dictionaries PERMS and GROUPS.
PERMS contains all permissions defined at server start
on the form {key:desc, key:desc, ...}
GROUPS gathers permissions (which must have been
previously created as keys in PERMS) into clusters
on the form {groupname: [key, key, ...], ...}
"""
# Defining all permissions.
PERMS = [
'emit',
'wall',
'teleport',
'setobjalias',
'wipe',
'set',
'cpattr',
'mvattr',
'find',
'create',
'copy',
'open',
'link',
'unlink',
'dig',
'desc',
'destroy',
'examine',
'typeclass',
'debug',
'puppet',
'typeclass',
'batchcommands',
'batchcodes',
'ccreate',
'cdesc',
'tell',
'time',
'list',
'ps',
'stats',
'reload',
'py',
'listscripts',
'listcmdsets',
'listobjects',
'boot',
'delplayer',
'newpassword',
'home',
'service',
'shutdown',
'perm',
'sethelp',
]
# Permission Groups
# Permission groups clump the previously defined permissions into
# larger chunks. {groupname: [permissionkey,... ]}
GROUPS = {
"Immortals": PERMS,
"Wizards": [perm for perm in PERMS
if perm not in ['shutdown',
'py',
'reload',
'service',
'perm',
'typeclass',
'batchcommands',
'batchcodes']],
"Builders": [perm for perm in PERMS
if perm not in ['shutdown',
'py',
'reload',
'service',
'perm',
'batchcommands',
'batchcodes',
'puppet',
'wall',
'boot',
'delplayer',
'newpassword']],
"PlayerHelpers": ['tell',
'sethelp', 'ccreate', 'use_channels'],
"Players": ['tell', 'ccreate', 'use_channels']
}

View file

@ -1,25 +0,0 @@
"""
A simple manager for Permission groups
"""
from django.db import models
class PermissionGroupManager(models.Manager):
"Adds a search method to the default manager"
def search_permgroup(self, ostring):
"""
Find a permission group
ostring = permission group name (case sensitive)
or database dbref
"""
groups = []
try:
dbref = int(ostring.strip('#'))
groups = self.filter(id=dbref)
except Exception:
pass
if not groups:
groups = self.filter(db_key=ostring)
return groups

View file

@ -1,150 +0,0 @@
"""
The PermissionGroup model clumps permissions together into
manageable chunks.
"""
from django.db import models
from src.utils.idmapper.models import SharedMemoryModel
from src.permissions.manager import PermissionGroupManager
from src.utils.utils import is_iter
#------------------------------------------------------------
#
# PermissionGroup
#
#------------------------------------------------------------
class PermissionGroup(SharedMemoryModel):
"""
This groups permissions into a clump.
The following properties are defined:
key - main ident for group
desc - optional description of group
group_permissions - the perms stored in group
permissions - the group's own permissions
"""
#
# PermissionGroup database model setup
#
# These database fields are all set using their corresponding properties,
# named same as the field, but withtout the db_* prefix.
#
# identifier for the group
db_key = models.CharField(max_length=80, unique=True)
# description of the group's permission contents
db_desc = models.CharField(max_length=255, null=True, blank=True)
# the permissions stored in this group; comma separated string
# (NOT to be confused with the group object's own permissions!)
db_group_permissions = models.TextField(blank=True)
# OBS - this is the groups OWN permissions, for accessing/changing
# the group object itself (comma-separated string)!
db_permissions = models.CharField(max_length=256, blank=True)
# Database manager
objects = PermissionGroupManager()
# Wrapper properties to easily set database fields. These are
# @property decorators that allows to access these fields using
# normal python operations (without having to remember to save()
# etc). So e.g. a property 'attr' has a get/set/del decorator
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
# is the object in question).
# key property (wraps db_key)
#@property
def key_get(self):
"Getter. Allows for value = self.key"
return self.db_key
#@key.setter
def key_set(self, value):
"Setter. Allows for self.key = value"
self.db_key = value
self.save()
#@key.deleter
def key_del(self):
"Deleter. Allows for del self.key"
self.db_key = None
self.save()
key = property(key_get, key_set, key_del)
# desc property (wraps db_desc)
#@property
def desc_get(self):
"Getter. Allows for value = self.desc"
return self.db_desc
#@desc.setter
def desc_set(self, value):
"Setter. Allows for self.desc = value"
self.db_desc = value
self.save()
#@desc.deleter
def desc_del(self):
"Deleter. Allows for del self.desc"
self.db_desc = None
self.save()
desc = property(desc_get, desc_set, desc_del)
# group_permissions property
#@property
def group_permissions_get(self):
"Getter. Allows for value = self.name. Returns a list of group_permissions."
if self.db_group_permissions:
return [perm.strip() for perm in self.db_group_permissions.split(',')]
return []
#@group_permissions.setter
def group_permissions_set(self, value):
"Setter. Allows for self.name = value. Stores as a comma-separated string."
if is_iter(value):
value = ",".join([str(val).strip().lower() for val in value])
self.db_group_permissions = value
self.save()
#@group_permissions.deleter
def group_permissions_del(self):
"Deleter. Allows for del self.name"
self.db_group_permissions = ""
self.save()
group_permissions = property(group_permissions_get, group_permissions_set, group_permissions_del)
# permissions property
#@property
def permissions_get(self):
"Getter. Allows for value = self.name. Returns a list of permissions."
if self.db_permissions:
return [perm.strip() for perm in self.db_permissions.split(',')]
return []
#@permissions.setter
def permissions_set(self, value):
"Setter. Allows for self.name = value. Stores as a comma-separated string."
if is_iter(value):
value = ",".join([str(val).strip().lower() for val in value])
self.db_permissions = value
self.save()
#@permissions.deleter
def permissions_del(self):
"Deleter. Allows for del self.name"
self.db_permissions = ""
self.save()
permissions = property(permissions_get, permissions_set, permissions_del)
class Meta:
"Define Django meta options"
verbose_name = "Permission Group"
verbose_name_plural = "Permission Groups"
#
# PermissionGroup help method
#
#
def contains(self, permission):
"""
Checks if the given permission string is defined in the
permission group.
"""
return permission in self.group_permissions

View file

@ -1,643 +0,0 @@
"""
Permissions
Evennia's permission system can be as coarse or fine-grained as desired.
**Note: Django's in-built permission checking is still used for web-related access
to the admin site etc. This uses the normal django permission
strings of the form 'appname.permstring' and automatically adds three of them
for each model - for src/object this would be for example
'object.create', 'object.admin' and 'object.edit'. Checking and permissions
are done completely separate from the present mud permission system, see
Django manuals. **
Almost all engine elements (Commands, Players, Objects, Scripts,
Channels) have a field or variable called 'permissions' - this holds a
list or a comma-separated string of 'permission strings'.
Whenever a permission is to be checked there is always an
'Accessing object' and an 'Accessed object'. The 'permissions' field
on the two objects are matched against each other. A permission field
can contain one of two types of strings - 'keys' and 'locks' and the
accessing object's keys are matched against the accessing object's locks
depending on which type of access is required. The check function
has_perm (in this module) is used to handle the check.
access_obj.permissions (keys) ---> | |
lock_type ---> | has_perm() | ---> False/True
accessed_obj.permissions (locks) ---> | |
If there are no locks (of the right type) available on accessed_obj, access is
granted by default. You can however give an argument to has_perm() to reverse
this policy to one where default is no access until explicitly granted.
- Keys
Keys are simple strings that can contain any alphanumerical symbol and
be of any length. They are case-sensitive. Only whitespace, colons(:) and
commas (,) are not allowed in keys.
examples:
myperm
look_perm
Builders
obj.create (this is what a general Django key looks like)
- Locks
A lock always consists of two parts separated by a colon (:). A lock must nowhere
contain commas (,) since these are used as separators between different locks and keys
during storage.
-type header- -lock body-
type1 type2 FLAG:string1 string2 string3()
type header
The first part is the 'type' of lock, i.e. what kind of functionality
this lock regulates. The function has_perm() takes this as an argument and uses it to sort
out which locks are checked (if any).
A type name is not case sensitive but may not contain any whitespace (or colons). Use
whitespaces to expand the number of types this lock represents.
The last word in the type header may be a special flag that regulates how the second
part of the lock is handled, especially if the second part is a space-separated list
of permissions:
- OR or ANY (or none, default) - ANY one match in list means that the entire lock is passed.
- AND or ALL - ALL permissions in list must be matched by accessing obj for lock to pass
- NOT - inverses the lock. A matched lock means False and vice versa.
lock body
The second part, after the comma, is the function body of the lock. The function body
can look in several ways:
- it could be a permission string, to be matched directly with a key
- it could be a function call to a function defined in a safe module (this returns True/False)
- it could be a space-separated list of any combination of the above.
- it could be one of the non-passable keywords FALSE, LOCK or IMPASSABLE (can be put in a
list too, but the list will then always return False, so they are usually
used on their own).
examples:
look:look_perm
this lock is of type 'look' and simply checks if the accessing_obj has
they key look_perm or not.
add edit:Builders
this lock regulates two lock types, 'add' and 'edit'. In both cases,
having the key 'Builders' will pass the lock.
add edit:Builders Admin
this is like above, but it is passed if the accessing object has either
the key Builders or the key Admin (or both)
system NOT: Untrusted
this lock of type 'system' is passed if the accessing object does NOT have
the key 'Untrusted', since the NOT flag inverses the lock result.
delete:FALSE
this lock, of type 'delete' can never be passed since the keyword FALSE
is always False (same with LOCK and IMPASSABLE).
open:
this lock has an empty lock body and is an always passable lock. Unless you
change how has_perm() behaves you can usually just completely skip defining
this lock to get the same effect.
enter:has_key()
this 'enter'-type lock might for example be placed on a door. The lock body
is a function has_key() defined in a module set in the settings. See below
for more info on lock functions. It is up the admin to create
these lock functions ahead of time as needed for the game.
unlock:has_pickpocket_lvl(4,difficulty=7)
function locks can even take arguments, in this case it calls a function
has_pick() with info on how hard the lock is.
delete AND: Builders can_delete has_credits()
Since the keyword AND is set (ALL would also work), the
accessing object must have both the 'Builders', 'can_delete'
*and* the function lock 'has_credits()' in order to pass the lock.
- More on functional locks
A function lock goes into the second part of a lock definition (after the colon).
Syntax:
funcname(arg3, arg4, ...)
arg1 == always accessing_obj
arg2 == always accessed_obj
A function lock is called just like any Python function and should be defined in
the same way. The system searches for the first matching function in one of the
modules defined by settings.PERMISSION_FUNC_MODULES and executes it.
Accessing_object and Accessed_object are always passed, followed by the arguments
and keywords supplied in the permission. The function should return a boolean and
must make sure to handle any possible type of objects being passed in
the first two arguments (such as Command objects, Scripts or whatever). There is a
convenience function available in this module (get_types) for doing just that.
examples:
'is_stronger_than(40)'
'is_suitably_dressed()'
'has_lockpick_higher_than(24, difficulty=4)'
'has_attr_value('mood', 'happy')
- Permissions string
As mentioned, keys and locks are stored together on an object in a string
called 'permissions'. You are most likely to come into contact with this
when defining new Commands.
This is a comma-separated string (which is why commas
are not allowed in keys nor in locks). Keys and Locks can appear in any
order in the permission string, they are easily separated by the colon
appearing only in locks.
example:
'call ALL:can_edit Builders'
this might be a Command permission and has only one lock on it (a command usually have no need
for keys of its own) but two requirements to be able to call it: The accessing object
(probably a player) must have both the can_edit and Builders keys (one is not enough
since the ALL flag is set).
'Builders, edit_channels, control:has_id(45)'
this permission string could sit on a Character object. It has two keys and one lock.
The keys tell us Character is a Builder and also has the edit_channels permission.
The lock is checked when someone tries to gain 'control' over this object (whatever
this means in your game). This lock calls the function has_id() with the argument 45.
We can't see here what has_id() actually does, but a likely implementation would
be to return True only if the accessing object actually has an id of 45.
- Reserved Server lock types
The Evennia server (i.e. everything in /src) tries to 'outsource' as many permission checks
as possible to what the admin can customize in /game. All the admin needs to do is import
has_perm() from this module to use the system. As seen above, the actual syntax of
locks and keys above gives the admin much freedom in designing their own system.
That said, there are a few actions that cannot be outsourced due to technical reasons.
In these situations the server must hard-code its permission checks. What this means
is that it always calls has_perm() with a specific lock-type keyword that you cannot change. For
example it always checks if a command may be accessed by matching the calling player's
keys against command-locks of type 'call'. Below you will find all hard-coded lock types
the server checks and when it does it.
locktype entity situation
call Command when a player tries to call a
command. Determines if the command is available
at all. Also determines if command will be
visible in help lists etc.
chan_send Channels
chan_listen "
traverse Exits
"""
from django.conf import settings
from src.permissions.models import PermissionGroup
from src.utils import logger
from src.utils.utils import is_iter
IMPASSABLE = ['FALSE', 'LOCK', 'IMPASSABLE']
ORFLAGS = ['OR', 'ANY']
ANDFLAGS = ['AND', 'ALL']
NOTFLAGS = ['NOT']
LOCK_FUNC_PATHS = settings.PERMISSION_FUNC_MODULES
CACHED_FUNC_MODULES = []
PARENTS = {
"command":"src.commands.command.Command",
"msg":"src.comms.models.Msg",
"channel":"src.comms.models.Channel",
"help":"src.help.models.HelpEntry",
"typeclass":"src.typeclasses.typeclass.TypeClass",
"script":"src.scripts.models.ScriptDB",
"object":"src.objects.models.ObjectDB",
"player":"src.players.models.Player"}
#
# Utility functions for handling permissions
#
def perm_to_list(permissions):
"""
Process a permstring and return a list
permissions - can be a list of permissions, a permission string
or a comma-separated string of permissions.
"""
if not permissions:
return []
if not is_iter(permissions):
# convert input to a list
permissions = str(permissions)
if ',' in permissions:
permissions = permissions.split(',')
else:
permissions = [permissions]
p_nested = []
for nested_perm in (p for p in permissions if ',' in p):
# handling eventual weird nested comma-separated
# permissions inside lists
p_nested.extend(nested_perm.split(','))
permissions.extend(p_nested)
# merge units with unmatched parenthesis (so e.g. '(a,b,c,d)' are not
# split by comma separation (this allows for functional locks with
# multiple arguments, like 'has_attr(attrname, attrvalue)'). This
# also removes all whitespace in the parentheses to avoid confusion later.
lparents = 0
rparents = 0
in_block = False
perm_list = []
for perm in permissions:
lparents += perm.count('(')
rparents += perm.count(')')
if lparents > rparents:
# unmatched left-parenthesis - trigger block preservation
if not in_block:
# beginning of block
in_block = True
perm_list.append(perm)
else:
# in block
block = perm_list.pop()
perm_list.append(",".join([block.strip(), perm.strip()]))
elif in_block:
# parentheses match again - end the block
in_block = False
block = perm_list.pop()
perm_list.append(",".join([block.strip(), perm.strip()]))
else:
# no parenthesis/block
perm_list.append(perm.strip())
return perm_list
def set_perm(obj, new_perms, replace=True):
"""
Set a permission string on an object.
The permissions given by default replace the old one.
If 'replace' is unset, the new one will be appended to
the old ones.
obj - object to receive permission. must have field/variable
named 'permissions' for this to work.
new_perms - a permission string, a list of permission strings
or a comma-separated string of permissions
replace - clear and replace old permission completely
Note - this is also how you add an entity to a group
"""
try:
# get the old permissions if possible
obj_perms = perm_to_list(obj.permissions)
except AttributeError:
# this object cannot get a permission
return False
new_perms = perm_to_list(new_perms)
if replace:
# replace permissions completely
obj_perms = new_perms
else:
# extend the old permissions with the new ones
for newperm in (perm for perm in new_perms if perm not in obj_perms):
obj_perms.append(newperm)
# set on object as comma-separated list.
obj.permissions = ",".join(str(perm).strip() for perm in obj_perms)
try:
# E.g. Commands are not db-connected and cannot save,
# so we ignore errors here.
obj.save()
except Exception:
pass
return True
def add_perm(obj, add_perms):
"""
Convenience function to add a permission to an entity.
"""
return set_perm(obj, add_perms, replace=False)
def del_perm(obj, del_perms):
"""
Remove permission from object (if possible)
"""
try:
obj_perms = perm_to_list(obj.permissions)
except AttributeError:
return False
del_perms = perm_to_list(del_perms)
obj_perms = [perm for perm in obj_perms if perm not in del_perms]
obj.permissions = ",".join(str(perm) for perm in obj_perms)
try:
obj.save()
except Exception:
pass
return True
def get_types(accessing_obj, accessed_obj):
"""
A convenience function for determining what type the objects are.
This is intended for easy importing into the modules
defined in LOCK_FUNC_PATHS.
"""
def has_parent(basepath, obj):
"Checks if basepath is somewhere is objs parent tree."
return any(cls for cls in obj.__class__.mro()
if basepath == "%s.%s" % (cls.__module__, cls.__name__))
checking_type = None
checked_type = None
try:
checking_type = [parent for parent, path in PARENTS.items()
if has_parent(path, accessing_obj)][0]
if checking_type == 'typeclass':
checking_type = [parent for parent, path in PARENTS.items()
if has_parent(path, accessing_obj.db)][0]
except IndexError:
logger.log_trace("LockFunc: %s is not of a valid type."
% accessing_obj)
raise
try:
checked_type = [parent for parent, path in PARENTS.items()
if has_parent(path, accessed_obj)][0]
if checking_type == 'typeclass':
checked_type = [parent for parent, path in PARENTS.items()
if has_parent(path, accessed_obj.db)][0]
except IndexError:
logger.log_trace("LockFunc: %s is not of a valid type."
% accessed_obj)
raise
return (checking_type, checked_type)
#
# helper functions for has_perm()
#
def append_group_permissions(keylist):
"""
Step through keylist and discover if
one the keys match a permission group name
(case sensitive). In that case, go into
that group and add its permissions to
the keylist.
"""
groups = []
for match_key in keylist:
try:
groups.append(PermissionGroup.objects.get(db_key=match_key))
except Exception:
pass
for group in groups:
keylist.extend(perm_to_list(group.group_permissions))
return list(set(keylist)) # set makes elements of keylist unique
def try_impassable(lockdefs):
"""
Test if this list of lockdefs is impassable.
"""
return any(True for lockdef in lockdefs if lockdef in IMPASSABLE)
def try_key_lock(iflag, keylist, lockdefs):
"""
Test a direct-comparison match between keys and lockstrings.
Returns the number of matches found.
"""
if iflag in ANDFLAGS:
return len([True for key in keylist if key in lockdefs])
elif iflag in NOTFLAGS:
return not any(True for key in keylist if key in lockdefs)
else:
return any(True for key in keylist if key in lockdefs)
def try_functional_lock(lflag, lockdefs, accessing_obj, accessed_obj):
"""
Functional lock
lockdefs is a list of permission strings (called by check_lock)
"""
global CACHED_FUNC_MODULES
if not CACHED_FUNC_MODULES:
# Cache the imported func module(s) once and for all
# so we don't have to re-import them for every call.
# We allow for LOCK_FUNC_PATHS to be a tuple of
# paths as well.
CACHED_FUNC_MODULES = []
try:
module_paths = list(LOCK_FUNC_PATHS)
except Exception:
module_paths = [LOCK_FUNC_PATHS]
for path in module_paths:
try:
CACHED_FUNC_MODULES.append(__import__(path, fromlist=[True]))
except ImportError:
logger.log_trace("lock func: import error for '%s'" % path)
continue
# try to execute functions, if they exist
#print "locklist:", locklist
passed_locks = 0
for lockstring in lockdefs:
if not lockstring \
or not ('(' in lockstring and ')' in lockstring) \
or not (lockstring.find('(') < lockstring.find(')')):
# must be a valid function() call
continue
funcname, args = (str(part).strip() for part in lockstring.split('(', 1))
args = args.rstrip(')').split(',')
kwargs = [kwarg for kwarg in args if '=' in kwarg]
args = tuple([arg for arg in args if arg not in kwargs])
kwargs = dict([(key.strip(), value.strip()) for key, value in [kwarg.split('=', 1) for kwarg in kwargs]])
#print "%s '%s'" % (funcname, args)
for module in CACHED_FUNC_MODULES:
# step through all safe modules, executing the first one that matches
lockfunc = module.__dict__.get(funcname, None)
if callable(lockfunc):
try:
# try the lockfunction.
if lockfunc(accessing_obj, accessed_obj, *args, **kwargs):
if lflag in ANDFLAGS:
passed_locks += 1
elif lflag in NOTFLAGS:
return False
else:
return True
except Exception:
continue
return passed_locks
#------------------------------------------------------------
# has_perm & has_perm_string : main access functions
#------------------------------------------------------------
def has_perm(accessing_obj, accessed_obj, lock_type, default_deny=False):
"""
The main access method of the permission system. Note that
this will not perform checks against django's in-built permission
system, that is assumed to be done in the calling function
after this method returns False.
accessing_obj - the object trying to gain access
accessed_obj - the object being checked for access
lock_type - Only locks of this type 'lock_type:permissionstring'
will be considered for a match.
default_deny - Normally, if no suitable locks are found on the object, access
is granted by default. This switch changes security policy to
instead lock down the object unless access is explicitly given.
"""
# Get the list of locks of the accessed_object
try:
locklist = [lock for lock in perm_to_list(accessed_obj.permissions) if ':' in lock]
except AttributeError:
# This is not a valid permissable object at all
logger.log_trace()
return False
# filter the locks to find only those of the correct lock_type. This creates
# a list with elements being tuples (flag, [lockdef, lockdef, ...])
lock_type = lock_type.lower()
locklist = [(typelist[-1].strip(), [lo.strip() for lo in lock.split(None)])
for typelist, lock in [(ltype.split(None), lock)
for ltype, lock in [lock.split(':', 1)
for lock in locklist]]
if typelist and lock_type in typelist]
if not locklist or not any(locklist):
# No viable locks; use default security policy
return not default_deny
# we have locks of the right type. Set default flag OR on all that
# don't explictly specify a flag (AND, OR, NOT). These flags determine
# how the permstrings in the lock definition should relate to one another.
locktemp = []
for ltype, lock in locklist:
if ltype not in ANDFLAGS and ltype not in NOTFLAGS:
ltype = 'OR'
locktemp.append((ltype, lock))
locklist = locktemp
# Get the list of keys of the accessing_object
keylist = []
#print "testing %s" % accessing_obj.__class__
if hasattr(accessing_obj, 'is_superuser') and accessing_obj.is_superuser:
# superusers always have access.
return True
# try to add permissions from connected player
if hasattr(accessing_obj, 'has_player') and accessing_obj.has_player:
# accessing_obj has a valid, connected player. We start with those permissions.
player = accessing_obj.player
if player.is_superuser:
# superuser always has access
return True
try:
keylist.extend([perm for perm in perm_to_list(player.permissions)
if not ':' in perm])
except Exception:
pass
# next we get the permissions directly from accessing_obj.
try:
keylist.extend([perm for perm in perm_to_list(accessing_obj.permissions)
if not ':' in perm])
except Exception:
# not a valid permissable object
return False
# expand also with group permissions, if any
keylist = append_group_permissions(keylist)
#print "keylist: %s" % keylist
# Test permissions against the locks
for lflag, lockdefs in locklist:
# impassable locks normally shuts down the entire operation right away.
if try_impassable(lockdefs):
return lflag in NOTFLAGS
# test direct key-lock comparison and functional locks
if lflag in ANDFLAGS:
# with the AND flag, we have to match all lockdefs to pass the lock
passed_locks = try_key_lock(lflag, keylist, lockdefs)
passed_locks += try_functional_lock(lflag, lockdefs, accessing_obj, accessed_obj)
if passed_locks == len(lockdefs):
return True
else:
# normal operation; any match passes the lock
if try_key_lock(lflag, keylist, lockdefs):
return True
if try_functional_lock(lflag, lockdefs, accessing_obj, accessed_obj):
return True
# If we fall through to here, we don't have access
return False
def has_perm_string(accessing_obj, lock_list, default_deny=False):
"""
This tests the given accessing object against the
given string rather than a particular accessing object.
OBS: Be careful if supplying function locks to this method since
there is no meaningful accessed_obj present (the one fed to
the function is just a dummy).
accessing_obj - the object being checked for permissions
lock_list - a list or a comma-separated string of lock definitions.
default_deny - Normally, if no suitable locks are found on the object, access
is granted by default. This switch changes security policy to
instead lock down the object unless access is explicitly given.
"""
# prepare the permissions we want, so it's properly stripped etc.
class Dummy(object):
"Dummy object"
def __init__(self, permissions):
self.permissions = permissions
if not is_iter(lock_list):
lock_list = [perm for perm in lock_list.split(',')]
lockstring = ",".join(["dummy:%s" % perm.strip() for perm in lock_list if perm.strip()])
# prepare a dummy object with the permissions
accessed_obj = Dummy(lockstring)
# call has_perm normally with the dummy object.
return has_perm(accessing_obj, accessed_obj, 'dummy', default_deny)

View file

@ -47,7 +47,6 @@ from django.utils.encoding import smart_str
from src.server.sessionhandler import SESSIONS
from src.players import manager
from src.typeclasses.models import Attribute, TypedObject
from src.permissions import permissions
from src.utils import logger
#------------------------------------------------------------
@ -105,7 +104,6 @@ class PlayerDB(TypedObject):
"""
#
# PlayerDB Database model setup
#
@ -237,16 +235,6 @@ class PlayerDB(TypedObject):
return self.user.is_superuser
is_superuser = property(is_superuser_get)
def set_perm(self, perm):
"Shortcuts to set permissions, replacing old ones"
return permissions.set_perm(self, perm)
def add_perm(self, perm):
"Add permissions to the old ones"
return permissions.add_perm(self, perm)
def del_perm(self, perm):
"Delete permission from old ones"
return permissions.del_perm(self, perm)
#
# PlayerDB class access methods
#

View file

@ -27,7 +27,13 @@ class Player(TypeClass):
"""
# the text encoding to use.
self.db.encoding = "utf-8"
pass
# A basic security setup
self.locks.add("examine:perm(Wizards)")
self.locks.add("edit:perm(Wizards)")
self.locks.add("delete:perm(Wizards)")
self.locks.add("boot:perm(Wizards)")
self.locks.add("msg:all()")
# Note that the hooks below also exist
# in the character object's typeclass. You

View file

@ -28,6 +28,7 @@ from django.conf import settings
from django.db import models
from src.typeclasses.models import Attribute, TypedObject
from src.scripts.manager import ScriptManager
#from src.locks.lockhandler import LockHandler
#------------------------------------------------------------
#
@ -104,7 +105,7 @@ class ScriptDB(TypedObject):
# Database manager
objects = ScriptManager()
class Meta:
"Define Django meta options"
verbose_name = "Script"

View file

@ -74,6 +74,8 @@ def create_objects():
user=god_user)
god_character.id = 1
god_character.db.desc = 'This is User #1.'
god_character.locks.add("examine:perm(Immortals);edit:false();delete:false();boot:false();msg:all()")
god_character.save()
# Limbo is the initial starting room.
@ -99,14 +101,14 @@ def create_channels():
print " Creating default channels ..."
# public channel
key, aliases, desc, perms = settings.CHANNEL_PUBLIC
pchan = create.create_channel(key, aliases, desc, perms)
key, aliases, desc, locks = settings.CHANNEL_PUBLIC
pchan = create.create_channel(key, aliases, desc, locks=locks)
# mudinfo channel
key, aliases, desc, perms = settings.CHANNEL_MUDINFO
ichan = create.create_channel(key, aliases, desc, perms)
key, aliases, desc, locks = settings.CHANNEL_MUDINFO
ichan = create.create_channel(key, aliases, desc, locks=locks)
# connectinfo channel
key, aliases, desc, perms = settings.CHANNEL_CONNECTINFO
cchan = create.create_channel(key, aliases, desc, perms)
key, aliases, desc, locks = settings.CHANNEL_CONNECTINFO
cchan = create.create_channel(key, aliases, desc, locks=locks)
# connect the god user to all these channels by default.
goduser = get_god_user()
@ -126,33 +128,6 @@ def import_MUX_help_files():
print " Moving imported help db to help category '%s'." \
% default_category
HelpEntry.objects.all_to_category(default_category)
def create_permission_groups():
"""
This sets up the default permissions groups
by parsing a permission config file.
Note that we don't catch any exceptions here,
this must be debugged until it works.
"""
print " Creating and setting up permissions/groups ..."
# We try to get the data from config first.
setup_path = settings.PERMISSION_SETUP_MODULE
if not setup_path:
# go with the default
setup_path = "src.permissions.default_permissions"
module = __import__(setup_path, fromlist=[True])
# We have a successful import. Get the dicts.
groupdict = module.GROUPS
# Create groups and populate them
for group in groupdict:
group = create.create_permission_group(group, desc=group,
group_perms=groupdict[group])
if not group:
print " Creation of Group '%s' failed." % group
def create_system_scripts():
"""
@ -231,7 +206,6 @@ def handle_setup(last_step):
create_connect_screens,
create_objects,
create_channels,
create_permission_groups,
create_system_scripts,
import_MUX_help_files,
start_game_time,
@ -240,7 +214,7 @@ def handle_setup(last_step):
if not settings.IMPORT_MUX_HELP:
# skip importing of the MUX helpfiles, they are
# not interesting except for developers.
del setup_queue[6]
del setup_queue[5]
#print " Initial setup: %s steps." % (len(setup_queue))

View file

@ -102,7 +102,10 @@ class TelnetProtocol(StatefulTelnetProtocol, session.Session):
def at_disconnect(self, reason="Connection closed. Goodbye for now."):
"""
Disconnect from server
"""
"""
char = self.get_character()
if char:
char.at_disconnect()
self.at_data_out(reason)
self.connectionLost(step=2)

View file

@ -246,6 +246,9 @@ class WebClientSession(session.Session):
"""
if reason:
self.lineSend(self.suid, reason)
char = self.get_character()
if char:
char.at_disconnect()
self.client.disconnect(self.suid, step=2)
def at_data_out(self, string='', data=None):

View file

@ -224,50 +224,36 @@ TIME_MONTH_PER_YEAR = 12
###################################################
# Game Permissions
# In-Game access
###################################################
# The module where the base permissions and
# groups are defined (read only once the very
# first time the server starts). If not set,
# src/permissions/permissions_setup.py is used.
PERMISSION_SETUP_MODULE = ""
# By defining a default player group to belong to,
# all players may start with some permissions pre-set.
# Available groups are set either above, or in
# src/permissions/permissions_setup.py.
# The access hiearchy, in climbing order. A higher
# permission in the hierarchy includes access of all
# levels below it.
PERMISSION_HIERARCHY = ("Players","PlayerHelpers","Builders", "Wizards", "Immortals")
# The default permission given to all new players
PERMISSION_PLAYER_DEFAULT = "Players"
# Tuple of modules implementing permission lock methods
# (see src/permissions/locks.py and
# src/permissions/permissions.py)
PERMISSION_FUNC_MODULES = ("src.permissions.lockfunc_default",)
# Tuple of modules implementing lock functions. All callable functions
# inside these modules will be available as lock functions.
LOCK_FUNC_MODULES = ("src.locks.lockfuncs",)
###################################################
# In-game Channels created from server start
###################################################
# Defines a dict with one key for each from-start
# channel. Each key points to a tuple containing
# (name, aliases, description, permissions)
# where aliases may be a tuple too, and permissions
# is a comma-separated string of permissions
# (see src/permissions/permissions.py)
# (name, aliases, description, locks)
# where aliases may be a tuple too, and locks is
# a valid lockstring definition.
# Default user channel for communication
CHANNEL_PUBLIC = ("Public", 'ooc', 'Public discussion',
'''chan_admin:has_id(1),
chan_listen:use_channels,
chan_send:use_channels''')
"admin:perm(Wizards);listen:all();send:all()")
# General info about the server
CHANNEL_MUDINFO = ("MUDinfo", '', 'Informative messages',
'''chan_admin:has_id(1),
chan_listen:Immortals,
chan_send:Immortals''')
"admin:perm(Immortals);listen:perm(Immortals);send:false()")
# Channel showing when new people connecting
CHANNEL_CONNECTINFO = ("MUDconnections", ('connections, mud_conns'),
'Connection log',
'''chan_admin:has_id(1),
chan_listen:Wizards,
chan_send:Wizards''')
"admin:perm(Immortals);listen:perm(Wizards);send:false()")
###################################################
# IMC2 Configuration
@ -444,7 +430,6 @@ INSTALLED_APPS = (
'src.irc',
'src.help',
'src.scripts',
'src.permissions',
'src.web.news',
'src.web.website',)
# The user profile extends the User object with more functionality;

View file

@ -32,9 +32,12 @@ from django.conf import settings
from django.utils.encoding import smart_str
from src.utils.idmapper.models import SharedMemoryModel
from src.typeclasses import managers
from src.locks.lockhandler import LockHandler
from src.utils import logger
from src.utils.utils import is_iter, has_parent
PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
# used by Attribute to efficiently identify stored object types.
# Note that these have to be updated if directory structure changes.
PARENTS = {
@ -81,7 +84,6 @@ class Attribute(SharedMemoryModel):
"""
#
# Attribute Database Model setup
#
@ -94,8 +96,8 @@ class Attribute(SharedMemoryModel):
db_value = models.TextField(blank=True, null=True)
# tells us what type of data is stored in the attribute
db_mode = models.CharField(max_length=20, null=True, blank=True)
# permissions to do things to this attribute
db_permissions = models.CharField(max_length=255, blank=True)
# Lock storage
db_lock_storage = models.TextField(blank=True)
# references the object the attribute is linked to (this is set
# by each child class to this abstact class)
db_obj = None # models.ForeignKey("RefencedObject")
@ -105,6 +107,12 @@ class Attribute(SharedMemoryModel):
# Database manager
objects = managers.AttributeManager()
# Lock handler self.locks
def __init__(self, *args, **kwargs):
"Initializes the parent first -important!"
SharedMemoryModel.__init__(self, *args, **kwargs)
self.locks = LockHandler(self)
class Meta:
"Define Django meta options"
abstract = True
@ -152,27 +160,6 @@ class Attribute(SharedMemoryModel):
self.save()
mode = property(mode_get, mode_set, mode_del)
# permissions property
#@property
def permissions_get(self):
"Getter. Allows for value = self.permissions. Returns a list of permissions."
if self.db_permissions:
return [perm.strip() for perm in self.db_permissions.split(',')]
return []
#@permissions.setter
def permissions_set(self, value):
"Setter. Allows for self.permissions = value. Stores as a comma-separated string."
if is_iter(value):
value = ",".join([str(val).strip().lower() for val in value])
self.db_permissions = value
self.save()
#@permissions.deleter
def permissions_del(self):
"Deleter. Allows for del self.permissions"
self.db_permissions = ""
self.save()
permissions = property(permissions_get, permissions_set, permissions_del)
# obj property (wraps db_obj)
#@property
def obj_get(self):
@ -256,6 +243,23 @@ class Attribute(SharedMemoryModel):
self.delete()
value = property(value_get, value_set, value_del)
# lock_storage property (wraps db_lock_storage)
#@property
def lock_storage_get(self):
"Getter. Allows for value = self.lock_storage"
return self.db_lock_storage
#@lock_storage.setter
def lock_storage_set(self, value):
"""Saves the lock_storage. This is usually not called directly, but through self.lock()"""
self.db_lock_storage = value
self.save()
#@lock_storage.deleter
def lock_storage_del(self):
"Deleter is disabled. Use the lockhandler.delete (self.lock.delete) instead"""
logger.log_errmsg("Lock_Storage (on %s) cannot be deleted. Use obj.lock.delete() instead." % self)
lock_storage = property(lock_storage_get, lock_storage_set, lock_storage_del)
#
#
# Attribute methods
@ -315,6 +319,15 @@ class Attribute(SharedMemoryModel):
#print "type identified: %s" % db_type[0]
return str(in_value.id), db_type[0]
def access(self, accessing_obj, access_type='read', default=False):
"""
Determines if another object has permission to access.
accessing_obj - object trying to access this one
access_type - type of access sought
default - what to return if no lock of access_type was found
"""
return self.locks.check(accessing_obj, access_type=access_type, default=default)
#------------------------------------------------------------
@ -360,10 +373,18 @@ class TypedObject(SharedMemoryModel):
db_date_created = models.DateTimeField(editable=False, auto_now_add=True)
# Permissions (access these through the 'permissions' property)
db_permissions = models.CharField(max_length=512, blank=True)
# Lock storage
db_lock_storage = models.TextField(blank=True)
# Database manager
objects = managers.TypedObjectManager()
# lock handler self.locks
def __init__(self, *args, **kwargs):
"We must initialize the parent first - important!"
SharedMemoryModel.__init__(self, *args, **kwargs)
self.locks = LockHandler(self)
class Meta:
"""
Django setup info.
@ -466,6 +487,24 @@ class TypedObject(SharedMemoryModel):
self.save()
permissions = property(permissions_get, permissions_set, permissions_del)
# lock_storage property (wraps db_lock_storage)
#@property
def lock_storage_get(self):
"Getter. Allows for value = self.lock_storage"
return self.db_lock_storage
#@lock_storage.setter
def lock_storage_set(self, value):
"""Saves the lock_storagetodate. This is usually not called directly, but through self.lock()"""
self.db_lock_storage = value
self.save()
#@lock_storage.deleter
def lock_storage_del(self):
"Deleter is disabled. Use the lockhandler.delete (self.lock.delete) instead"""
logger.log_errmsg("Lock_Storage (on %s) cannot be deleted. Use obj.lock.delete() instead." % self)
lock_storage = property(lock_storage_get, lock_storage_set, lock_storage_del)
#
#
# TypedObject main class methods and properties
@ -502,7 +541,6 @@ class TypedObject(SharedMemoryModel):
# typeclass' __getattribute__, since that one would
# try to look back to this very database object.)
typeclass = object.__getattribute__(self, 'typeclass')
#typeclass = object.__getattribute__(self, 'typeclass')
#print " '%s' not on db --> Checking typeclass %s instead." % (propname, typeclass)
if typeclass:
return object.__getattribute__(typeclass(self), propname)
@ -550,7 +588,8 @@ class TypedObject(SharedMemoryModel):
cmessage = "\n".join(["[NO MUDINFO CHANNEL]: %s" % line for line in message.split('\n')])
logger.log_errmsg(cmessage)
path = self.db_typeclass_path
#path = self.db_typeclass_path
path = object.__getattribute__(self, 'db_typeclass_path')
errstring = ""
if not path:
@ -967,3 +1006,38 @@ class TypedObject(SharedMemoryModel):
"Stop accidental deletion."
raise Exception("Cannot delete the ndb object!")
ndb = property(ndb_get, ndb_set, ndb_del)
# Lock / permission methods
def access(self, accessing_obj, access_type='read', default=False):
"""
Determines if another object has permission to access.
accessing_obj - object trying to access this one
access_type - type of access sought
default - what to return if no lock of access_type was found
"""
return self.locks.check(accessing_obj, access_type=access_type, default=default)
def has_perm(self, accessing_obj, access_type):
"Alias to access"
logger.log_depmsg("has_perm() is deprecated. Use access() instead.")
return self.access(accessing_obj, access_type)
def check_permstring(self, permstring):
"""
This explicitly checks for we hold particular permission without involving
any locks.
"""
if not permstring:
return False
perm = permstring.lower()
if perm in [p.lower() for p in self.permissions]:
# simplest case - we have a direct match
return True
if perm in PERMISSION_HIERARCHY:
# check if we have a higher hierarchy position
ppos = PERMISSION_HIERARCHY.index(perm)
return any(True for hpos, hperm in enumerate(PERMISSION_HIERARCHY)
if hperm in [p.lower() for p in self.permissions] and hpos > ppos)
return False

View file

@ -13,7 +13,6 @@ Models covered:
Objects
Scripts
Help
PermissionGroup
Message
Channel
Players
@ -22,9 +21,6 @@ Models covered:
from django.conf import settings
from django.contrib.auth.models import User
from django.db import IntegrityError
from src.permissions.permissions import set_perm
from src.permissions.models import PermissionGroup
from src.utils import logger
from src.utils.utils import is_iter, has_parent
@ -33,7 +29,7 @@ from src.utils.utils import is_iter, has_parent
#
def create_object(typeclass, key=None, location=None,
home=None, player=None, permissions=None, aliases=None):
home=None, player=None, permissions=None, locks=None, aliases=None):
"""
Create a new in-game object. Any game object is a combination
of a database object that stores data persistently to
@ -94,18 +90,21 @@ def create_object(typeclass, key=None, location=None,
new_object.player = player
player.obj = new_object
if permissions:
set_perm(new_object, permissions)
if aliases:
if not is_iter(aliases):
aliases = [aliases]
new_object.aliases = ",".join([alias.strip() for alias in aliases])
# call the hook method. This is where all at_creation
# customization happens as the typeclass stores custom
# things on its database object.
new_object.at_object_creation()
# custom-given variables override the hook
if permissions:
new_object.permissions = permissions
if aliases:
if not is_iter(aliases):
aliases = [aliases]
new_object.aliases = ",".join([alias.strip() for alias in aliases])
if locks:
new_object.locks.add(locks)
# perform a move_to in order to display eventual messages.
if home:
new_object.home = home
@ -121,7 +120,7 @@ def create_object(typeclass, key=None, location=None,
# Script creation
#
def create_script(typeclass, key=None, obj=None, autostart=True):
def create_script(typeclass, key=None, obj=None, locks=None, autostart=True):
"""
Create a new script. All scripts are a combination
of a database object that communicates with the
@ -177,15 +176,21 @@ def create_script(typeclass, key=None, obj=None, autostart=True):
new_db_object.name = "%s" % typeclass.__name__
else:
new_db_object.name = "#%i" % new_db_object.id
# call the hook method. This is where all at_creation
# customization happens as the typeclass stores custom
# things on its database object.
new_script.at_script_creation()
# custom-given variables override the hook
if locks:
new_script.locks.add(locks)
if obj:
try:
new_script.obj = obj
except ValueError:
new_script.obj = obj.dbobj
# call the hook method. This is where all at_creation
# customization happens as the typeclass stores custom
# things on its database object.
new_script.at_script_creation()
new_script.save()
@ -200,7 +205,7 @@ def create_script(typeclass, key=None, obj=None, autostart=True):
# Help entry creation
#
def create_help_entry(key, entrytext, category="General", permissions=None):
def create_help_entry(key, entrytext, category="General", locks=None):
"""
Create a static help entry in the help database. Note that Command
help entries are dynamic and directly taken from the __doc__ entries
@ -215,8 +220,8 @@ def create_help_entry(key, entrytext, category="General", permissions=None):
new_help.key = key
new_help.entrytext = entrytext
new_help.help_category = category
if permissions:
set_perm(new_help, permissions)
if locks:
new_help.locks.add(locks)
new_help.save()
return new_help
except IntegrityError:
@ -227,51 +232,13 @@ def create_help_entry(key, entrytext, category="General", permissions=None):
logger.log_trace()
return None
#
# Permission groups
#
def create_permission_group(group_name, desc=None, group_perms=None,
permissions=None):
"""
Adds a new group
group_name - case sensitive, unique key for group.
desc - description of permission group
group_perms - the permissions stored in this group - can be
a list of permission strings, a single permission
or a comma-separated string of permissions.
permissions - can be a list of permission strings, a single
permission or a comma-separated string of permissions.
OBS-these are the group's OWN permissions, for editing
the group etc - NOT the permissions stored in it!
"""
new_group = PermissionGroup.objects.filter(db_key__exact=group_name)
if new_group:
new_group = new_group[0]
else:
new_group = PermissionGroup()
new_group.key = group_name
if desc:
new_group.desc = desc
if group_perms:
if is_iter(group_perms):
group_perms = ",".join([str(perm) for perm in group_perms])
new_group.group_permissions = group_perms
if permissions:
set_perm(new_group, permissions)
new_group.save()
return new_group
#
# Comm system methods
#
def create_message(senderobj, message, channels=None,
receivers=None, permissions=None):
receivers=None, locks=None):
"""
Create a new communication message. Msgs are used for all
player-to-player communication, both between individual players
@ -284,7 +251,7 @@ def create_message(senderobj, message, channels=None,
may be actual channel objects or their unique key strings.
receivers - a player to send to, or a list of them. May be Player objects
or playernames.
permissions - permission string, or a list of permission strings.
locks - lock definition string
The Comm system is created very open-ended, so it's fully possible
to let a message both go to several channels and to several receivers
@ -325,13 +292,13 @@ def create_message(senderobj, message, channels=None,
new_message.receivers = [to_player(receiver) for receiver in
[to_object(receiver) for receiver in receivers]
if receiver]
if permissions:
set_perm(new_message, permissions)
if locks:
new_message.locks.add(locks)
new_message.save()
return new_message
def create_channel(key, aliases=None, desc=None,
permissions=None, keep_log=True):
locks=None, keep_log=True):
"""
Create A communication Channel. A Channel serves as a central
hub for distributing Msgs to groups of people without
@ -342,8 +309,7 @@ def create_channel(key, aliases=None, desc=None,
key - this must be unique.
aliases - list of alternative (likely shorter) keynames.
listen/send/admin permissions are strings if permissions separated
by commas.
locks - lock string definitions
"""
from src.comms.models import Channel
@ -361,8 +327,8 @@ def create_channel(key, aliases=None, desc=None,
string = "Could not add channel: key '%s' already exists." % key
logger.log_errmsg(string)
return None
if permissions:
set_perm(new_channel, permissions)
if locks:
new_channel.locks.add(locks)
new_channel.save()
channelhandler.CHANNELHANDLER.add_channel(new_channel)
return new_channel
@ -375,7 +341,7 @@ def create_player(name, email, password,
permissions=None,
create_character=True,
location=None, typeclass=None, home=None,
is_superuser=False, user=None):
is_superuser=False, user=None, locks=None):
"""
This creates a new player, handling the creation of the User
@ -425,11 +391,18 @@ def create_player(name, email, password,
new_player = PlayerDB(db_key=name, user=new_user)
new_player.save()
# assign mud permissions
if not permissions:
permissions = settings.PERMISSION_PLAYER_DEFAULT
set_perm(new_player, permissions)
# call hook method (may override default permissions)
new_player.at_player_creation()
# custom given arguments potentially overrides the hook
if permissions:
new_player.permissions = permissions
elif not new_player.permissions:
new_player.permissions = settings.PERMISSION_PLAYER_DEFAULT
if locks:
new_player.locks.add(locks)
# create *in-game* 'player' object
if create_character:
if not typeclass:
@ -437,8 +410,8 @@ def create_player(name, email, password,
# creating the object automatically links the player
# and object together by player.obj <-> obj.player
new_character = create_object(typeclass, name,
location, home,
location, home,
permissions=permissions,
player=new_player)
#set_perm(new_character, permissions)
return new_character
return new_player

View file

@ -67,3 +67,14 @@ def log_infomsg(infomsg):
infomsg = str(e)
for line in infomsg.splitlines():
log.msg('[..] %s' % line)
def log_depmsg(depmsg):
"""
Prints a deprecation message
"""
try:
depmsg = utils.to_str(depmsg)
except Exception, e:
depmsg = str(e)
for line in depmsg.splitlines():
log.msg('[DP] %s' % line)

View file

@ -10,12 +10,16 @@ from django.db.models.loading import AppCache
from django.utils.datastructures import SortedDict
from django.conf import settings
from src.scripts.models import ScriptDB
from src.objects.models import ObjectDB
from src.players.models import PlayerDB
from src.comms.models import Channel, Msg
from src.help.models import HelpEntry
from src.typeclasses import models as typeclassmodels
from src.objects import exithandler
from src.comms import channelhandler
from src.comms.models import Channel
from src.utils import reimport
from src.utils import logger
from src.utils import reimport, utils, logger
def reload_modules():
"""
@ -96,6 +100,21 @@ def reload_modules():
typeclassmodels.reset()
exithandler.EXITHANDLER.clear()
channelhandler.CHANNELHANDLER.update()
# run through all objects in database, forcing re-caching.
cemit_info(" Starting asynchronous object reset loop ...")
def run_reset_loop():
# run a reset loop on all objects
[(o.cmdset.reset(), o.locks.reset()) for o in ObjectDB.objects.all()]
[s.locks.reset() for s in ScriptDB.objects.all()]
[p.locks.reset() for p in PlayerDB.objects.all()]
[h.locks.reset() for h in HelpEntry.objects.all()]
[m.locks.reset() for m in Msg.objects.all()]
[c.locks.reset() for c in Channel.objects.all()]
at_return = lambda r: cemit_info(" ... @reload: Asynchronous reset loop finished.")
at_err = lambda e: cemit_info("%s\n@reload: Asynchronous reset loop exited with an error." % e)
utils.run_async(run_reset_loop, at_return, at_err)
def reload_scripts(scripts=None, obj=None, key=None,
dbref=None, init_mode=False):

View file

@ -32,7 +32,6 @@ from src.players.models import PlayerDB
from src.scripts.models import ScriptDB
from src.comms.models import Msg, Channel
from src.help.models import HelpEntry
from src.permissions.models import PermissionGroup
from src.config.models import ConfigValue
#
@ -136,20 +135,6 @@ channels = Channel.objects.channel_search
helpentries = HelpEntry.objects.search_help
#
# Search for a permission group
# Note that the name is case sensitive.
#
# def search_permissiongroup(self, ostring):
# """
# Find a permission group
#
# ostring = permission group name (case sensitive)
# or database dbref
# """
permgroups = PermissionGroup.objects.search_permgroup
#
# Get a configuration value
#

View file

@ -4,7 +4,7 @@ General helper functions that don't fit neatly under any given category.
They provide some useful string and conversion methods that might
be of use when designing your own game.
"""
import os, sys
import os, sys, imp
import textwrap
import datetime
from twisted.internet import threads
@ -188,7 +188,7 @@ def get_evennia_version():
def pypath_to_realpath(python_path, file_ending='.py'):
"""
Converts a path on dot python form (e.g. 'src.objects.models') to
a system path (src/objects/models.py). Calculates all paths as
a system path ($BASE_PATH/src/objects/models.py). Calculates all paths as
absoulte paths starting from the evennia main directory.
"""
pathsplit = python_path.strip().split('.')
@ -442,3 +442,48 @@ def has_parent(basepath, obj):
# this can occur if we tried to store a class object, not an
# instance. Not sure if one should defend against this.
return False
def mod_import(mod_path):
"""
Takes filename of a module, converts it to a python path
and imports it.
"""
def log_trace(errmsg=None):
"""
Log a traceback to the log. This should be called
from within an exception. errmsg is optional and
adds an extra line with added info.
"""
from traceback import format_exc
from twisted.python import log
tracestring = format_exc()
if tracestring:
for line in tracestring.splitlines():
log.msg('[::] %s' % line)
if errmsg:
try:
errmsg = to_str(errmsg)
except Exception, e:
errmsg = str(e)
for line in errmsg.splitlines():
log.msg('[EE] %s' % line)
if not os.path.isabs(mod_path):
mod_path = os.path.abspath(mod_path)
path, filename = mod_path.rsplit(os.path.sep, 1)
modname = filename.rstrip('.py')
try:
result = imp.find_module(modname, [path])
except ImportError:
log_trace("Could not find module '%s' (%s.py) at path '%s'" % (modname, modname, path))
try:
mod = imp.load_module(modname, *result)
except ImportError:
log_trace("Could not find or import module %s at path '%s'" % (modname, path))
mod = None
# we have to close the file handle manually
result[0].close()
return mod