mirror of
https://github.com/evennia/evennia.git
synced 2026-03-24 16:56:32 +01:00
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:
parent
c2030c2c0c
commit
08b3de9e5e
49 changed files with 1714 additions and 1877 deletions
|
|
@ -13,7 +13,6 @@ Models covered:
|
|||
Objects
|
||||
Scripts
|
||||
Help
|
||||
PermissionGroup
|
||||
Message
|
||||
Channel
|
||||
Players
|
||||
|
|
@ -22,9 +21,6 @@ Models covered:
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import IntegrityError
|
||||
|
||||
from src.permissions.permissions import set_perm
|
||||
from src.permissions.models import PermissionGroup
|
||||
from src.utils import logger
|
||||
from src.utils.utils import is_iter, has_parent
|
||||
|
||||
|
|
@ -33,7 +29,7 @@ from src.utils.utils import is_iter, has_parent
|
|||
#
|
||||
|
||||
def create_object(typeclass, key=None, location=None,
|
||||
home=None, player=None, permissions=None, aliases=None):
|
||||
home=None, player=None, permissions=None, locks=None, aliases=None):
|
||||
"""
|
||||
Create a new in-game object. Any game object is a combination
|
||||
of a database object that stores data persistently to
|
||||
|
|
@ -94,18 +90,21 @@ def create_object(typeclass, key=None, location=None,
|
|||
new_object.player = player
|
||||
player.obj = new_object
|
||||
|
||||
if permissions:
|
||||
set_perm(new_object, permissions)
|
||||
if aliases:
|
||||
if not is_iter(aliases):
|
||||
aliases = [aliases]
|
||||
new_object.aliases = ",".join([alias.strip() for alias in aliases])
|
||||
|
||||
# call the hook method. This is where all at_creation
|
||||
# customization happens as the typeclass stores custom
|
||||
# things on its database object.
|
||||
new_object.at_object_creation()
|
||||
|
||||
# custom-given variables override the hook
|
||||
if permissions:
|
||||
new_object.permissions = permissions
|
||||
if aliases:
|
||||
if not is_iter(aliases):
|
||||
aliases = [aliases]
|
||||
new_object.aliases = ",".join([alias.strip() for alias in aliases])
|
||||
if locks:
|
||||
new_object.locks.add(locks)
|
||||
|
||||
# perform a move_to in order to display eventual messages.
|
||||
if home:
|
||||
new_object.home = home
|
||||
|
|
@ -121,7 +120,7 @@ def create_object(typeclass, key=None, location=None,
|
|||
# Script creation
|
||||
#
|
||||
|
||||
def create_script(typeclass, key=None, obj=None, autostart=True):
|
||||
def create_script(typeclass, key=None, obj=None, locks=None, autostart=True):
|
||||
"""
|
||||
Create a new script. All scripts are a combination
|
||||
of a database object that communicates with the
|
||||
|
|
@ -177,15 +176,21 @@ def create_script(typeclass, key=None, obj=None, autostart=True):
|
|||
new_db_object.name = "%s" % typeclass.__name__
|
||||
else:
|
||||
new_db_object.name = "#%i" % new_db_object.id
|
||||
|
||||
# call the hook method. This is where all at_creation
|
||||
# customization happens as the typeclass stores custom
|
||||
# things on its database object.
|
||||
new_script.at_script_creation()
|
||||
|
||||
# custom-given variables override the hook
|
||||
if locks:
|
||||
new_script.locks.add(locks)
|
||||
|
||||
if obj:
|
||||
try:
|
||||
new_script.obj = obj
|
||||
except ValueError:
|
||||
new_script.obj = obj.dbobj
|
||||
# call the hook method. This is where all at_creation
|
||||
# customization happens as the typeclass stores custom
|
||||
# things on its database object.
|
||||
new_script.at_script_creation()
|
||||
|
||||
new_script.save()
|
||||
|
||||
|
|
@ -200,7 +205,7 @@ def create_script(typeclass, key=None, obj=None, autostart=True):
|
|||
# Help entry creation
|
||||
#
|
||||
|
||||
def create_help_entry(key, entrytext, category="General", permissions=None):
|
||||
def create_help_entry(key, entrytext, category="General", locks=None):
|
||||
"""
|
||||
Create a static help entry in the help database. Note that Command
|
||||
help entries are dynamic and directly taken from the __doc__ entries
|
||||
|
|
@ -215,8 +220,8 @@ def create_help_entry(key, entrytext, category="General", permissions=None):
|
|||
new_help.key = key
|
||||
new_help.entrytext = entrytext
|
||||
new_help.help_category = category
|
||||
if permissions:
|
||||
set_perm(new_help, permissions)
|
||||
if locks:
|
||||
new_help.locks.add(locks)
|
||||
new_help.save()
|
||||
return new_help
|
||||
except IntegrityError:
|
||||
|
|
@ -227,51 +232,13 @@ def create_help_entry(key, entrytext, category="General", permissions=None):
|
|||
logger.log_trace()
|
||||
return None
|
||||
|
||||
#
|
||||
# Permission groups
|
||||
#
|
||||
|
||||
def create_permission_group(group_name, desc=None, group_perms=None,
|
||||
permissions=None):
|
||||
"""
|
||||
Adds a new group
|
||||
|
||||
group_name - case sensitive, unique key for group.
|
||||
desc - description of permission group
|
||||
group_perms - the permissions stored in this group - can be
|
||||
a list of permission strings, a single permission
|
||||
or a comma-separated string of permissions.
|
||||
permissions - can be a list of permission strings, a single
|
||||
permission or a comma-separated string of permissions.
|
||||
OBS-these are the group's OWN permissions, for editing
|
||||
the group etc - NOT the permissions stored in it!
|
||||
"""
|
||||
|
||||
new_group = PermissionGroup.objects.filter(db_key__exact=group_name)
|
||||
if new_group:
|
||||
new_group = new_group[0]
|
||||
else:
|
||||
new_group = PermissionGroup()
|
||||
new_group.key = group_name
|
||||
if desc:
|
||||
new_group.desc = desc
|
||||
if group_perms:
|
||||
if is_iter(group_perms):
|
||||
group_perms = ",".join([str(perm) for perm in group_perms])
|
||||
new_group.group_permissions = group_perms
|
||||
if permissions:
|
||||
set_perm(new_group, permissions)
|
||||
new_group.save()
|
||||
return new_group
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Comm system methods
|
||||
#
|
||||
|
||||
def create_message(senderobj, message, channels=None,
|
||||
receivers=None, permissions=None):
|
||||
receivers=None, locks=None):
|
||||
"""
|
||||
Create a new communication message. Msgs are used for all
|
||||
player-to-player communication, both between individual players
|
||||
|
|
@ -284,7 +251,7 @@ def create_message(senderobj, message, channels=None,
|
|||
may be actual channel objects or their unique key strings.
|
||||
receivers - a player to send to, or a list of them. May be Player objects
|
||||
or playernames.
|
||||
permissions - permission string, or a list of permission strings.
|
||||
locks - lock definition string
|
||||
|
||||
The Comm system is created very open-ended, so it's fully possible
|
||||
to let a message both go to several channels and to several receivers
|
||||
|
|
@ -325,13 +292,13 @@ def create_message(senderobj, message, channels=None,
|
|||
new_message.receivers = [to_player(receiver) for receiver in
|
||||
[to_object(receiver) for receiver in receivers]
|
||||
if receiver]
|
||||
if permissions:
|
||||
set_perm(new_message, permissions)
|
||||
if locks:
|
||||
new_message.locks.add(locks)
|
||||
new_message.save()
|
||||
return new_message
|
||||
|
||||
def create_channel(key, aliases=None, desc=None,
|
||||
permissions=None, keep_log=True):
|
||||
locks=None, keep_log=True):
|
||||
"""
|
||||
Create A communication Channel. A Channel serves as a central
|
||||
hub for distributing Msgs to groups of people without
|
||||
|
|
@ -342,8 +309,7 @@ def create_channel(key, aliases=None, desc=None,
|
|||
|
||||
key - this must be unique.
|
||||
aliases - list of alternative (likely shorter) keynames.
|
||||
listen/send/admin permissions are strings if permissions separated
|
||||
by commas.
|
||||
locks - lock string definitions
|
||||
"""
|
||||
|
||||
from src.comms.models import Channel
|
||||
|
|
@ -361,8 +327,8 @@ def create_channel(key, aliases=None, desc=None,
|
|||
string = "Could not add channel: key '%s' already exists." % key
|
||||
logger.log_errmsg(string)
|
||||
return None
|
||||
if permissions:
|
||||
set_perm(new_channel, permissions)
|
||||
if locks:
|
||||
new_channel.locks.add(locks)
|
||||
new_channel.save()
|
||||
channelhandler.CHANNELHANDLER.add_channel(new_channel)
|
||||
return new_channel
|
||||
|
|
@ -375,7 +341,7 @@ def create_player(name, email, password,
|
|||
permissions=None,
|
||||
create_character=True,
|
||||
location=None, typeclass=None, home=None,
|
||||
is_superuser=False, user=None):
|
||||
is_superuser=False, user=None, locks=None):
|
||||
|
||||
"""
|
||||
This creates a new player, handling the creation of the User
|
||||
|
|
@ -425,11 +391,18 @@ def create_player(name, email, password,
|
|||
new_player = PlayerDB(db_key=name, user=new_user)
|
||||
new_player.save()
|
||||
|
||||
# assign mud permissions
|
||||
if not permissions:
|
||||
permissions = settings.PERMISSION_PLAYER_DEFAULT
|
||||
set_perm(new_player, permissions)
|
||||
# call hook method (may override default permissions)
|
||||
new_player.at_player_creation()
|
||||
|
||||
# custom given arguments potentially overrides the hook
|
||||
if permissions:
|
||||
new_player.permissions = permissions
|
||||
elif not new_player.permissions:
|
||||
new_player.permissions = settings.PERMISSION_PLAYER_DEFAULT
|
||||
|
||||
if locks:
|
||||
new_player.locks.add(locks)
|
||||
|
||||
# create *in-game* 'player' object
|
||||
if create_character:
|
||||
if not typeclass:
|
||||
|
|
@ -437,8 +410,8 @@ def create_player(name, email, password,
|
|||
# creating the object automatically links the player
|
||||
# and object together by player.obj <-> obj.player
|
||||
new_character = create_object(typeclass, name,
|
||||
location, home,
|
||||
location, home,
|
||||
permissions=permissions,
|
||||
player=new_player)
|
||||
#set_perm(new_character, permissions)
|
||||
return new_character
|
||||
return new_player
|
||||
|
|
|
|||
|
|
@ -67,3 +67,14 @@ def log_infomsg(infomsg):
|
|||
infomsg = str(e)
|
||||
for line in infomsg.splitlines():
|
||||
log.msg('[..] %s' % line)
|
||||
|
||||
def log_depmsg(depmsg):
|
||||
"""
|
||||
Prints a deprecation message
|
||||
"""
|
||||
try:
|
||||
depmsg = utils.to_str(depmsg)
|
||||
except Exception, e:
|
||||
depmsg = str(e)
|
||||
for line in depmsg.splitlines():
|
||||
log.msg('[DP] %s' % line)
|
||||
|
|
|
|||
|
|
@ -10,12 +10,16 @@ from django.db.models.loading import AppCache
|
|||
from django.utils.datastructures import SortedDict
|
||||
from django.conf import settings
|
||||
from src.scripts.models import ScriptDB
|
||||
from src.objects.models import ObjectDB
|
||||
from src.players.models import PlayerDB
|
||||
from src.comms.models import Channel, Msg
|
||||
from src.help.models import HelpEntry
|
||||
|
||||
from src.typeclasses import models as typeclassmodels
|
||||
from src.objects import exithandler
|
||||
from src.comms import channelhandler
|
||||
from src.comms.models import Channel
|
||||
from src.utils import reimport
|
||||
from src.utils import logger
|
||||
from src.utils import reimport, utils, logger
|
||||
|
||||
def reload_modules():
|
||||
"""
|
||||
|
|
@ -96,6 +100,21 @@ def reload_modules():
|
|||
typeclassmodels.reset()
|
||||
exithandler.EXITHANDLER.clear()
|
||||
channelhandler.CHANNELHANDLER.update()
|
||||
|
||||
# run through all objects in database, forcing re-caching.
|
||||
|
||||
cemit_info(" Starting asynchronous object reset loop ...")
|
||||
def run_reset_loop():
|
||||
# run a reset loop on all objects
|
||||
[(o.cmdset.reset(), o.locks.reset()) for o in ObjectDB.objects.all()]
|
||||
[s.locks.reset() for s in ScriptDB.objects.all()]
|
||||
[p.locks.reset() for p in PlayerDB.objects.all()]
|
||||
[h.locks.reset() for h in HelpEntry.objects.all()]
|
||||
[m.locks.reset() for m in Msg.objects.all()]
|
||||
[c.locks.reset() for c in Channel.objects.all()]
|
||||
at_return = lambda r: cemit_info(" ... @reload: Asynchronous reset loop finished.")
|
||||
at_err = lambda e: cemit_info("%s\n@reload: Asynchronous reset loop exited with an error." % e)
|
||||
utils.run_async(run_reset_loop, at_return, at_err)
|
||||
|
||||
def reload_scripts(scripts=None, obj=None, key=None,
|
||||
dbref=None, init_mode=False):
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ from src.players.models import PlayerDB
|
|||
from src.scripts.models import ScriptDB
|
||||
from src.comms.models import Msg, Channel
|
||||
from src.help.models import HelpEntry
|
||||
from src.permissions.models import PermissionGroup
|
||||
from src.config.models import ConfigValue
|
||||
|
||||
#
|
||||
|
|
@ -136,20 +135,6 @@ channels = Channel.objects.channel_search
|
|||
|
||||
helpentries = HelpEntry.objects.search_help
|
||||
|
||||
#
|
||||
# Search for a permission group
|
||||
# Note that the name is case sensitive.
|
||||
#
|
||||
# def search_permissiongroup(self, ostring):
|
||||
# """
|
||||
# Find a permission group
|
||||
#
|
||||
# ostring = permission group name (case sensitive)
|
||||
# or database dbref
|
||||
# """
|
||||
|
||||
permgroups = PermissionGroup.objects.search_permgroup
|
||||
|
||||
#
|
||||
# Get a configuration value
|
||||
#
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ General helper functions that don't fit neatly under any given category.
|
|||
They provide some useful string and conversion methods that might
|
||||
be of use when designing your own game.
|
||||
"""
|
||||
import os, sys
|
||||
import os, sys, imp
|
||||
import textwrap
|
||||
import datetime
|
||||
from twisted.internet import threads
|
||||
|
|
@ -188,7 +188,7 @@ def get_evennia_version():
|
|||
def pypath_to_realpath(python_path, file_ending='.py'):
|
||||
"""
|
||||
Converts a path on dot python form (e.g. 'src.objects.models') to
|
||||
a system path (src/objects/models.py). Calculates all paths as
|
||||
a system path ($BASE_PATH/src/objects/models.py). Calculates all paths as
|
||||
absoulte paths starting from the evennia main directory.
|
||||
"""
|
||||
pathsplit = python_path.strip().split('.')
|
||||
|
|
@ -442,3 +442,48 @@ def has_parent(basepath, obj):
|
|||
# this can occur if we tried to store a class object, not an
|
||||
# instance. Not sure if one should defend against this.
|
||||
return False
|
||||
|
||||
def mod_import(mod_path):
|
||||
"""
|
||||
Takes filename of a module, converts it to a python path
|
||||
and imports it.
|
||||
"""
|
||||
|
||||
def log_trace(errmsg=None):
|
||||
"""
|
||||
Log a traceback to the log. This should be called
|
||||
from within an exception. errmsg is optional and
|
||||
adds an extra line with added info.
|
||||
"""
|
||||
from traceback import format_exc
|
||||
from twisted.python import log
|
||||
|
||||
tracestring = format_exc()
|
||||
if tracestring:
|
||||
for line in tracestring.splitlines():
|
||||
log.msg('[::] %s' % line)
|
||||
if errmsg:
|
||||
try:
|
||||
errmsg = to_str(errmsg)
|
||||
except Exception, e:
|
||||
errmsg = str(e)
|
||||
for line in errmsg.splitlines():
|
||||
log.msg('[EE] %s' % line)
|
||||
|
||||
if not os.path.isabs(mod_path):
|
||||
mod_path = os.path.abspath(mod_path)
|
||||
path, filename = mod_path.rsplit(os.path.sep, 1)
|
||||
modname = filename.rstrip('.py')
|
||||
|
||||
try:
|
||||
result = imp.find_module(modname, [path])
|
||||
except ImportError:
|
||||
log_trace("Could not find module '%s' (%s.py) at path '%s'" % (modname, modname, path))
|
||||
try:
|
||||
mod = imp.load_module(modname, *result)
|
||||
except ImportError:
|
||||
log_trace("Could not find or import module %s at path '%s'" % (modname, path))
|
||||
mod = None
|
||||
# we have to close the file handle manually
|
||||
result[0].close()
|
||||
return mod
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue