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

View file

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

View file

@ -28,9 +28,11 @@ class CmdSetMeta(type):
# by default we key the cmdset the same as the # by default we key the cmdset the same as the
# name of its class. # name of its class.
mcs.key = mcs.__name__ mcs.key = mcs.__name__
mcs.path = "%s.%s" % (mcs.__module__, mcs.__name__)
if not type(mcs.key_mergetypes) == dict: if not type(mcs.key_mergetypes) == dict:
mcs.key_mergetypes = {} mcs.key_mergetypes = {}
super(CmdSetMeta, mcs).__init__(*args, **kwargs) 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]) module = __import__(modulepath, fromlist=[True])
cmdsetclass = module.__dict__[classname] cmdsetclass = module.__dict__[classname]
CACHED_CMDSETS[wanted_cache_key] = cmdsetclass CACHED_CMDSETS[wanted_cache_key] = cmdsetclass
#print "cmdset %s found." % wanted_cache_key
#instantiate the cmdset (and catch its errors) #instantiate the cmdset (and catch its errors)
if callable(cmdsetclass): if callable(cmdsetclass):
cmdsetclass = cmdsetclass(cmdsetobj) cmdsetclass = cmdsetclass(cmdsetobj)
@ -362,3 +361,21 @@ class CmdSetHandler(object):
self.cmdset_stack = [self.cmdset_stack[0]] self.cmdset_stack = [self.cmdset_stack[0]]
self.mergetype_stack = [self.cmdset_stack[0].mergetype] self.mergetype_stack = [self.cmdset_stack[0].mergetype]
self.update() 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 from src.utils.utils import is_iter
class CommandMeta(type): class CommandMeta(type):
@ -17,18 +17,26 @@ class CommandMeta(type):
""" """
Simply make sure all data are stored as lowercase and Simply make sure all data are stored as lowercase and
do checking on all properties that should be in list form. do checking on all properties that should be in list form.
Sets up locks to be more forgiving.
""" """
mcs.key = mcs.key.lower() mcs.key = mcs.key.lower()
if mcs.aliases and not is_iter(mcs.aliases): if mcs.aliases and not is_iter(mcs.aliases):
mcs.aliases = mcs.aliases.split(',') mcs.aliases = mcs.aliases.split(',')
mcs.aliases = [str(alias).strip().lower() for alias in mcs.aliases] 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(',') # pre-process locks as defined in class definition
mcs.permissions = [str(perm).strip().lower() for perm in mcs.permissions] 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() mcs.help_category = mcs.help_category.lower()
super(CommandMeta, mcs).__init__(*args, **kwargs) super(CommandMeta, mcs).__init__(*args, **kwargs)
# The Command class is the basic unit of an Evennia command; when # The Command class is the basic unit of an Evennia command; when
# defining new commands, the admin subclass this class and # defining new commands, the admin subclass this class and
# define their own parser method to handle the input. The # define their own parser method to handle the input. The
@ -69,15 +77,17 @@ class Command(object):
key = "command" key = "command"
# alternative ways to call the command (e.g. 'l', 'glance', 'examine') # alternative ways to call the command (e.g. 'l', 'glance', 'examine')
aliases = [] aliases = []
# a list of permission strings or comma-separated string limiting # a list of lock definitions on the form cmd:[NOT] func(args) [ AND|OR][ NOT] func2(args)
# access to this command. locks = ""
permissions = []
# used by the help system to group commands in lists. # used by the help system to group commands in lists.
help_category = "general" help_category = "general"
# There is also the property 'obj'. This gets set by the system # 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. # 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 # self.obj should NOT be defined here since it will not be overwritten
# if it already exists. # if it already exists.
def __init__(self):
self.lockhandler = LockHandler(self)
def __str__(self): def __str__(self):
"Print the command" "Print the command"
@ -115,15 +125,15 @@ class Command(object):
""" """
return (cmdname == self.key) or (cmdname in self.aliases) 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 This hook is called by the cmdhandler to determine if srcobj
is allowed to execute this command. It should return a boolean is allowed to execute this command. It should return a boolean
value and is not normally something that need to be changed since value and is not normally something that need to be changed since
it's using the Evennia permission system directly. 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 # Common Command hooks
def at_pre_cmd(self): def at_pre_cmd(self):

View file

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

View file

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

View file

