Trunk: Merged the Devel-branch (branches/griatch) into /trunk. This constitutes a major refactoring of Evennia. Development will now continue in trunk. See the wiki and the past posts to the mailing list for info. /Griatch

This commit is contained in:
Griatch 2010-08-29 18:46:58 +00:00
parent df29defbcd
commit f83c2bddf8
222 changed files with 22304 additions and 14371 deletions

View file

18
src/permissions/admin.py Normal file
View file

@ -0,0 +1,18 @@
#
# 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

@ -0,0 +1,100 @@
"""
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',
'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',
'batchcommands',
'batchcodes']],
"Builders": [perm for perm in PERMS
if perm not in ['shutdown',
'py',
'reload',
'service',
'perm',
'batchcommands',
'batchcodes',
'wall',
'boot',
'delplayer',
'newpassword']],
"PlayerHelpers": ['tell',
'sethelp', 'ccreate', 'use_channels'],
"Players": ['tell', 'ccreate', 'use_channels']
}

View file

@ -0,0 +1,188 @@
"""
This module provides a set of permission lock functions for use
with Evennia's permissions system.
To call these locks, make sure this module is included in the
settings tuple PERMISSION_FUNC_MODULES then define a permission
string of the form 'myfunction(myargs)' and store it in the
'permissions' field or variable on your object/command/channel/whatever.
As part of the permission check, such permission strings will be
evaluated to call myfunction(checking_obj, checked_obj, *yourargs) in
this module. A boolean value is expected back.
Note that checking_obj and checked_obj can be any object type
with a permissions variable/field, so be careful to not expect
a certain object type.
MUX locks
Below is a list nicked from the MUX docs on the locks available
in MUX. These are not all necessarily relevant to an Evennia game
but to show they are all possible with Evennia, each entry is a
suggestion on how one could implement similar functionality in Evennia.
Name: Affects: Effect:
-------------------------------------------------------------------------
DefaultLock: Exits: controls who may traverse the exit to
its destination.
Evennia: specialized permission key
'traverse' checked in move method
Rooms: controls whether the player sees the SUCC
or FAIL message for the room following the
room description when looking at the room.
Evennia: This is better done by implementing
a clever room class ...
Players/Things: controls who may GET the object.
Evennia: specialized permission key 'get'
defined on object, checked by get command
EnterLock: Players/Things: controls who may ENTER the object
Evennia: specialized permission key 'enter'
defined on object, checked by move command
GetFromLock: All but Exits: controls who may gets things from a given
location.
Evennia: Probably done best with a lock function
that searches the database for permitted users
GiveLock: Players/Things: controls who may give the object.
Evennia: specialized permission key 'give'
checked by the give command
LeaveLock: Players/Things: controls who may LEAVE the object.
Evennia: specialized permission key 'leave'
checked by move command
LinkLock: All but Exits: controls who may link to the location if the
location is LINK_OK (for linking exits or
setting drop-tos) or ABODE (for setting
homes)
Evennia: specialized permission key 'link'
set on obj and checked by link command
MailLock: Players: controls who may @mail the player.
Evennia: Lock function that pulls the
config from the player to see if the
calling player is on the blacklist/whitelist
OpenLock: All but Exits: controls who may open an exit.
Evennia: specialized permission key 'open'
set on exit, checked by open command
PageLock: Players: controls who may page the player.
Evennia: see Maillock
ParentLock: All: controls who may make @parent links to the
object.
Evennia: This is handled with typeclasses
and typeclass switching instead.
ReceiveLock: Players/Things: controls who may give things to the object.
Evennia: See GiveLock
SpeechLock: All but Exits: controls who may speak in that location
Evennia: Lock function checking if there
is some special restrictions on the room
(game dependent)
TeloutLock: All but Exits: controls who may teleport out of the
location.
Evennia: See LeaveLock
TportLock: Rooms/Things: controls who may teleport there
Evennia: See EnterLock
UseLock: All but Exits: controls who may USE the object, GIVE the
object money and have the PAY attributes
run, have their messages heard and possibly
acted on by LISTEN and AxHEAR, and invoke
$-commands stored on the object.
Evennia: Implemented per game
DropLock: All but rooms: controls who may drop that object.
Evennia: specialized permission key 'drop'
set on room, checked by drop command.
VisibleLock: All: Controls object visibility when the object
is not dark and the looker passes the lock.
In DARK locations, the object must also be
set LIGHT and the viewer must pass the
VisibleLock.
Evennia: Better done with Scripts implementing
a dark state/cmdset. For a single object,
use a specialized permission key 'visible'
set on object and checked by look command.
"""
from src.permissions.permissions import get_types, has_perm, has_perm_string
from src.utils import search
def noperm(checking_obj, checked_obj, *args):
"""
Usage:
noperm(mypermstring)
noperm(perm1, perm2, perm3, ...)
A negative permission; this will return False only if
the checking object *has any* of the given permission(s), True
otherwise. The searched permission cannot itself be a
function-permission (i.e. you cannot wrap functions in
functions).
"""
if not args:
# this is an always-false permission
return False
return not has_perm_string(checking_obj, args)
def is_superuser(checking_obj, checked_obj, *args):
"""
Usage:
is_superuser()
Determines if the checking object is superuser.
"""
if hasattr(checking_obj, 'is_superuser'):
return checking_obj.is_superuser
return False
def has_id(checking_obj, checked_obj, *args):
"""
Usage:
has_id(3)
This lock type checks if the checking object
has a particular dbref. Note that this only
works for checking objects that are stored
in the database (e.g. not for commands)
"""
if not args:
return False
try:
dbref = int(args[0].strip())
except ValueError:
return False
if hasattr(checking_obj, 'id'):
return dbref == checking_obj.id
return False
def has_attr(checking_obj, checked_obj, *args):
"""
Usage:
has_attr(attrname)
has_attr(attrname, value)
Searches attributes *and* properties stored on the checking
object. The first form works like a flag - if the attribute/property
exists on the object, it returns True. The second form also requires
that the value of the attribute/property matches. Note that all
retrieved values will be converted to strings before doing the comparison.
"""
# deal with arguments
if not args:
return False
attrname = args[0].strip()
value = None
if len(args) > 1:
value = args[1].strip()
# first, look for normal properties on the object trying to gain access
if hasattr(checking_obj, attrname):
if value:
return str(getattr(checking_obj, attrname)) == value
return True
# check attributes, if they exist
#print "lockfunc default: %s (%s)" % (checking_obj, attrname)
if hasattr(checking_obj, 'has_attribute') \
and checking_obj.has_attribute(attrname):
if value:
return hasattr(checking_obj, 'attr') \
and checking_obj.attr(attrname) == value
return True
return False

View file

@ -0,0 +1,25 @@
"""
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

150
src/permissions/models.py Normal file
View file

@ -0,0 +1,150 @@
"""
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

@ -0,0 +1,644 @@
"""
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 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)