@ -5,7 +5,6 @@ Building and world design commands
""" """
from django.conf import settings from django.conf import settings
from src.permissions.permissions import has_perm, has_perm_string
from src.objects.models import ObjectDB, ObjAttribute from src.objects.models import ObjectDB, ObjAttribute
from src.utils import create, utils, debug from src.utils import create, utils, debug
from src.commands.default.muxcommand import MuxCommand from src.commands.default.muxcommand import MuxCommand
@ -121,7 +120,7 @@ class CmdSetObjAlias(MuxCommand):
key = "@alias" key = "@alias"
aliases = "@setobjalias" aliases = "@setobjalias"
permissions = "cmd:setobjalias" locks = "cmd:perm(setobjalias) or perm(Builders)"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
@ -143,7 +142,7 @@ class CmdSetObjAlias(MuxCommand):
else: else:
caller.msg("No aliases exist for '%s'." % obj.key) caller.msg("No aliases exist for '%s'." % obj.key)
return 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.") caller.msg("You don't have permission to do that.")
return return
if not aliases or not aliases[0]: if not aliases or not aliases[0]:
@ -164,7 +163,7 @@ class CmdSetObjAlias(MuxCommand):
aliases = list(set(old_aliases)) aliases = list(set(old_aliases))
# save back to object. # save back to object.
obj.aliases = aliases 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): class CmdCopy(ObjManipCommand):
""" """
@ -183,7 +182,7 @@ class CmdCopy(ObjManipCommand):
""" """
key = "@copy" key = "@copy"
permissions = "cmd:copy" locks = "cmd:perm(copy) or perm(Builders)"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
@ -252,7 +251,7 @@ class CmdCpAttr(MuxCommand):
Copy the attribute one object to one or more attributes on another object. Copy the attribute one object to one or more attributes on another object.
""" """
key = "@cpattr" key = "@cpattr"
permissions = "cmd:cpattr" locks = "cmd:perm(cpattr) or perm(Builders)"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
@ -334,7 +333,7 @@ class CmdCreate(ObjManipCommand):
""" """
key = "@create" key = "@create"
permissions = "cmd:create" locks = "cmd:perm(create) or perm(Builders)"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
@ -366,8 +365,10 @@ class CmdCreate(ObjManipCommand):
# create object (if not a valid typeclass, the default # create object (if not a valid typeclass, the default
# object typeclass will automatically be used) # 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, obj = create.create_object(typeclass, name, caller,
home=caller, aliases=aliases) home=caller, aliases=aliases, locks=lockstring)
if not obj: if not obj:
string = "Error when creating object." string = "Error when creating object."
continue continue
@ -412,7 +413,7 @@ class CmdDebug(MuxCommand):
""" """
key = "@debug" key = "@debug"
permissions = "cmd:debug" locks = "cmd:perm(debug) or perm(Builders)"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
@ -462,7 +463,7 @@ class CmdDesc(MuxCommand):
""" """
key = "@desc" key = "@desc"
aliases = "@describe" aliases = "@describe"
permissions = "cmd:desc" locks = "cmd:perm(desc) or perm(Builders)"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
@ -504,7 +505,7 @@ class CmdDestroy(MuxCommand):
key = "@destroy" key = "@destroy"
aliases = "@delete" aliases = "@delete"
permissions = "cmd:destroy" locks = "cmd:perm(destroy) or perm(Builders)"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
@ -525,7 +526,7 @@ class CmdDestroy(MuxCommand):
if obj.player and not 'override' in self.switches: if obj.player and not 'override' in self.switches:
string = "Object %s is a player object. Use /override to delete anyway." % objname string = "Object %s is a player object. Use /override to delete anyway." % objname
continue continue
if not has_perm(caller, obj, 'create'): if not obj.access(caller, 'delete'):
string = "You don't have permission to delete %s." % objname string = "You don't have permission to delete %s." % objname
continue continue
# do the deletion # 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'. like to the name of the room and the exits in question; an example would be 'north;no;n'.
""" """
key = "@dig" key = "@dig"
permissions = "cmd:dig" locks = "cmd:perm(dig) or perm(Builders)"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
@ -681,7 +682,7 @@ class CmdLink(MuxCommand):
""" """
key = "@link" key = "@link"
permissions = "cmd:link" locks = "cmd:perm(link) or perm(Builders)"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
@ -759,7 +760,7 @@ class CmdListCmdSets(MuxCommand):
""" """
key = "@cmdsets" key = "@cmdsets"
aliases = "@listcmsets" aliases = "@listcmsets"
permissions = "cmd:listcmdsets" locks = "cmd:perm(listcmdsets) or perm(Builders)"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
@ -789,7 +790,7 @@ class CmdMvAttr(ObjManipCommand):
and target are the same, in which case this is like a copy operation) and target are the same, in which case this is like a copy operation)
""" """
key = "@mvattr" key = "@mvattr"
permissions = "cmd:mvattr" locks = "cmd:perm(mvattr) or perm(Builders)"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
@ -861,7 +862,7 @@ class CmdName(ObjManipCommand):
key = "@name" key = "@name"
aliases = ["@rename"] aliases = ["@rename"]
permissions = "cmd:rename" locks = "cmd:perm(rename) or perm(Builders)"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
@ -912,7 +913,7 @@ class CmdOpen(ObjManipCommand):
""" """
key = "@open" key = "@open"
permissions = "cmd:open" locks = "cmd:perm(open) or perm(Builders)"
help_category = "Building" help_category = "Building"
# a custom member method to chug out exits and do checks # a custom member method to chug out exits and do checks
@ -1053,7 +1054,7 @@ class CmdSetAttribute(ObjManipCommand):
""" """
key = "@set" key = "@set"
permissions = "cmd:set" locks = "cmd:perm(set) or perm(Builders)"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
@ -1128,7 +1129,7 @@ class CmdTypeclass(MuxCommand):
key = "@typeclass" key = "@typeclass"
aliases = "@type, @parent" aliases = "@type, @parent"
permissions = "cmd:typeclass" locks = "cmd:perm(typeclass) or perm(Builders)"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
@ -1166,7 +1167,7 @@ class CmdTypeclass(MuxCommand):
typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH, typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH,
typeclass) typeclass)
if not has_perm(caller, obj, 'change_typeclass'): if not obj.access(caller, 'edit'):
caller.msg("You are not allowed to do that.") caller.msg("You are not allowed to do that.")
return return
@ -1208,7 +1209,7 @@ class CmdWipe(ObjManipCommand):
matching the given attribute-wildcard search string. matching the given attribute-wildcard search string.
""" """
key = "@wipe" key = "@wipe"
permissions = "cmd:wipe" locks = "cmd:perm(wipe) or perm(Builders)"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
@ -1229,6 +1230,9 @@ class CmdWipe(ObjManipCommand):
obj = caller.search(objname) obj = caller.search(objname)
if not obj: if not obj:
return return
if not obj.access(caller, 'edit'):
caller.msg("You are not allowed to do that.")
return
if not attrs: if not attrs:
# wipe everything # wipe everything
for attr in obj.get_all_attributes(): for attr in obj.get_all_attributes():
@ -1241,7 +1245,90 @@ class CmdWipe(ObjManipCommand):
string = string % (",".join(attrs), obj.name) string = string % (",".join(attrs), obj.name)
caller.msg(string) 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): class CmdExamine(ObjManipCommand):
""" """
examine - detailed info on objects examine - detailed info on objects
@ -1255,7 +1342,7 @@ class CmdExamine(ObjManipCommand):
""" """
key = "@examine" key = "@examine"
aliases = ["@ex","ex", "exam"] aliases = ["@ex","ex", "exam"]
permissions = "cmd:examine" locks = "cmd:perm(examine) or perm(Builders)"
help_category = "Building" help_category = "Building"
def crop_line(self, text, heading="", line_width=79): def crop_line(self, text, heading="", line_width=79):
@ -1324,7 +1411,10 @@ class CmdExamine(ObjManipCommand):
string += "\n{wLocation{n: %s" % obj.location string += "\n{wLocation{n: %s" % obj.location
perms = obj.permissions perms = obj.permissions
if perms: 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"): 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 string += "\n{wCurrent Cmdset (before permission checks){n:\n\r %s" % obj.cmdset
if obj.scripts.all(): if obj.scripts.all():
@ -1360,7 +1450,7 @@ class CmdExamine(ObjManipCommand):
if not self.args: if not self.args:
# If no arguments are provided, examine the invoker's location. # If no arguments are provided, examine the invoker's location.
obj = caller.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. #If we don't have special info access, just look at the object instead.
caller.exec_cmd('look %s' % obj.name) caller.exec_cmd('look %s' % obj.name)
return return
@ -1379,7 +1469,7 @@ class CmdExamine(ObjManipCommand):
obj = caller.search(obj_name) obj = caller.search(obj_name)
if not obj: if not obj:
continue 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. #If we don't have special info access, just look at the object instead.
caller.exec_cmd('look %s' % obj_name) caller.exec_cmd('look %s' % obj_name)
continue continue
@ -1407,7 +1497,7 @@ class CmdFind(MuxCommand):
key = "@find" key = "@find"
aliases = "@locate, find, locate" aliases = "@locate, find, locate"
permissions = "cmd:find" locks = "cmd:perm(find) or perm(Builders)"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
@ -1447,7 +1537,7 @@ class CmdTeleport(MuxCommand):
""" """
key = "@tel" key = "@tel"
aliases = "@teleport" aliases = "@teleport"
permissions = "cmd:teleport" locks = "cmd:perm(teleport) or perm(Builders)"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
@ -1501,7 +1591,7 @@ class CmdUnLink(CmdLink):
# this is just a child of CmdLink # this is just a child of CmdLink
key = "@unlink" key = "@unlink"
permissions = "cmd:unlink" locks = "cmd:perm(unlink) or perm(Builders)"
help_key = "Building" help_key = "Building"
def func(self): def func(self):

View file

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

View file

@ -5,7 +5,6 @@ Comsys command module.
from src.comms.models import Channel, Msg, ChannelConnection from src.comms.models import Channel, Msg, ChannelConnection
from src.comms.channelhandler import CHANNELHANDLER from src.comms.channelhandler import CHANNELHANDLER
from src.utils import create, utils from src.utils import create, utils
from src.permissions.permissions import has_perm
from src.commands.default.muxcommand import MuxCommand from src.commands.default.muxcommand import MuxCommand
def find_channel(caller, channelname, silent=False): def find_channel(caller, channelname, silent=False):
@ -41,6 +40,7 @@ class CmdAddCom(MuxCommand):
key = "addcom" key = "addcom"
aliases = ["aliaschan","chanalias"] aliases = ["aliaschan","chanalias"]
help_category = "Comms" help_category = "Comms"
locks = "cmd:not perm(channel_banned)"
def func(self): def func(self):
"Implement the command" "Implement the command"
@ -67,7 +67,7 @@ class CmdAddCom(MuxCommand):
return return
# check permissions # 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.") caller.msg("You are not allowed to listen to this channel.")
return return
@ -83,7 +83,7 @@ class CmdAddCom(MuxCommand):
if alias: if alias:
# create a nick and add it to the caller. # 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'." string += "You can now refer to the channel %s with the alias '%s'."
caller.msg(string % (channel.key, alias)) caller.msg(string % (channel.key, alias))
else: else:
@ -106,6 +106,7 @@ class CmdDelCom(MuxCommand):
key = "delcom" key = "delcom"
aliases = ["delaliaschan, delchanalias"] aliases = ["delaliaschan, delchanalias"]
help_category = "Comms" help_category = "Comms"
locks = "cmd:not perm(channel_banned)"
def func(self): def func(self):
"Implementing the command. " "Implementing the command. "
@ -126,20 +127,20 @@ class CmdDelCom(MuxCommand):
return return
chkey = channel.key.lower() chkey = channel.key.lower()
# find all nicks linked to this channel and delete them # find all nicks linked to this channel and delete them
for nick in [nick for nick in caller.nicks for nick in [nick for nick in caller.nicks.get(nick_type="channel")
if nick.db_type == "channel" and nick.db_real.lower() == chkey]: if nick.db_real.lower() == chkey]:
nick.delete() nick.delete()
channel.disconnect_from(player) channel.disconnect_from(player)
caller.msg("You stop listening to channel '%s'. Eventual aliases were removed." % channel.key) caller.msg("You stop listening to channel '%s'. Eventual aliases were removed." % channel.key)
return return
else: else:
# we are removing a channel nick # 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) channel = find_channel(caller, channame, silent=True)
if not channel: if not channel:
caller.msg("No channel with alias '%s' was found." % ostring) caller.msg("No channel with alias '%s' was found." % ostring)
else: 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)) caller.msg("Your alias '%s' for channel %s was cleared." % (ostring, channel.key))
# def cmd_allcom(command): # def cmd_allcom(command):
@ -249,6 +250,7 @@ class CmdChannels(MuxCommand):
key = "@channels" key = "@channels"
aliases = ["@clist", "channels", "comlist", "chanlist", "channellist", "all channels"] aliases = ["@clist", "channels", "comlist", "chanlist", "channellist", "all channels"]
help_category = "Comms" help_category = "Comms"
locks = "cmd:all()"
def func(self): def func(self):
"Implement function" "Implement function"
@ -256,7 +258,7 @@ class CmdChannels(MuxCommand):
caller = self.caller caller = self.caller
# all channels we have available to listen to # 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: if not channels:
caller.msg("No channels available") caller.msg("No channels available")
return return
@ -265,7 +267,7 @@ class CmdChannels(MuxCommand):
if self.cmdstring != "comlist": if self.cmdstring != "comlist":
string = "\nAll available channels:" string = "\nChannels available:"
cols = [[" "], ["Channel"], ["Aliases"], ["Perms"], ["Description"]] cols = [[" "], ["Channel"], ["Aliases"], ["Perms"], ["Description"]]
for chan in channels: for chan in channels:
if chan in subs: if chan in subs:
@ -274,7 +276,7 @@ class CmdChannels(MuxCommand):
cols[0].append(" ") cols[0].append(" ")
cols[1].append(chan.key) cols[1].append(chan.key)
cols[2].append(",".join(chan.aliases)) cols[2].append(",".join(chan.aliases))
cols[3].append(",".join(chan.permissions)) cols[3].append(str(chan.locks))
cols[4].append(chan.desc) cols[4].append(chan.desc)
# put into table # put into table
for ir, row in enumerate(utils.format_table(cols)): for ir, row in enumerate(utils.format_table(cols)):
@ -284,18 +286,18 @@ class CmdChannels(MuxCommand):
string += "\n" + "".join(row) string += "\n" + "".join(row)
self.caller.msg(string) self.caller.msg(string)
string = "\nYour channel subscriptions:" string = "\nChannel subscriptions:"
if not subs: if not subs:
string += "(None)" string += "(None)"
else: else:
nicks = [nick for nick in caller.nicks if nick.db_type == 'channel'] nicks = [nick for nick in caller.nicks.get(nick_type="channel")]
print nicks cols = [[" "], ["Channel"], ["Aliases"], ["Description"]]
cols = [["Channel"], ["Aliases"], ["Description"]]
for chan in subs: for chan in subs:
cols[0].append(chan.key) cols[0].append(" ")
cols[1].append(",".join([nick.db_nick for nick in nicks 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)) if nick.db_real.lower() == chan.key.lower()] + chan.aliases))
cols[2].append(chan.desc) cols[3].append(chan.desc)
# put into table # put into table
for ir, row in enumerate(utils.format_table(cols)): for ir, row in enumerate(utils.format_table(cols)):
if ir == 0: if ir == 0:
@ -316,6 +318,7 @@ class CmdCdestroy(MuxCommand):
key = "@cdestroy" key = "@cdestroy"
help_category = "Comms" help_category = "Comms"
locks = "cmd:all()"
def func(self): def func(self):
"Destroy objects cleanly." "Destroy objects cleanly."
@ -328,7 +331,7 @@ class CmdCdestroy(MuxCommand):
if not channel: if not channel:
caller.msg("Could not find channel %s." % self.args) caller.msg("Could not find channel %s." % self.args)
return 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.") caller.msg("You are not allowed to do that.")
return return
@ -572,7 +575,7 @@ class CmdChannelCreate(MuxCommand):
key = "@ccreate" key = "@ccreate"
aliases = "channelcreate" aliases = "channelcreate"
permissions = "cmd:ccreate" locks = "cmd:not perm(channel_banned)"
help_category = "Comms" help_category = "Comms"
def func(self): def func(self):
@ -601,9 +604,8 @@ class CmdChannelCreate(MuxCommand):
caller.msg("A channel with that name already exists.") caller.msg("A channel with that name already exists.")
return return
# Create and set the channel up # Create and set the channel up
permissions = "chan_send:%s,chan_listen:%s,chan_admin:has_id(%s)" % \ lockstring = "send:all();listen:all();admin:id(%s)" % caller.id
("Players","Players",caller.id) new_chan = create.create_channel(channame, aliases, description, locks=lockstring)
new_chan = create.create_channel(channame, aliases, description, permissions)
new_chan.connect_to(caller) new_chan.connect_to(caller)
caller.msg("Created channel %s and connected to it." % new_chan.key) caller.msg("Created channel %s and connected to it." % new_chan.key)
@ -663,7 +665,7 @@ class CmdCdesc(MuxCommand):
""" """
key = "@cdesc" key = "@cdesc"
permissions = "cmd:cdesc" locks = "cmd:not perm(channel_banned)"
help_category = "Comms" help_category = "Comms"
def func(self): def func(self):
@ -679,7 +681,7 @@ class CmdCdesc(MuxCommand):
caller.msg("Channel '%s' not found." % self.lhs) caller.msg("Channel '%s' not found." % self.lhs)
return return
#check permissions #check permissions
if not has_perm(caller, channel, 'channel_admin'): if not caller.access(caller, 'admin'):
caller.msg("You cant admin this channel.") caller.msg("You cant admin this channel.")
return return
# set the description # set the description
@ -694,19 +696,19 @@ class CmdPage(MuxCommand):
Usage: Usage:
page[/switches] [<player>,<player>,... = <message>] page[/switches] [<player>,<player>,... = <message>]
tell '' tell ''
page/list <number> page <number>
Switch: 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 Send a message to target user (if online). If no
argument is given, you will instead see who was the last argument is given, you will get a list of your latest messages.
person you paged to.
""" """
key = "page" key = "page"
aliases = ['tell'] aliases = ['tell']
permissions = "cmd:tell" locks = "cmd:not perm(page_banned)"
help_category = "Comms" help_category = "Comms"
def func(self): def func(self):
@ -722,17 +724,29 @@ class CmdPage(MuxCommand):
if msg.receivers] if msg.receivers]
# get last messages we've got # get last messages we've got
pages_we_got = list(Msg.objects.get_messages_by_receiver(player)) 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 = pages_we_sent + pages_we_got
pages.sort(lambda x, y: cmp(x.date_sent, y.date_sent)) pages.sort(lambda x, y: cmp(x.date_sent, y.date_sent))
number = 10 number = 5
if self.args: if self.args:
try: try:
number = int(self.args) number = int(self.args)
except ValueError: except ValueError:
pass caller.msg("Usage: tell [<player> = msg]")
return
if len(pages) > number: if len(pages) > number:
lastpages = pages[-number:] lastpages = pages[-number:]
@ -744,19 +758,14 @@ class CmdPage(MuxCommand):
"{n,{c ".join([obj.name for obj in page.receivers]), "{n,{c ".join([obj.name for obj in page.receivers]),
page.message) page.message)
for page in lastpages]) for page in lastpages])
caller.msg("Your latest pages:\n %s" % lastpages )
return
if not self.args or not self.rhs: if lastpages:
if pages_we_sent: string = "Your latest pages:\n %s" % lastpages
string = "You last paged {c%s{n." % (", ".join([obj.name
for obj in pages_we_sent[-1].receivers]))
caller.msg(string)
return
else: else:
string = "You haven't paged anyone yet." string = "You haven't paged anyone yet."
caller.msg(string) caller.msg(string)
return return
# We are sending. Build a list of targets # We are sending. Build a list of targets
@ -786,7 +795,7 @@ class CmdPage(MuxCommand):
if not recobjs: if not recobjs:
caller.msg("No players matching your target were found.") caller.msg("No players matching your target were found.")
return return
header = "{wPlayer{n {c%s{n {wpages:{n" % caller.key header = "{wPlayer{n {c%s{n {wpages:{n" % caller.key
message = self.rhs message = self.rhs
@ -800,12 +809,17 @@ class CmdPage(MuxCommand):
# tell the players they got a message. # tell the players they got a message.
received = [] received = []
rstrings = []
for pobj in recobjs: 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: if hasattr(pobj, 'has_player') and not pobj.has_player:
received.append("{C%s{n" % pobj.name) 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: else:
received.append("{c%s{n" % pobj.name) received.append("{c%s{n" % pobj.name)
received = ", ".join(received) if rstrings:
caller.msg("You paged %s with: '%s'." % (received, message)) 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 import time
from django.conf import settings from django.conf import settings
from src.server.sessionhandler import SESSIONS 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.objects.models import HANDLE_SEARCH_ERRORS
from src.utils import utils from src.utils import utils
from src.objects.models import Nick from src.objects.models import Nick
@ -23,7 +21,7 @@ class CmdHome(MuxCommand):
""" """
key = "home" key = "home"
permissions = "cmd:home" locks = "cmd:perm(home) or perm(Builders)"
def func(self): def func(self):
"Implement the command" "Implement the command"
@ -48,6 +46,7 @@ class CmdLook(MuxCommand):
""" """
key = "look" key = "look"
aliases = ["l"] aliases = ["l"]
locks = "cmd:all()"
def func(self): def func(self):
""" """
@ -84,6 +83,7 @@ class CmdPassword(MuxCommand):
Changes your password. Make sure to pick a safe one. Changes your password. Make sure to pick a safe one.
""" """
key = "@password" key = "@password"
locks = "cmd:all()"
def func(self): def func(self):
"hook function." "hook function."
@ -138,7 +138,8 @@ class CmdNick(MuxCommand):
""" """
key = "nick" key = "nick"
aliases = ["nickname", "nicks", "@nick", "alias"] aliases = ["nickname", "nicks", "@nick", "alias"]
locks = "cmd:all()"
def func(self): def func(self):
"Create the nickname" "Create the nickname"
@ -185,7 +186,7 @@ class CmdNick(MuxCommand):
if oldnick: if oldnick:
# clear the alias # clear the alias
string += "\nNick '%s' (= '%s') was cleared." % (nick, oldnick[0].db_real) 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: else:
string += "\nNo nick '%s' found, so it could not be removed." % nick string += "\nNo nick '%s' found, so it could not be removed." % nick
else: else:
@ -194,7 +195,7 @@ class CmdNick(MuxCommand):
string += "\nNick %s changed from '%s' to '%s'." % (nick, oldnick[0].db_real, real) string += "\nNick %s changed from '%s' to '%s'." % (nick, oldnick[0].db_real, real)
else: else:
string += "\nNick set: '%s' = '%s'." % (nick, real) 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) caller.msg(string)
class CmdInventory(MuxCommand): class CmdInventory(MuxCommand):
@ -209,6 +210,7 @@ class CmdInventory(MuxCommand):
""" """
key = "inventory" key = "inventory"
aliases = ["inv", "i"] aliases = ["inv", "i"]
locks = "cmd:all()"
def func(self): def func(self):
"hook function" "hook function"
@ -237,7 +239,8 @@ class CmdGet(MuxCommand):
""" """
key = "get" key = "get"
aliases = "grab" aliases = "grab"
locks = "cmd:all()"
def func(self): def func(self):
"implements the command." "implements the command."
@ -256,7 +259,7 @@ class CmdGet(MuxCommand):
# don't allow picking up player objects, nor exits. # don't allow picking up player objects, nor exits.
caller.msg("You can't get that.") caller.msg("You can't get that.")
return return
if not has_perm(caller, obj, 'get'): if not obj.access(caller, 'get'):
if obj.db.get_err_msg: if obj.db.get_err_msg:
caller.msg(obj.db.get_err_msg) caller.msg(obj.db.get_err_msg)
else: else:
@ -285,6 +288,7 @@ class CmdDrop(MuxCommand):
""" """
key = "drop" key = "drop"
locks = "cmd:all()"
def func(self): def func(self):
"Implement command" "Implement command"
@ -322,14 +326,15 @@ class CmdQuit(MuxCommand):
Gracefully disconnect from the game. Gracefully disconnect from the game.
""" """
key = "@quit" key = "@quit"
locks = "cmd:all()"
def func(self): def func(self):
"hook function" "hook function"
sessions = self.caller.sessions sessions = self.caller.sessions
for session in sessions: for session in sessions:
session.msg("Quitting. Hope to see you soon again.") session.msg("Quitting. Hope to see you soon again.")
session.at_disconnect() session.session_disconnect()
class CmdWho(MuxCommand): class CmdWho(MuxCommand):
""" """
who who
@ -357,7 +362,7 @@ class CmdWho(MuxCommand):
if self.cmdstring == "doing": if self.cmdstring == "doing":
show_session_data = False show_session_data = False
else: 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: if show_session_data:
table = [["Player Name"], ["On for"], ["Idle"], ["Room"], ["Cmds"], ["Host"]] table = [["Player Name"], ["On for"], ["Idle"], ["Room"], ["Cmds"], ["Host"]]
@ -411,6 +416,7 @@ class CmdSay(MuxCommand):
key = "say" key = "say"
aliases = ['"'] aliases = ['"']
locks = "cmd:all()"
def func(self): def func(self):
"Run the say command" "Run the say command"
@ -505,6 +511,7 @@ class CmdPose(MuxCommand):
""" """
key = "pose" key = "pose"
aliases = [":", "emote"] aliases = [":", "emote"]
locks = "cmd:all()"
def parse(self): def parse(self):
""" """
@ -608,6 +615,7 @@ class CmdEncoding(MuxCommand):
key = "@encoding" key = "@encoding"
aliases = "@encode" aliases = "@encode"
locks = "cmd:all()"
def func(self): def func(self):
""" """
@ -640,39 +648,31 @@ class CmdEncoding(MuxCommand):
string = "Your custom text encoding was changed from '%s' to '%s'." % (old_encoding, encoding) string = "Your custom text encoding was changed from '%s' to '%s'." % (old_encoding, encoding)
caller.msg(string) caller.msg(string)
class CmdGroup(MuxCommand): class CmdAccess(MuxCommand):
""" """
group - show your groups access - show access groups
Usage: Usage:
group access
This command shows you which user permission groups This command shows you the permission hierarchy and
you are a member of, if any. which permission groups you are a member of.
""" """
key = "access" key = "access"
aliases = "groups" aliases = ["groups", "hierarchy"]
locks = "cmd:all()"
def func(self): def func(self):
"Load the permission groups" "Load the permission groups"
caller = self.caller caller = self.caller
hierarchy_full = settings.PERMISSION_HIERARCHY
string = "" string = "\n{wPermission Hierarchy{n (climbing):\n %s" % ", ".join(hierarchy_full)
if caller.player and caller.player.is_superuser: hierarchy = [p.lower() for p in hierarchy_full]
string += "\n This is a SUPERUSER account! Group membership does not matter." string += "\n{wYour access{n:"
else: string += "\nCharacter %s: %s" % (caller.key, ", ".join(caller.permissions))
# get permissions and determine if they are groups if hasattr(caller, 'player'):
perms = list(set(caller.permissions + caller.player.permissions)) string += "\nPlayer %s: %s" % (caller.player.key, ", ".join(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."
caller.msg(string) caller.msg(string)
## def cmd_apropos(command): ## 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.utils.utils import fill, dedent
from src.commands.command import Command from src.commands.command import Command
from src.help.models import HelpEntry from src.help.models import HelpEntry
from src.permissions.permissions import has_perm
from src.utils import create from src.utils import create
from src.commands.default.muxcommand import MuxCommand from src.commands.default.muxcommand import MuxCommand
@ -65,6 +64,8 @@ class CmdHelp(Command):
topics related to the game. topics related to the game.
""" """
key = "help" key = "help"
locks = "cmd:all()"
# this is a special cmdhandler flag that makes the cmdhandler also pack # this is a special cmdhandler flag that makes the cmdhandler also pack
# the current cmdset with the call to self.func(). # the current cmdset with the call to self.func().
return_cmdset = True return_cmdset = True
@ -73,6 +74,7 @@ class CmdHelp(Command):
""" """
inp is a string containing the command or topic match. inp is a string containing the command or topic match.
""" """
self.original_args = self.args.strip()
self.args = self.args.strip().lower() self.args = self.args.strip().lower()
def func(self): def func(self):
@ -94,7 +96,7 @@ class CmdHelp(Command):
if query in LIST_ARGS: if query in LIST_ARGS:
# we want to list all available help entries # we want to list all available help entries
hdict_cmd = {} 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('__') if not cmd.key.startswith('__')
and not (hasattr(cmd, 'is_exit') and cmd.is_exit)): and not (hasattr(cmd, 'is_exit') and cmd.is_exit)):
if hdict_cmd.has_key(cmd.help_category): if hdict_cmd.has_key(cmd.help_category):
@ -103,7 +105,7 @@ class CmdHelp(Command):
hdict_cmd[cmd.help_category] = [cmd.key] hdict_cmd[cmd.help_category] = [cmd.key]
hdict_db = {} hdict_db = {}
for topic in (topic for topic in HelpEntry.objects.get_all_topics() 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): if hdict_db.has_key(topic.help_category):
hdict_db[topic.help_category].append(topic.key) hdict_db[topic.help_category].append(topic.key)
else: else:
@ -116,7 +118,7 @@ class CmdHelp(Command):
# Cmd auto-help dynamic entries # Cmd auto-help dynamic entries
cmdmatches = [cmd for cmd in cmdset 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: if len(cmdmatches) > 1:
# multiple matches. Try to limit it down to exact match # multiple matches. Try to limit it down to exact match
exactmatches = [cmd for cmd in cmdmatches if cmd == query] exactmatches = [cmd for cmd in cmdmatches if cmd == query]
@ -127,12 +129,12 @@ class CmdHelp(Command):
dbmatches = \ dbmatches = \
[topic for topic in [topic for topic in
HelpEntry.objects.find_topicmatch(query, exact=False) HelpEntry.objects.find_topicmatch(query, exact=False)
if has_perm(caller, topic, 'view')] if topic.access(caller, 'view', default=True)]
if len(dbmatches) > 1: if len(dbmatches) > 1:
exactmatches = \ exactmatches = \
[topic for topic in [topic for topic in
HelpEntry.objects.find_topicmatch(query, exact=True) HelpEntry.objects.find_topicmatch(query, exact=True)
if has_perm(caller, topic, 'view')] if topic.access(caller, 'view', default=True)]
if exactmatches: if exactmatches:
dbmatches = exactmatches dbmatches = exactmatches
@ -140,11 +142,11 @@ class CmdHelp(Command):
if (not cmdmatches) and (not dbmatches): if (not cmdmatches) and (not dbmatches):
# no normal match. Check if this is a category match instead # no normal match. Check if this is a category match instead
categ_cmdmatches = [cmd.key for cmd in cmdset 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 = \ categ_dbmatches = \
[topic.key for topic in [topic.key for topic in
HelpEntry.objects.find_topics_with_category(query) HelpEntry.objects.find_topics_with_category(query)
if has_perm(caller, topic, 'view')] if topic.access(caller, 'view', default=True)]
cmddict = None cmddict = None
dbdict = None dbdict = None
if categ_cmdmatches: if categ_cmdmatches:
@ -154,7 +156,7 @@ class CmdHelp(Command):
if cmddict or dbdict: if cmddict or dbdict:
help_entry = format_help_list(cmddict, dbdict) help_entry = format_help_list(cmddict, dbdict)
else: 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: elif len(cmdmatches) == 1:
# we matched against a command name or alias. Show its help entry. # we matched against a command name or alias. Show its help entry.
@ -184,7 +186,7 @@ class CmdSetHelp(MuxCommand):
@help - edit the help database @help - edit the help database
Usage: Usage:
@help[/switches] <topic>[,category[,permission,permission,...]] = <text> @help[/switches] <topic>[,category[,locks]] = <text>
Switches: Switches:
add - add or replace a new topic with text. add - add or replace a new topic with text.
@ -203,7 +205,7 @@ class CmdSetHelp(MuxCommand):
""" """
key = "@help" key = "@help"
aliases = "@sethelp" aliases = "@sethelp"
permissions = "cmd:sethelp" locks = "cmd:perm(PlayerHelpers)"
help_category = "Building" help_category = "Building"
def func(self): def func(self):
@ -214,36 +216,33 @@ class CmdSetHelp(MuxCommand):
lhslist = self.lhslist lhslist = self.lhslist
rhs = self.rhs rhs = self.rhs
if not self.rhs: if not self.args:
caller.msg("Usage: @sethelp/[add|del|append|merge] <topic>[,category[,permission,..] = <text>]") caller.msg("Usage: @sethelp/[add|del|append|merge] <topic>[,category[,locks,..] = <text>]")
return return
topicstr = "" topicstr = ""
category = "" category = ""
permissions = "" lockstring = ""
try: try:
topicstr = lhslist[0] topicstr = lhslist[0]
category = lhslist[1] category = lhslist[1]
permissions = ",".join(lhslist[2:]) lockstring = ",".join(lhslist[2:])
except Exception: except Exception:
pass pass
if not topicstr: if not topicstr:
caller.msg("You have to define a topic!") caller.msg("You have to define a topic!")
return return
string = "" string = ""
print topicstr, category, permissions #print topicstr, category, lockstring
if switches and switches[0] in ('append', 'app','merge'): if switches and switches[0] in ('append', 'app','merge'):
# add text to the end of a help topic # add text to the end of a help topic
# find the topic to append to # find the topic to append to
old_entry = None old_entry = HelpEntry.objects.filter(db_key__iexact=topicstr)
try:
old_entry = HelpEntry.objects.get(key=topicstr)
except Exception:
pass
if not old_entry: if not old_entry:
string = "Could not find topic '%s'. You must give an exact name." % topicstr string = "Could not find topic '%s'. You must give an exact name." % topicstr
else: else:
old_entry = old_entry[0]
entrytext = old_entry.entrytext entrytext = old_entry.entrytext
if switches[0] == 'merge': if switches[0] == 'merge':
old_entry.entrytext = "%s %s" % (entrytext, self.rhs) old_entry.entrytext = "%s %s" % (entrytext, self.rhs)
@ -255,15 +254,11 @@ class CmdSetHelp(MuxCommand):
elif switches and switches[0] in ('delete','del'): elif switches and switches[0] in ('delete','del'):
#delete a help entry #delete a help entry
old_entry = None old_entry = HelpEntry.objects.filter(db_key__iexact=topicstr)
try:
old_entry = HelpEntry.objects.get(key=topicstr)
except Exception:
pass
if not old_entry: if not old_entry:
string = "Could not find topic. You must give an exact name." string = "Could not find topic '%s'." % topicstr
else: else:
old_entry.delete() old_entry[0].delete()
string = "Deleted the help entry '%s'." % topicstr string = "Deleted the help entry '%s'." % topicstr
else: else:
@ -279,7 +274,8 @@ class CmdSetHelp(MuxCommand):
old_entry.key = topicstr old_entry.key = topicstr
old_entry.entrytext = self.rhs old_entry.entrytext = self.rhs
old_entry.help_category = category old_entry.help_category = category
old_entry.permissions = permissions old_entry.locks.clear()
old_entry.locks.add(lockstring)
old_entry.save() old_entry.save()
string = "Overwrote the old topic '%s' with a new one." % topicstr string = "Overwrote the old topic '%s' with a new one." % topicstr
else: else:
@ -287,7 +283,8 @@ class CmdSetHelp(MuxCommand):
else: else:
# no old entry. Create a new one. # no old entry. Create a new one.
new_entry = create.create_help_entry(topicstr, new_entry = create.create_help_entry(topicstr,
rhs, category, permissions) rhs, category, lockstring)
if new_entry: if new_entry:
string = "Topic '%s' was successfully created." % topicstr string = "Topic '%s' was successfully created." % topicstr
else: else:

View file

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

View file

@ -9,9 +9,11 @@ import os, datetime
import django, twisted import django, twisted
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.conf import settings
from src.server.sessionhandler import SESSIONS from src.server.sessionhandler import SESSIONS
from src.scripts.models import ScriptDB from src.scripts.models import ScriptDB
from src.objects.models import ObjectDB from src.objects.models import ObjectDB
from src.players.models import PlayerDB
from src.config.models import ConfigValue from src.config.models import ConfigValue
from src.utils import reloads, create, logger, utils, gametime from src.utils import reloads, create, logger, utils, gametime
from src.commands.default.muxcommand import MuxCommand from src.commands.default.muxcommand import MuxCommand
@ -28,7 +30,7 @@ class CmdReload(MuxCommand):
re-validates all scripts. re-validates all scripts.
""" """
key = "@reload" key = "@reload"
permissions = "cmd:reload" locks = "cmd:perm(reload) or perm(Immortals)"
help_category = "System" help_category = "System"
def func(self): def func(self):
@ -84,7 +86,7 @@ class CmdPy(MuxCommand):
""" """
key = "@py" key = "@py"
aliases = ["!"] aliases = ["!"]
permissions = "cmd:py" locks = "cmd:perm(py) or perm(Immortals)"
help_category = "System" help_category = "System"
def func(self): def func(self):
@ -130,7 +132,7 @@ class CmdPy(MuxCommand):
obj.delete() obj.delete()
script.delete() script.delete()
class CmdListScripts(MuxCommand): class CmdScripts(MuxCommand):
""" """
Operate on scripts. Operate on scripts.
@ -149,7 +151,7 @@ class CmdListScripts(MuxCommand):
""" """
key = "@scripts" key = "@scripts"
aliases = "@listscripts" aliases = "@listscripts"
permissions = "cmd:listscripts" locks = "cmd:perm(listscripts) or perm(Wizards)"
help_category = "System" help_category = "System"
def format_script_list(self, scripts): 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 Give a summary of object types in database
@ -267,8 +269,8 @@ class CmdListObjects(MuxCommand):
given, <nr> defaults to 10. given, <nr> defaults to 10.
""" """
key = "@objects" key = "@objects"
aliases = ["@listobjects", "@listobjs"] aliases = ["@listobjects", "@listobjs", '@stats', '@db']
permissions = "cmd:listobjects" locks = "cmd:perm(listobjects) or perm(Builders)"
help_category = "System" help_category = "System"
def func(self): def func(self):
@ -280,9 +282,24 @@ class CmdListObjects(MuxCommand):
nlim = int(self.args) nlim = int(self.args)
else: else:
nlim = 10 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() dbtotals = ObjectDB.objects.object_totals()
#print dbtotals
string = "\n{wDatase Object totals:{n"
table = [["Count"], ["Typeclass"]] table = [["Count"], ["Typeclass"]]
for path, count in dbtotals.items(): for path, count in dbtotals.items():
table[0].append(count) table[0].append(count)
@ -332,7 +349,7 @@ class CmdService(MuxCommand):
""" """
key = "@service" key = "@service"
permissions = "cmd:service" locks = "cmd:perm(service) or perm(Immortals)"
help_category = "System" help_category = "System"
def func(self): def func(self):
@ -351,7 +368,7 @@ class CmdService(MuxCommand):
sessions = caller.sessions sessions = caller.sessions
if not sessions: if not sessions:
return return
service_collection = sessions[0].server.service_collection service_collection = SESSIONS.server.services
if switch == "list": if switch == "list":
# Just display the list of installed services and their # Just display the list of installed services and their
@ -417,7 +434,7 @@ class CmdShutdown(MuxCommand):
Shut the game server down gracefully. Shut the game server down gracefully.
""" """
key = "@shutdown" key = "@shutdown"
permissions = "cmd:shutdown" locks = "cmd:perm(shutdown) or perm(Immortals)"
help_category = "System" help_category = "System"
def func(self): def func(self):
@ -468,159 +485,124 @@ class CmdTime(MuxCommand):
""" """
key = "@time" key = "@time"
aliases = "@uptime" aliases = "@uptime"
permissions = "cmd:time" locks = "cmd:perm(time) or perm(Players)"
help_category = "System" help_category = "System"
def func(self): def func(self):
"Show times." "Show times."
string1 = "\nCurrent server uptime: \t" table = [["Current server uptime:",
string1 += "{w%s{n" % (utils.time_format(gametime.uptime(format=False), 2)) "Total server running time:",
"Total in-game time (realtime x %g):" % (gametime.TIMEFACTOR),
string2 = "\nTotal server running time: \t" "Server time stamp:"
string2 += "{w%s{n" % (utils.time_format(gametime.runtime(format=False), 2)) ],
[utils.time_format(gametime.uptime(format=False), 2),
string3 = "\nTotal in-game time (realtime x %g):\t" % (gametime.TIMEFACTOR) utils.time_format(gametime.runtime(format=False), 2),
string3 += "{w%s{n" % (utils.time_format(gametime.gametime(format=False), 2)) utils.time_format(gametime.gametime(format=False), 2),
datetime.datetime.now()
string4 = "\nServer time stamp: \t" ]]
string4 += "{w%s{n" % (str(datetime.datetime.now())) if utils.host_os_is('posix'):
string5 = ""
if not utils.host_os_is('nt'):
# os.getloadavg() is not available on Windows.
loadavg = os.getloadavg() loadavg = os.getloadavg()
string5 += "\nServer load (per minute): \t" table[0].append("Server load (per minute):")
string5 += "{w%g%%{n" % (100 * loadavg[0]) table[1].append("{w%g%%{n" % (100 * loadavg[0]))
string = "%s%s%s%s%s" % (string1, string2, string3, string4, string5) 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) self.caller.msg(string)
class CmdList(MuxCommand): class CmdServerLoad(MuxCommand):
""" """
@list - list info server load statistics
Usage: Usage:
@list <option> @serverload
Options: Show server load statistics in a table.
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.
""" """
key = "@list" key = "@serverload"
permissions = "cmd:list" locks = "cmd:perm(list) or perm(Immortals)"
help_category = "System" help_category = "System"
def func(self): def func(self):
"Show list." "Show list."
caller = self.caller caller = self.caller
if not self.args:
caller.msg("Usage: @list process|objects|scripts|perms")
return
string = "" # display active processes
if self.arglist[0] in ["proc","process"]:
# display active processes if not utils.host_os_is('posix'):
string = "Process listings are only available under Linux/Unix."
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")
else: else:
string = "'%s' is not a valid option." % self.arglist[0] import resource
# send info 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) caller.msg(string)
#TODO - expand @ps as we add irc/imc2 support. #TODO - expand @ps as we add irc/imc2 support.
class CmdPs(MuxCommand): class CmdPs(MuxCommand):
""" """
@ps - list processes list processes
Usage Usage
@ps @ps
Shows the process/event table. Shows the process/event table.
""" """
key = "@ps" key = "@ps"
permissions = "cmd:ps" locks = "cmd:perm(ps) or perm(Builders)"
help_category = "System" help_category = "System"
def func(self): def func(self):
@ -651,36 +633,4 @@ class CmdPs(MuxCommand):
string += "\n{wTotal{n: %d scripts." % len(all_scripts) string += "\n{wTotal{n: %d scripts." % len(all_scripts)
self.caller.msg(string) 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: except ImportError:
from django.test import TestCase from django.test import TestCase
from django.conf import settings from django.conf import settings
from src.utils import create from src.utils import create, ansi
from src.server import session, sessionhandler from src.server import session, sessionhandler
from src.locks.lockhandler import LockHandler
from src.config.models import ConfigValue 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!) # print all feedback from test commands (can become very verbose!)
VERBOSE = False VERBOSE = False
NOMANGLE = False
class FakeSession(session.Session): class FakeSession(session.Session):
""" """
@ -55,8 +57,13 @@ class FakeSession(session.Session):
else: else:
rstring = return_list rstring = return_list
self.player.character.ndb.return_string = None self.player.character.ndb.return_string = None
if not message.startswith(rstring): message_noansi = ansi.parse_ansi(message, strip_ansi=True).strip()
retval = "Returned message ('%s') != desired message ('%s')" % (message, rstring) 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) raise AssertionError(retval)
if VERBOSE: if VERBOSE:
print message print message
@ -77,17 +84,28 @@ class CommandTest(TestCase):
self.room2 = create.create_object(settings.BASE_ROOM_TYPECLASS, key="room2") self.room2 = create.create_object(settings.BASE_ROOM_TYPECLASS, key="room2")
# create a faux player/character for testing. # 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.player.user.is_superuser = True
self.char1.lock_storage = ""
self.char1.locks = LockHandler(self.char1)
self.char1.ndb.return_string = None self.char1.ndb.return_string = None
sess = FakeSession() sess = FakeSession()
sess.connectionMade() sess.connectionMade()
sess.session_login(self.char1.player) sess.session_login(self.char1.player)
# create second player and some objects # create second player
self.char2 = create.create_object(settings.BASE_CHARACTER_TYPECLASS, key="char2", location=self.room1) 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 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.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.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) 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 This also mangles the input in various ways to test if the command
will be fooled. will be fooled.
""" """
if not VERBOSE: if not VERBOSE and not NOMANGLE:
# only mangle if not VERBOSE, to make fewer return lines # only mangle if not VERBOSE, to make fewer return lines
test1 = re.sub(r'\s', '', raw_string) # remove all whitespace inside it test1 = re.sub(r'\s', '', raw_string) # remove all whitespace inside it
test2 = "%s/åäö öäö;-:$£@*~^' 'test" % raw_string # inserting weird characters in call test2 = "%s/åäö öäö;-:$£@*~^' 'test" % raw_string # inserting weird characters in call
@ -129,52 +147,129 @@ class CommandTest(TestCase):
# Default set Command testing # Default set Command testing
#------------------------------------------------------------ #------------------------------------------------------------
# general.py tests
class TestLook(CommandTest):
def test_call(self):
self.execute_cmd("look here")
class TestHome(CommandTest): class TestHome(CommandTest):
def test_call(self): def test_call(self):
self.char1.location = self.room1 self.char1.location = self.room1
self.char1.home = self.room2 self.char1.home = self.room2
self.execute_cmd("home") self.execute_cmd("home")
self.assertEqual(self.char1.location, self.room2) self.assertEqual(self.char1.location, self.room2)
class TestLook(CommandTest):
def test_call(self):
self.execute_cmd("look here")
class TestPassword(CommandTest): class TestPassword(CommandTest):
def test_call(self): def test_call(self):
self.execute_cmd("@password testpassword = newpassword") 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): class TestNick(CommandTest):
def test_call(self): def test_call(self):
self.char1.player.user.is_superuser = False
self.execute_cmd("nickname testalias = testaliasedstring1") self.execute_cmd("nickname testalias = testaliasedstring1")
self.execute_cmd("nickname/player testalias = testaliasedstring2") self.execute_cmd("nickname/player testalias = testaliasedstring2")
self.execute_cmd("nickname/object testalias = testaliasedstring3") self.execute_cmd("nickname/object testalias = testaliasedstring3")
self.assertEquals(u"testaliasedstring1", self.char1.nickhandler("testalias")) self.assertEquals(u"testaliasedstring1", self.char1.nicks.get("testalias"))
self.assertEquals(u"testaliasedstring2", self.char1.nickhandler("testalias",nick_type="player")) self.assertEquals(u"testaliasedstring2", self.char1.nicks.get("testalias",nick_type="player"))
self.assertEquals(u"testaliasedstring3", self.char1.nickhandler("testalias",nick_type="object")) 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 # system.py command tests
class TestPy(CommandTest): class TestPy(CommandTest):
def test_call(self): def test_call(self):
self.execute_cmd("@py 1+2", [">>> 1+2", "<<< 3"]) self.execute_cmd("@py 1+2", [">>> 1+2", "<<< 3"])
class TestListScripts(CommandTest): class TestScripts(CommandTest):
def test_call(self): def test_call(self):
self.execute_cmd("@scripts") self.execute_cmd("@scripts", "id")
class TestListObjects(CommandTest): class TestObjects(CommandTest):
def test_call(self): def test_call(self):
self.execute_cmd("@objects") self.execute_cmd("@objects", "Database totals")
class TestListService(CommandTest): # Cannot be tested since we don't have an active server running at this point.
def test_call(self): # class TestListService(CommandTest):
self.execute_cmd("@service") # def test_call(self):
# self.execute_cmd("@service/list", "---")
class TestVersion(CommandTest): class TestVersion(CommandTest):
def test_call(self): def test_call(self):
self.execute_cmd("@version") self.execute_cmd("@version", '---')
class TestTime(CommandTest): class TestTime(CommandTest):
def test_call(self): def test_call(self):
self.execute_cmd("@time") self.execute_cmd("@time", "Current server uptime")
class TestList(CommandTest): class TestServerLoad(CommandTest):
def test_call(self): def test_call(self):
self.execute_cmd("@list") self.execute_cmd("@serverload", "Server load")
class TestPs(CommandTest): class TestPs(CommandTest):
def test_call(self): def test_call(self):
self.execute_cmd("@ps","\n{wNon-timed scripts") self.execute_cmd("@ps","Non-timed scripts")
class TestStats(CommandTest):
# 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): 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" key = "connect"
aliases = ["conn", "con", "co"] aliases = ["conn", "con", "co"]
locks = "cmd:all()" # not really needed
def func(self): def func(self):
""" """
@ -116,6 +117,7 @@ class CmdCreate(MuxCommand):
""" """
key = "create" key = "create"
aliases = ["cre", "cr"] aliases = ["cre", "cr"]
locks = "cmd:all()"
def parse(self): def parse(self):
""" """
@ -193,6 +195,9 @@ class CmdCreate(MuxCommand):
location=default_home, location=default_home,
typeclass=typeclass, typeclass=typeclass,
home=default_home) home=default_home)
# character safety features
new_character.locks.delete("get")
new_character.locks.add("get:perm(Wizards)")
# set a default description # set a default description
new_character.db.desc = "This is a Player." new_character.db.desc = "This is a Player."
@ -227,6 +232,7 @@ class CmdQuit(MuxCommand):
""" """
key = "quit" key = "quit"
aliases = ["q", "qu"] aliases = ["q", "qu"]
locks = "cmd:all()"
def func(self): def func(self):
"Simply close the connection." "Simply close the connection."
@ -241,6 +247,7 @@ class CmdUnconnectedLook(MuxCommand):
""" """
key = "look" key = "look"
aliases = "l" aliases = "l"
locks = "cmd:all()"
def func(self): def func(self):
"Show the connect screen." "Show the connect screen."
@ -259,6 +266,7 @@ class CmdUnconnectedHelp(MuxCommand):
""" """
key = "help" key = "help"
aliases = ["h", "?"] aliases = ["h", "?"]
locks = "cmd:all()"
def func(self): def func(self):
"Shows help" "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.conf import settings
from django.db import IntegrityError from django.db import IntegrityError
from src.comms.models import Msg from src.comms.models import Msg
from src.permissions import permissions
from src.utils import create, debug, utils from src.utils import create, debug, utils
from src.commands.default.muxcommand import MuxCommand from src.commands.default.muxcommand import MuxCommand
@ -29,7 +28,7 @@ class CmdTest(MuxCommand):
key = "@test" key = "@test"
aliases = ["@te", "@test all"] aliases = ["@te", "@test all"]
help_category = "Utils" help_category = "Utils"
permissions = "cmd:Immortals" #Wizards locks = "cmd:perm(Wizards)"
# the muxcommand class itself handles the display # the muxcommand class itself handles the display
# so we just defer to it by not adding any function. # 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("Imported %s" % cmdsetname)
#self.caller.msg(cmdsethandler.CACHED_CMDSETS) #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): class TestCom(MuxCommand):
""" """
Test the command system Test the command system
@ -195,7 +69,7 @@ class TestCom(MuxCommand):
@testcom/create/list [channel] @testcom/create/list [channel]
""" """
key = "@testcom" key = "@testcom"
permissions = "cmd:Immortals Wizards" locks = "cmd:perm(Wizards)"
help_category = "Utils" help_category = "Utils"
def func(self): def func(self):
"Run the test program" "Run the test program"

View file

@ -25,14 +25,14 @@ does this for you.
""" """
from src.comms.models import Channel, Msg from src.comms.models import Channel, Msg
from src.commands import cmdset, command from src.commands import cmdset, command
from src.permissions.permissions import has_perm from src.utils import utils
class ChannelCommand(command.Command): class ChannelCommand(command.Command):
""" """
Channel Channel
Usage: 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 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 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 # this flag is what identifies this cmd as a channel cmd
# and branches off to the system send-to-channel command # and branches off to the system send-to-channel command
# (which is customizable by admin) # (which is customizable by admin)
is_channel = True
key = "general" key = "general"
help_category = "Channel Names" help_category = "Channel Names"
permissions = "cmd:use_channels" locks = "cmd:all()"
is_channel = True
obj = None obj = None
def parse(self): def parse(self):
""" """
Simple parser 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()) self.args = (channelname.strip(), msg.strip())
def func(self): def func(self):
@ -73,7 +73,7 @@ class ChannelCommand(command.Command):
string = "You are not connected to channel '%s'." string = "You are not connected to channel '%s'."
caller.msg(string % channelkey) caller.msg(string % channelkey)
return 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'." string = "You are not permitted to send to channel '%s'."
caller.msg(string % channelkey) caller.msg(string % channelkey)
return return
@ -102,6 +102,25 @@ class ChannelHandler(object):
""" """
self.cached_channel_cmds = [] 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): def add_channel(self, channel):
""" """
Add an individual channel to the handler. This should be Add an individual channel to the handler. This should be
@ -113,8 +132,12 @@ class ChannelHandler(object):
cmd = ChannelCommand() cmd = ChannelCommand()
cmd.key = channel.key.strip().lower() cmd.key = channel.key.strip().lower()
cmd.obj = channel cmd.obj = channel
cmd.__doc__= self._format_help(channel)
if channel.aliases: if channel.aliases:
cmd.aliases = channel.aliases cmd.aliases = channel.aliases
cmd.lock_storage = "cmd:all();%s" % channel.locks
cmd.lockhandler.reset()
self.cached_channel_cmds.append(cmd) self.cached_channel_cmds.append(cmd)
def update(self): def update(self):
@ -133,8 +156,9 @@ class ChannelHandler(object):
chan_cmdset.key = '_channelset' chan_cmdset.key = '_channelset'
chan_cmdset.priority = 10 chan_cmdset.priority = 10
chan_cmdset.duplicates = True 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) chan_cmdset.add(cmd)
return chan_cmdset return chan_cmdset

View file

@ -56,7 +56,7 @@ class MsgManager(models.Manager):
try: try:
idnum = int(idnum) idnum = int(idnum)
return self.get(id=id) return self.get(id=id)
except: except Exception:
return None return None
def get_messages_by_sender(self, player): def get_messages_by_sender(self, player):
@ -259,7 +259,10 @@ class ChannelManager(models.Manager):
pass pass
if not channels: if not channels:
# no id match. Search on the key. # 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 return channels
# #

View file

@ -16,10 +16,9 @@ be able to delete connections on the fly).
from django.db import models from django.db import models
from src.utils.idmapper.models import SharedMemoryModel from src.utils.idmapper.models import SharedMemoryModel
from src.players.models import PlayerDB
from src.server.sessionhandler import SESSIONS from src.server.sessionhandler import SESSIONS
from src.comms import managers 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 is_iter
from src.utils.utils import dbref as is_dbref from src.utils.utils import dbref as is_dbref
@ -81,7 +80,6 @@ class Msg(SharedMemoryModel):
permissions - perm strings permissions - perm strings
""" """
from src.players.models import PlayerDB
# #
# Msg database model setup # Msg database model setup
# #
@ -90,7 +88,7 @@ class Msg(SharedMemoryModel):
# named same as the field, but withtout the db_* prefix. # named same as the field, but withtout the db_* prefix.
# There must always be one sender of the message. # 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 # The destination objects of this message. Stored as a
# comma-separated string of object dbrefs. Can be defined along # comma-separated string of object dbrefs. Can be defined along
# with channels below. # with channels below.
@ -103,6 +101,8 @@ class Msg(SharedMemoryModel):
# should itself handle eventual headers etc. # should itself handle eventual headers etc.
db_message = models.TextField() db_message = models.TextField()
db_date_sent = models.DateTimeField(editable=False, auto_now_add=True) 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. # These are settable by senders/receivers/channels respectively.
# Stored as a comma-separated string of dbrefs. Can be used by the # 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 # 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_sender = models.BooleanField(default=False)
db_hide_from_receivers = models.CharField(max_length=255, null=True, blank=True) 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) db_hide_from_channels = models.CharField(max_length=255, null=True, blank=True)
# permission strings, separated by commas # Storage of lock strings
db_permissions = models.CharField(max_length=255, blank=True) db_lock_storage = models.TextField(null=True)
# Database manager # Database manager
objects = managers.MsgManager() objects = managers.MsgManager()
def __init__(self, *args, **kwargs):
SharedMemoryModel.__init__(self, *args, **kwargs)
self.locks = LockHandler(self)
class Meta: class Meta:
"Define Django meta options" "Define Django meta options"
verbose_name = "Message" verbose_name = "Message"
@ -278,29 +282,25 @@ class Msg(SharedMemoryModel):
self.save() self.save()
hide_from_channels = property(hide_from_channels_get, hide_from_channels_set, hide_from_channels_del) hide_from_channels = property(hide_from_channels_get, hide_from_channels_set, hide_from_channels_del)
# permissions property # lock_storage property (wraps db_lock_storage)
#@property #@property
def permissions_get(self): def lock_storage_get(self):
"Getter. Allows for value = self.permissions. Returns a list of permissions." "Getter. Allows for value = self.lock_storage"
if self.db_permissions: return self.db_lock_storage
return [perm.strip() for perm in self.db_permissions.split(',')] #@nick.setter
return [] def lock_storage_set(self, value):
#@permissions.setter """Saves the lock_storagetodate. This is usually not called directly, but through self.lock()"""
def permissions_set(self, value): self.db_lock_storage = 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() 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): def __str__(self):
@ -313,6 +313,15 @@ class Msg(SharedMemoryModel):
return "%s -> %s: %s" % (self.sender.key, return "%s -> %s: %s" % (self.sender.key,
", ".join([rec.key for rec in self.receivers]), ", ".join([rec.key for rec in self.receivers]),
self.message) 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) db_aliases = models.CharField(max_length=255)
# Whether this channel should remember its past messages # Whether this channel should remember its past messages
db_keep_log = models.BooleanField(default=True) db_keep_log = models.BooleanField(default=True)
# Permission strings, separated by commas # Storage of lock definitions
db_permissions = models.CharField(max_length=255, blank=True) db_lock_storage = models.TextField(blank=True)
# Database manager # Database manager
objects = managers.ChannelManager() 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 # Wrapper properties to easily set database fields. These are
# @property decorators that allows to access these fields using # @property decorators that allows to access these fields using
# normal python operations (without having to remember to save() # normal python operations (without having to remember to save()
@ -436,26 +449,21 @@ class Channel(SharedMemoryModel):
self.save() self.save()
keep_log = property(keep_log_get, keep_log_set, keep_log_del) keep_log = property(keep_log_get, keep_log_set, keep_log_del)
# permissions property # lock_storage property (wraps db_lock_storage)
#@property #@property
def permissions_get(self): def lock_storage_get(self):
"Getter. Allows for value = self.permissions. Returns a list of permissions." "Getter. Allows for value = self.lock_storage"
if self.db_permissions: return self.db_lock_storage
return [perm.strip() for perm in self.db_permissions.split(',')] #@nick.setter
return [] def lock_storage_set(self, value):
#@permissions.setter """Saves the lock_storagetodate. This is usually not called directly, but through self.lock()"""
def permissions_set(self, value): self.db_lock_storage = 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() 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: class Meta:
"Define Django meta options" "Define Django meta options"
@ -515,7 +523,7 @@ class Channel(SharedMemoryModel):
def connect_to(self, player): def connect_to(self, player):
"Connect the user to this channel" "Connect the user to this channel"
if not has_perm(player, self, 'chan_listen'): if not self.access(player, 'listen'):
return False return False
conn = ChannelConnection.objects.create_connection(player, self) conn = ChannelConnection.objects.create_connection(player, self)
if conn: if conn:
@ -531,7 +539,14 @@ class Channel(SharedMemoryModel):
for connection in Channel.objects.get_all_connections(self): for connection in Channel.objects.get_all_connections(self):
connection.delete() connection.delete()
super(Channel, self).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): class ChannelConnection(SharedMemoryModel):
""" """
@ -539,9 +554,8 @@ class ChannelConnection(SharedMemoryModel):
The advantage of making it like this is that one can easily The advantage of making it like this is that one can easily
break the connection just by deleting this object. break the connection just by deleting this object.
""" """
from src.players.models import PlayerDB
# Player connected to a channel # Player connected to a channel
db_player = models.ForeignKey(PlayerDB) db_player = models.ForeignKey("players.PlayerDB")
# Channel the player is connected to # Channel the player is connected to
db_channel = models.ForeignKey(Channel) 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.utils.idmapper.models import SharedMemoryModel
from src.help.manager import HelpEntryManager from src.help.manager import HelpEntryManager
from src.utils import ansi from src.utils import ansi
from src.locks.lockhandler import LockHandler
from src.utils.utils import is_iter from src.utils.utils import is_iter
#------------------------------------------------------------ #------------------------------------------------------------
@ -48,13 +49,18 @@ class HelpEntry(SharedMemoryModel):
db_entrytext = models.TextField(blank=True) db_entrytext = models.TextField(blank=True)
# a string of permissionstrings, separated by commas. # a string of permissionstrings, separated by commas.
db_permissions = models.CharField(max_length=255, blank=True) 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)). # (deprecated, only here to allow MUX helpfile load (don't use otherwise)).
# TODO: remove this when not needed anymore. # TODO: remove this when not needed anymore.
db_staff_only = models.BooleanField(default=False) db_staff_only = models.BooleanField(default=False)
# Database manager # Database manager
objects = HelpEntryManager() objects = HelpEntryManager()
def __init__(self, *args, **kwargs):
SharedMemoryModel.__init__(self, *args, **kwargs)
self.locks = LockHandler(self)
class Meta: class Meta:
"Define Django meta options" "Define Django meta options"
@ -138,6 +144,23 @@ class HelpEntry(SharedMemoryModel):
self.save() self.save()
permissions = property(permissions_get, permissions_set, permissions_del) 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 # HelpEntry main class methods
@ -149,3 +172,12 @@ class HelpEntry(SharedMemoryModel):
def __unicode__(self): def __unicode__(self):
return u'%s' % self.key 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. with Evennia's permissions system.
To call these locks, make sure this module is included in the To call these locks, make sure this module is included in the
settings tuple PERMISSION_FUNC_MODULES then define a permission settings tuple PERMISSION_FUNC_MODULES then define a lock on the form
string of the form 'myfunction(myargs)' and store it in the '<access_type>:func(args)' and add it to the object's lockhandler.
'permissions' field or variable on your object/command/channel/whatever. Run the check method of the handler to execute the lock check.
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.
Note that checking_obj and checked_obj can be any object type Note that accessing_obj and accessed_obj can be any object type
with a permissions variable/field, so be careful to not expect with a lock variable/field, so be careful to not expect
a certain object type. a certain object type.
MUX locks MUX locks
Below is a list nicked from the MUX docs on the locks available 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 from src.utils import search
PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
def noperm(checking_obj, checked_obj, *args): def true(*args, **kwargs):
""" "Always returns True."
Usage: return True
noperm(mypermstring) def all(*args, **kwargs):
noperm(perm1, perm2, perm3, ...) return True
def false(*args, **kwargs):
A negative permission; this will return False only if "Always returns False"
the checking object *has any* of the given permission(s), True return False
otherwise. The searched permission cannot itself be a def none(*args, **kwargs):
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
return False 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: Usage:
has_id(3) dbref(3)
This lock type checks if the checking object This lock type checks if the checking object
has a particular dbref. Note that this only has a particular dbref. Note that this only
@ -145,19 +167,29 @@ def has_id(checking_obj, checked_obj, *args):
if not args: if not args:
return False return False
try: try:
dbref = int(args[0].strip()) dbref = int(args[0].strip().strip('#'))
except ValueError: except ValueError:
return False return False
if hasattr(checking_obj, 'id'): if hasattr(accessing_obj, 'id'):
return dbref == checking_obj.id return dbref == accessing_obj.id
return False 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: Usage:
has_attr(attrname) has_attr(attrname)
has_attr(attrname, value) 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 Searches attributes *and* properties stored on the checking
object. The first form works like a flag - if the attribute/property object. The first form works like a flag - if the attribute/property
exists on the object, it returns True. The second form also requires 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 value = None
if len(args) > 1: if len(args) > 1:
value = args[1].strip() value = args[1].strip()
# first, look for normal properties on the object trying to gain access compare = 'eq'
if hasattr(checking_obj, attrname): 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: if value:
return str(getattr(checking_obj, attrname)) == value return valcompare(str(getattr(accessing_obj, attrname)), value, compare)
return True return True
# check attributes, if they exist # check attributes, if they exist
#print "lockfunc default: %s (%s)" % (checking_obj, attrname) if (hasattr(accessing_obj, 'has_attribute')
if hasattr(checking_obj, 'has_attribute') \ and accessing_obj.has_attribute(attrname)):
and checking_obj.has_attribute(attrname):
if value: if value:
return hasattr(checking_obj, 'attr') \ return (hasattr(accessing_obj, 'get_attribute')
and checking_obj.attr(attrname) == value and valcompare(accessing_obj.get_attribute(attrname), value, compare))
return True return True
return False 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.commands import cmdset, command
from src.permissions.permissions import has_perm
class ExitCommand(command.Command): class ExitCommand(command.Command):
"Simple identifier command" "Simple identifier command"
is_exit = True is_exit = True
locks = "cmd:all()" # should always be set to this.
destination = None destination = None
obj = None obj = None
def func(self): def func(self):
"Default exit traverse if no syscommand is defined." "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) self.caller.move_to(self.destination)
else: else:
self.caller.msg("You cannot enter.") 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.typeclasses.typeclass import TypeClass
from src.objects.manager import ObjectManager from src.objects.manager import ObjectManager
from src.config.models import ConfigValue from src.config.models import ConfigValue
from src.permissions.permissions import has_perm
from src.utils import logger from src.utils import logger
from src.utils.utils import is_iter from src.utils.utils import is_iter
@ -30,7 +30,7 @@ FULL_PERSISTENCE = settings.FULL_PERSISTENCE
try: try:
HANDLE_SEARCH_ERRORS = __import__( 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: except Exception:
from src.objects.object_search_funcs \ from src.objects.object_search_funcs \
import handle_search_errors as HANDLE_SEARCH_ERRORS import handle_search_errors as HANDLE_SEARCH_ERRORS
@ -70,6 +70,12 @@ class Alias(SharedMemoryModel):
"Define Django meta options" "Define Django meta options"
verbose_name = "Object alias" verbose_name = "Object alias"
verbose_name_plural = "Object aliases" 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" verbose_name_plural = "Nicknames"
unique_together = ("db_nick", "db_type", "db_obj") 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 # ObjectDB
@ -127,20 +174,24 @@ class ObjectDB(TypedObject):
typeclass - auto-linked typeclass typeclass - auto-linked typeclass
date_created - time stamp of object creation date_created - time stamp of object creation
permissions - perm strings permissions - perm strings
Dbref - #id of object locks - lock definitions (handler)
dbref - #id of object
db - persistent attribute storage db - persistent attribute storage
ndb - non-persistent attribute storage ndb - non-persistent attribute storage
The ObjectDB adds the following properties: The ObjectDB adds the following properties:
aliases - alternative names for object
player - optional connected player player - optional connected player
location - in-game location of object location - in-game location of object
home - safety location for object home - safety location for object (handler)
nicks - this objects nicknames for *other* objects
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) sessions - sessions connected to this object (see also player)
has_player - bool if an active player is currently connected has_player - bool if an active player is currently connected
contents - other objects having this object as location 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, # using their corresponding properties, named same as the field,
# but withtout the db_* prefix. # 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. # If this is a character object, the player is connected here.
db_player = models.ForeignKey("players.PlayerDB", blank=True, null=True) db_player = models.ForeignKey("players.PlayerDB", blank=True, null=True)
# The location in the game world. Since this one is likely # 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. # a safety location, this usually don't change much.
db_home = models.ForeignKey('self', related_name="homes_set", db_home = models.ForeignKey('self', related_name="homes_set",
blank=True, null=True) 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 # Database manager
objects = ObjectManager() 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 # Wrapper properties to easily set database fields. These are
# @property decorators that allows to access these fields using # @property decorators that allows to access these fields using
# normal python operations (without having to remember to save() # normal python operations (without having to remember to save()
@ -304,22 +357,29 @@ class ObjectDB(TypedObject):
self.db_home = None self.db_home = None
home = property(home_get, home_set, home_del) home = property(home_get, home_set, home_del)
# nicks property (wraps db_nicks) #@property for consistent aliases access throughout Evennia
#@property #@aliases.setter
def nicks_get(self): def aliases_set(self, aliases):
"Getter. Allows for value = self.aliases" "Adds an alias to object"
return list(Nick.objects.filter(db_obj=self)) if not is_iter(aliases):
#@nick.setter aliases = [aliases]
def nicks_set(self, nicks): for alias in aliases:
"""Setter is disabled. Use the nickhandler instead.""" query = Alias.objects.filter(db_obj=self, db_key__iexact=alias)
logger.log_errmsg("Nicks (%s) cannot be set through obj.nicks. Use obj.nickhandler instead." % nicks) if query.count():
#@nick.deleter continue
def nicks_del(self): new_alias = Alias(db_key=alias, db_obj=self)
"Deleter. Allows for del self.aliases" new_alias.save()
for nick in Nick.objects.filter(db_obj=self): #@aliases.getter
nick.delete() def aliases_get(self):
nicks = property(nicks_get, nicks_set, nicks_del) "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: class Meta:
"Define Django meta options" "Define Django meta options"
@ -378,55 +438,8 @@ class ObjectDB(TypedObject):
return [exi for exi in self.contents return [exi for exi in self.contents
if exi.has_attribute('_destination')] if exi.has_attribute('_destination')]
exits = property(exits_get) 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 # Main Search method
# #
@ -467,10 +480,10 @@ class ObjectDB(TypedObject):
if use_nicks: if use_nicks:
if ostring.startswith('*'): if ostring.startswith('*'):
# player nick replace # player nick replace
ostring = "*%s" % self.nickhandler(ostring.lstrip('*'), nick_type="player") ostring = "*%s" % self.nicks.get(ostring.lstrip('*'), nick_type="player")
else: else:
# object nick replace # 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, results = ObjectDB.objects.object_search(self, ostring,
global_search=global_search, global_search=global_search,
@ -485,25 +498,7 @@ class ObjectDB(TypedObject):
# #
# Execution/action methods # 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): def execute_cmd(self, raw_string):
""" """
Do something as this object. This command transparently 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): def emit_to(self, message, from_obj=None, data=None):
"Deprecated. Alias for msg" "Deprecated. Alias for msg"
logger.log_depmsg("emit_to() is deprecated. Use msg() instead.")
self.msg(message, from_obj, data) self.msg(message, from_obj, data)
def msg_contents(self, message, exclude=None, from_obj=None, data=None): 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): def emit_to_contents(self, message, exclude=None, from_obj=None, data=None):
"Deprecated. Alias for msg_contents" "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) self.msg_contents(message, exclude=exclude, from_obj=from_obj, data=data)
def move_to(self, destination, quiet=False, 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. 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): def handle_search_errors(emit_to_obj, ostring, results, global_search=False):
""" """
Takes a search result (a list) and 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 # check if the emit_to_object may se dbrefs
show_dbref = global_search and \ 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 = "More than one match for '%s'" % ostring
string += " (please narrow target):" string += " (please narrow target):"

View file

@ -49,10 +49,10 @@ class Object(TypeClass):
create_cmdset = True create_cmdset = True
try: try:
dummy = object.__getattribute__(dbobj, 'scripts') dummy = object.__getattribute__(dbobj, 'scripts')
create_scripts = type(dbobj.scripts) != ScriptHandler create_scripts = type(dbobj.scripts) != ScriptHandler
except AttributeError: except AttributeError:
create_scripts = True create_scripts = True
if create_cmdset: if create_cmdset:
dbobj.cmdset = CmdSetHandler(dbobj) dbobj.cmdset = CmdSetHandler(dbobj)
if utils.inherits_from(self, settings.BASE_CHARACTER_TYPECLASS): if utils.inherits_from(self, settings.BASE_CHARACTER_TYPECLASS):
@ -79,8 +79,18 @@ class Object(TypeClass):
""" """
Called once, when this object is first Called once, when this object is first
created. 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): def at_first_login(self):
""" """
@ -326,11 +336,16 @@ class Character(Object):
""" """
from settings import CMDSET_DEFAULT from settings import CMDSET_DEFAULT
self.cmdset.add_default(CMDSET_DEFAULT, permanent=True) 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): def at_after_move(self, source_location):
"Default is to look around after a move." "Default is to look around after a move."
self.execute_cmd('look') self.execute_cmd('look')
# #
# Base Room object # Base Room object
# #
@ -345,6 +360,7 @@ class Room(Object):
Simple setup, shown as an example Simple setup, shown as an example
(since default is None anyway) (since default is None anyway)
""" """
super(Room, self).at_object_creation()
self.location = None self.location = None
class Exit(Object): class Exit(Object):
@ -366,7 +382,13 @@ class Exit(Object):
definition (unless you want an entire class of exits definition (unless you want an entire class of exits
all leadning to the same hard-coded place ...) all leadning to the same hard-coded place ...)
""" """
# this is what makes it an exit
self.attr("_destination", "None") 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): def at_object_delete(self):
""" """
We have to make sure to clean the exithandler cache 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.objects import models, objects
from src.utils import create from src.utils import create
from src.commands.default import tests as commandtests from src.commands.default import tests as commandtests
from src.locks import tests as locktests
class TestObjAttrs(TestCase): class TestObjAttrs(TestCase):
""" """
@ -55,4 +56,5 @@ def suite():
tsuite = unittest.TestSuite() tsuite = unittest.TestSuite()
tsuite.addTest(unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__])) tsuite.addTest(unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__]))
tsuite.addTest(unittest.defaultTestLoader.loadTestsFromModule(commandtests)) tsuite.addTest(unittest.defaultTestLoader.loadTestsFromModule(commandtests))
tsuite.addTest(unittest.defaultTestLoader.loadTestsFromModule(locktests))
return tsuite 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.server.sessionhandler import SESSIONS
from src.players import manager from src.players import manager
from src.typeclasses.models import Attribute, TypedObject from src.typeclasses.models import Attribute, TypedObject
from src.permissions import permissions
from src.utils import logger from src.utils import logger
#------------------------------------------------------------ #------------------------------------------------------------
@ -105,7 +104,6 @@ class PlayerDB(TypedObject):
""" """
# #
# PlayerDB Database model setup # PlayerDB Database model setup
# #
@ -237,16 +235,6 @@ class PlayerDB(TypedObject):
return self.user.is_superuser return self.user.is_superuser
is_superuser = property(is_superuser_get) 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 # PlayerDB class access methods
# #

View file

@ -27,7 +27,13 @@ class Player(TypeClass):
""" """
# the text encoding to use. # the text encoding to use.
self.db.encoding = "utf-8" 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 # Note that the hooks below also exist
# in the character object's typeclass. You # in the character object's typeclass. You

View file

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

View file

@ -74,6 +74,8 @@ def create_objects():
user=god_user) user=god_user)
god_character.id = 1 god_character.id = 1
god_character.db.desc = 'This is User #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() god_character.save()
# Limbo is the initial starting room. # Limbo is the initial starting room.
@ -99,14 +101,14 @@ def create_channels():
print " Creating default channels ..." print " Creating default channels ..."
# public channel # public channel
key, aliases, desc, perms = settings.CHANNEL_PUBLIC key, aliases, desc, locks = settings.CHANNEL_PUBLIC
pchan = create.create_channel(key, aliases, desc, perms) pchan = create.create_channel(key, aliases, desc, locks=locks)
# mudinfo channel # mudinfo channel
key, aliases, desc, perms = settings.CHANNEL_MUDINFO key, aliases, desc, locks = settings.CHANNEL_MUDINFO
ichan = create.create_channel(key, aliases, desc, perms) ichan = create.create_channel(key, aliases, desc, locks=locks)
# connectinfo channel # connectinfo channel
key, aliases, desc, perms = settings.CHANNEL_CONNECTINFO key, aliases, desc, locks = settings.CHANNEL_CONNECTINFO
cchan = create.create_channel(key, aliases, desc, perms) cchan = create.create_channel(key, aliases, desc, locks=locks)
# connect the god user to all these channels by default. # connect the god user to all these channels by default.
goduser = get_god_user() goduser = get_god_user()
@ -126,33 +128,6 @@ def import_MUX_help_files():
print " Moving imported help db to help category '%s'." \ print " Moving imported help db to help category '%s'." \
% default_category % default_category
HelpEntry.objects.all_to_category(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(): def create_system_scripts():
""" """
@ -231,7 +206,6 @@ def handle_setup(last_step):
create_connect_screens, create_connect_screens,
create_objects, create_objects,
create_channels, create_channels,
create_permission_groups,
create_system_scripts, create_system_scripts,
import_MUX_help_files, import_MUX_help_files,
start_game_time, start_game_time,
@ -240,7 +214,7 @@ def handle_setup(last_step):
if not settings.IMPORT_MUX_HELP: if not settings.IMPORT_MUX_HELP:
# skip importing of the MUX helpfiles, they are # skip importing of the MUX helpfiles, they are
# not interesting except for developers. # not interesting except for developers.
del setup_queue[6] del setup_queue[5]
#print " Initial setup: %s steps." % (len(setup_queue)) #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."): def at_disconnect(self, reason="Connection closed. Goodbye for now."):
""" """
Disconnect from server Disconnect from server
""" """
char = self.get_character()
if char:
char.at_disconnect()
self.at_data_out(reason) self.at_data_out(reason)
self.connectionLost(step=2) self.connectionLost(step=2)

View file

@ -246,6 +246,9 @@ class WebClientSession(session.Session):
""" """
if reason: if reason:
self.lineSend(self.suid, reason) self.lineSend(self.suid, reason)
char = self.get_character()
if char:
char.at_disconnect()
self.client.disconnect(self.suid, step=2) self.client.disconnect(self.suid, step=2)
def at_data_out(self, string='', data=None): 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 access hiearchy, in climbing order. A higher
# The module where the base permissions and # permission in the hierarchy includes access of all
# groups are defined (read only once the very # levels below it.
# first time the server starts). If not set, PERMISSION_HIERARCHY = ("Players","PlayerHelpers","Builders", "Wizards", "Immortals")
# src/permissions/permissions_setup.py is used. # The default permission given to all new players
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.
PERMISSION_PLAYER_DEFAULT = "Players" PERMISSION_PLAYER_DEFAULT = "Players"
# Tuple of modules implementing permission lock methods # Tuple of modules implementing lock functions. All callable functions
# (see src/permissions/locks.py and # inside these modules will be available as lock functions.
# src/permissions/permissions.py) LOCK_FUNC_MODULES = ("src.locks.lockfuncs",)
PERMISSION_FUNC_MODULES = ("src.permissions.lockfunc_default",)
################################################### ###################################################
# In-game Channels created from server start # In-game Channels created from server start
################################################### ###################################################
# Defines a dict with one key for each from-start # Defines a dict with one key for each from-start
# channel. Each key points to a tuple containing # channel. Each key points to a tuple containing
# (name, aliases, description, permissions) # (name, aliases, description, locks)
# where aliases may be a tuple too, and permissions # where aliases may be a tuple too, and locks is
# is a comma-separated string of permissions # a valid lockstring definition.
# (see src/permissions/permissions.py)
# Default user channel for communication # Default user channel for communication
CHANNEL_PUBLIC = ("Public", 'ooc', 'Public discussion', CHANNEL_PUBLIC = ("Public", 'ooc', 'Public discussion',
'''chan_admin:has_id(1), "admin:perm(Wizards);listen:all();send:all()")
chan_listen:use_channels,
chan_send:use_channels''')
# General info about the server # General info about the server
CHANNEL_MUDINFO = ("MUDinfo", '', 'Informative messages', CHANNEL_MUDINFO = ("MUDinfo", '', 'Informative messages',
'''chan_admin:has_id(1), "admin:perm(Immortals);listen:perm(Immortals);send:false()")
chan_listen:Immortals,
chan_send:Immortals''')
# Channel showing when new people connecting # Channel showing when new people connecting
CHANNEL_CONNECTINFO = ("MUDconnections", ('connections, mud_conns'), CHANNEL_CONNECTINFO = ("MUDconnections", ('connections, mud_conns'),
'Connection log', 'Connection log',
'''chan_admin:has_id(1), "admin:perm(Immortals);listen:perm(Wizards);send:false()")
chan_listen:Wizards,
chan_send:Wizards''')
################################################### ###################################################
# IMC2 Configuration # IMC2 Configuration
@ -444,7 +430,6 @@ INSTALLED_APPS = (
'src.irc', 'src.irc',
'src.help', 'src.help',
'src.scripts', 'src.scripts',
'src.permissions',
'src.web.news', 'src.web.news',
'src.web.website',) 'src.web.website',)
# The user profile extends the User object with more functionality; # 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 django.utils.encoding import smart_str
from src.utils.idmapper.models import SharedMemoryModel from src.utils.idmapper.models import SharedMemoryModel
from src.typeclasses import managers from src.typeclasses import managers
from src.locks.lockhandler import LockHandler
from src.utils import logger from src.utils import logger
from src.utils.utils import is_iter, has_parent 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. # used by Attribute to efficiently identify stored object types.
# Note that these have to be updated if directory structure changes. # Note that these have to be updated if directory structure changes.
PARENTS = { PARENTS = {
@ -81,7 +84,6 @@ class Attribute(SharedMemoryModel):
""" """
# #
# Attribute Database Model setup # Attribute Database Model setup
# #
@ -94,8 +96,8 @@ class Attribute(SharedMemoryModel):
db_value = models.TextField(blank=True, null=True) db_value = models.TextField(blank=True, null=True)
# tells us what type of data is stored in the attribute # tells us what type of data is stored in the attribute
db_mode = models.CharField(max_length=20, null=True, blank=True) db_mode = models.CharField(max_length=20, null=True, blank=True)
# permissions to do things to this attribute # Lock storage
db_permissions = models.CharField(max_length=255, blank=True) db_lock_storage = models.TextField(blank=True)
# references the object the attribute is linked to (this is set # references the object the attribute is linked to (this is set
# by each child class to this abstact class) # by each child class to this abstact class)
db_obj = None # models.ForeignKey("RefencedObject") db_obj = None # models.ForeignKey("RefencedObject")
@ -105,6 +107,12 @@ class Attribute(SharedMemoryModel):
# Database manager # Database manager
objects = managers.AttributeManager() 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: class Meta:
"Define Django meta options" "Define Django meta options"
abstract = True abstract = True
@ -152,27 +160,6 @@ class Attribute(SharedMemoryModel):
self.save() self.save()
mode = property(mode_get, mode_set, mode_del) 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) # obj property (wraps db_obj)
#@property #@property
def obj_get(self): def obj_get(self):
@ -256,6 +243,23 @@ class Attribute(SharedMemoryModel):
self.delete() self.delete()
value = property(value_get, value_set, value_del) 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 # Attribute methods
@ -315,6 +319,15 @@ class Attribute(SharedMemoryModel):
#print "type identified: %s" % db_type[0] #print "type identified: %s" % db_type[0]
return str(in_value.id), 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) db_date_created = models.DateTimeField(editable=False, auto_now_add=True)
# Permissions (access these through the 'permissions' property) # Permissions (access these through the 'permissions' property)
db_permissions = models.CharField(max_length=512, blank=True) db_permissions = models.CharField(max_length=512, blank=True)
# Lock storage
db_lock_storage = models.TextField(blank=True)
# Database manager # Database manager
objects = managers.TypedObjectManager() 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: class Meta:
""" """
Django setup info. Django setup info.
@ -466,6 +487,24 @@ class TypedObject(SharedMemoryModel):
self.save() self.save()
permissions = property(permissions_get, permissions_set, permissions_del) 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 # TypedObject main class methods and properties
@ -502,7 +541,6 @@ class TypedObject(SharedMemoryModel):
# typeclass' __getattribute__, since that one would # typeclass' __getattribute__, since that one would
# try to look back to this very database object.) # try to look back to this very database object.)
typeclass = object.__getattribute__(self, 'typeclass') typeclass = object.__getattribute__(self, 'typeclass')
#typeclass = object.__getattribute__(self, 'typeclass')
#print " '%s' not on db --> Checking typeclass %s instead." % (propname, typeclass) #print " '%s' not on db --> Checking typeclass %s instead." % (propname, typeclass)
if typeclass: if typeclass:
return object.__getattribute__(typeclass(self), propname) 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')]) cmessage = "\n".join(["[NO MUDINFO CHANNEL]: %s" % line for line in message.split('\n')])
logger.log_errmsg(cmessage) logger.log_errmsg(cmessage)
path = self.db_typeclass_path #path = self.db_typeclass_path
path = object.__getattribute__(self, 'db_typeclass_path')
errstring = "" errstring = ""
if not path: if not path:
@ -967,3 +1006,38 @@ class TypedObject(SharedMemoryModel):
"Stop accidental deletion." "Stop accidental deletion."
raise Exception("Cannot delete the ndb object!") raise Exception("Cannot delete the ndb object!")
ndb = property(ndb_get, ndb_set, ndb_del) 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 Objects
Scripts Scripts
Help Help
PermissionGroup
Message Message
Channel Channel
Players Players
@ -22,9 +21,6 @@ Models covered:
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import IntegrityError 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 import logger
from src.utils.utils import is_iter, has_parent 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, 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 Create a new in-game object. Any game object is a combination
of a database object that stores data persistently to 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 new_object.player = player
player.obj = new_object 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 # call the hook method. This is where all at_creation
# customization happens as the typeclass stores custom # customization happens as the typeclass stores custom
# things on its database object. # things on its database object.
new_object.at_object_creation() 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. # perform a move_to in order to display eventual messages.
if home: if home:
new_object.home = home new_object.home = home
@ -121,7 +120,7 @@ def create_object(typeclass, key=None, location=None,
# Script creation # 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 Create a new script. All scripts are a combination
of a database object that communicates with the 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__ new_db_object.name = "%s" % typeclass.__name__
else: else:
new_db_object.name = "#%i" % new_db_object.id 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: if obj:
try: try:
new_script.obj = obj new_script.obj = obj
except ValueError: except ValueError:
new_script.obj = obj.dbobj 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() new_script.save()
@ -200,7 +205,7 @@ def create_script(typeclass, key=None, obj=None, autostart=True):
# Help entry creation # 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 Create a static help entry in the help database. Note that Command
help entries are dynamic and directly taken from the __doc__ entries 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.key = key
new_help.entrytext = entrytext new_help.entrytext = entrytext
new_help.help_category = category new_help.help_category = category
if permissions: if locks:
set_perm(new_help, permissions) new_help.locks.add(locks)
new_help.save() new_help.save()
return new_help return new_help
except IntegrityError: except IntegrityError:
@ -227,51 +232,13 @@ def create_help_entry(key, entrytext, category="General", permissions=None):
logger.log_trace() logger.log_trace()
return None 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 # Comm system methods
# #
def create_message(senderobj, message, channels=None, 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 Create a new communication message. Msgs are used for all
player-to-player communication, both between individual players 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. 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 receivers - a player to send to, or a list of them. May be Player objects
or playernames. 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 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 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 new_message.receivers = [to_player(receiver) for receiver in
[to_object(receiver) for receiver in receivers] [to_object(receiver) for receiver in receivers]
if receiver] if receiver]
if permissions: if locks:
set_perm(new_message, permissions) new_message.locks.add(locks)
new_message.save() new_message.save()
return new_message return new_message
def create_channel(key, aliases=None, desc=None, 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 Create A communication Channel. A Channel serves as a central
hub for distributing Msgs to groups of people without 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. key - this must be unique.
aliases - list of alternative (likely shorter) keynames. aliases - list of alternative (likely shorter) keynames.
listen/send/admin permissions are strings if permissions separated locks - lock string definitions
by commas.
""" """
from src.comms.models import Channel 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 string = "Could not add channel: key '%s' already exists." % key
logger.log_errmsg(string) logger.log_errmsg(string)
return None return None
if permissions: if locks:
set_perm(new_channel, permissions) new_channel.locks.add(locks)
new_channel.save() new_channel.save()
channelhandler.CHANNELHANDLER.add_channel(new_channel) channelhandler.CHANNELHANDLER.add_channel(new_channel)
return new_channel return new_channel
@ -375,7 +341,7 @@ def create_player(name, email, password,
permissions=None, permissions=None,
create_character=True, create_character=True,
location=None, typeclass=None, home=None, 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 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 = PlayerDB(db_key=name, user=new_user)
new_player.save() new_player.save()
# assign mud permissions # call hook method (may override default permissions)
if not permissions: new_player.at_player_creation()
permissions = settings.PERMISSION_PLAYER_DEFAULT
set_perm(new_player, permissions)
# 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 # create *in-game* 'player' object
if create_character: if create_character:
if not typeclass: if not typeclass:
@ -437,8 +410,8 @@ def create_player(name, email, password,
# creating the object automatically links the player # creating the object automatically links the player
# and object together by player.obj <-> obj.player # and object together by player.obj <-> obj.player
new_character = create_object(typeclass, name, new_character = create_object(typeclass, name,
location, home, location, home,
permissions=permissions,
player=new_player) player=new_player)
#set_perm(new_character, permissions)
return new_character return new_character
return new_player return new_player

View file

@ -67,3 +67,14 @@ def log_infomsg(infomsg):
infomsg = str(e) infomsg = str(e)
for line in infomsg.splitlines(): for line in infomsg.splitlines():
log.msg('[..] %s' % line) 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.utils.datastructures import SortedDict
from django.conf import settings from django.conf import settings
from src.scripts.models import ScriptDB 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.typeclasses import models as typeclassmodels
from src.objects import exithandler from src.objects import exithandler
from src.comms import channelhandler from src.comms import channelhandler
from src.comms.models import Channel from src.comms.models import Channel
from src.utils import reimport from src.utils import reimport, utils, logger
from src.utils import logger
def reload_modules(): def reload_modules():
""" """
@ -96,6 +100,21 @@ def reload_modules():
typeclassmodels.reset() typeclassmodels.reset()
exithandler.EXITHANDLER.clear() exithandler.EXITHANDLER.clear()
channelhandler.CHANNELHANDLER.update() 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, def reload_scripts(scripts=None, obj=None, key=None,
dbref=None, init_mode=False): 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.scripts.models import ScriptDB
from src.comms.models import Msg, Channel from src.comms.models import Msg, Channel
from src.help.models import HelpEntry from src.help.models import HelpEntry
from src.permissions.models import PermissionGroup
from src.config.models import ConfigValue from src.config.models import ConfigValue
# #
@ -136,20 +135,6 @@ channels = Channel.objects.channel_search
helpentries = HelpEntry.objects.search_help 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 # 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 They provide some useful string and conversion methods that might
be of use when designing your own game. be of use when designing your own game.
""" """
import os, sys import os, sys, imp
import textwrap import textwrap
import datetime import datetime
from twisted.internet import threads from twisted.internet import threads
@ -188,7 +188,7 @@ def get_evennia_version():
def pypath_to_realpath(python_path, file_ending='.py'): def pypath_to_realpath(python_path, file_ending='.py'):
""" """
Converts a path on dot python form (e.g. 'src.objects.models') to 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. absoulte paths starting from the evennia main directory.
""" """
pathsplit = python_path.strip().split('.') 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 # this can occur if we tried to store a class object, not an
# instance. Not sure if one should defend against this. # instance. Not sure if one should defend against this.
return False 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