mirror of
https://github.com/evennia/evennia.git
synced 2026-03-26 17: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
|
|
@ -25,14 +25,14 @@ does this for you.
|
|||
"""
|
||||
from src.comms.models import Channel, Msg
|
||||
from src.commands import cmdset, command
|
||||
from src.permissions.permissions import has_perm
|
||||
from src.utils import utils
|
||||
|
||||
class ChannelCommand(command.Command):
|
||||
"""
|
||||
Channel
|
||||
|
||||
Usage:
|
||||
<channel name or alias> <message>
|
||||
<channel name or alias> <message>
|
||||
|
||||
This is a channel. If you have subscribed to it, you can send to
|
||||
it by entering its name or alias, followed by the text you want to
|
||||
|
|
@ -41,17 +41,17 @@ class ChannelCommand(command.Command):
|
|||
# this flag is what identifies this cmd as a channel cmd
|
||||
# and branches off to the system send-to-channel command
|
||||
# (which is customizable by admin)
|
||||
is_channel = True
|
||||
key = "general"
|
||||
help_category = "Channel Names"
|
||||
permissions = "cmd:use_channels"
|
||||
is_channel = True
|
||||
locks = "cmd:all()"
|
||||
obj = None
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
Simple parser
|
||||
"""
|
||||
channelname, msg = self.args.split(":", 1)
|
||||
channelname, msg = self.args.split(":", 1) # cmdhandler sends channame:msg here.
|
||||
self.args = (channelname.strip(), msg.strip())
|
||||
|
||||
def func(self):
|
||||
|
|
@ -73,7 +73,7 @@ class ChannelCommand(command.Command):
|
|||
string = "You are not connected to channel '%s'."
|
||||
caller.msg(string % channelkey)
|
||||
return
|
||||
if not has_perm(caller, channel, 'chan_send'):
|
||||
if not channel.access(caller, 'send'):
|
||||
string = "You are not permitted to send to channel '%s'."
|
||||
caller.msg(string % channelkey)
|
||||
return
|
||||
|
|
@ -102,6 +102,25 @@ class ChannelHandler(object):
|
|||
"""
|
||||
self.cached_channel_cmds = []
|
||||
|
||||
def _format_help(self, channel):
|
||||
"builds a doc string"
|
||||
key = channel.key
|
||||
aliases = channel.aliases
|
||||
if not utils.is_iter(aliases):
|
||||
aliases = [aliases]
|
||||
ustring = "%s <message>" % key.lower() + "".join(["\n %s <message>" % alias.lower() for alias in aliases])
|
||||
desc = channel.desc
|
||||
string = \
|
||||
"""
|
||||
Channel '%s'
|
||||
|
||||
Usage (not including your personal aliases):
|
||||
%s
|
||||
|
||||
%s
|
||||
""" % (key, ustring, desc)
|
||||
return string
|
||||
|
||||
def add_channel(self, channel):
|
||||
"""
|
||||
Add an individual channel to the handler. This should be
|
||||
|
|
@ -113,8 +132,12 @@ class ChannelHandler(object):
|
|||
cmd = ChannelCommand()
|
||||
cmd.key = channel.key.strip().lower()
|
||||
cmd.obj = channel
|
||||
cmd.__doc__= self._format_help(channel)
|
||||
if channel.aliases:
|
||||
cmd.aliases = channel.aliases
|
||||
cmd.lock_storage = "cmd:all();%s" % channel.locks
|
||||
cmd.lockhandler.reset()
|
||||
|
||||
self.cached_channel_cmds.append(cmd)
|
||||
|
||||
def update(self):
|
||||
|
|
@ -133,8 +156,9 @@ class ChannelHandler(object):
|
|||
chan_cmdset.key = '_channelset'
|
||||
chan_cmdset.priority = 10
|
||||
chan_cmdset.duplicates = True
|
||||
for cmd in [cmd for cmd in self.cached_channel_cmds
|
||||
if has_perm(source_object, cmd, 'chan_send')]:
|
||||
|
||||
for cmd in [cmd for cmd in self.cached_channel_cmds
|
||||
if cmd.access(source_object, 'listen')]:
|
||||
chan_cmdset.add(cmd)
|
||||
return chan_cmdset
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class MsgManager(models.Manager):
|
|||
try:
|
||||
idnum = int(idnum)
|
||||
return self.get(id=id)
|
||||
except:
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def get_messages_by_sender(self, player):
|
||||
|
|
@ -259,7 +259,10 @@ class ChannelManager(models.Manager):
|
|||
pass
|
||||
if not channels:
|
||||
# no id match. Search on the key.
|
||||
channels = self.filter(db_key=ostring)
|
||||
channels = self.filter(db_key__iexact=ostring)
|
||||
if not channels:
|
||||
# still no match. Search by alias.
|
||||
channels = [channel for channel in self.all() if ostring.lower in [a.lower for a in channel.aliases]]
|
||||
return channels
|
||||
|
||||
#
|
||||
|
|
|
|||
|
|
@ -16,10 +16,9 @@ be able to delete connections on the fly).
|
|||
|
||||
from django.db import models
|
||||
from src.utils.idmapper.models import SharedMemoryModel
|
||||
from src.players.models import PlayerDB
|
||||
from src.server.sessionhandler import SESSIONS
|
||||
from src.comms import managers
|
||||
from src.permissions.permissions import has_perm
|
||||
from src.locks.lockhandler import LockHandler
|
||||
from src.utils.utils import is_iter
|
||||
from src.utils.utils import dbref as is_dbref
|
||||
|
||||
|
|
@ -81,7 +80,6 @@ class Msg(SharedMemoryModel):
|
|||
permissions - perm strings
|
||||
|
||||
"""
|
||||
from src.players.models import PlayerDB
|
||||
#
|
||||
# Msg database model setup
|
||||
#
|
||||
|
|
@ -90,7 +88,7 @@ class Msg(SharedMemoryModel):
|
|||
# named same as the field, but withtout the db_* prefix.
|
||||
|
||||
# There must always be one sender of the message.
|
||||
db_sender = models.ForeignKey(PlayerDB, related_name='sender_set')
|
||||
db_sender = models.ForeignKey("players.PlayerDB", related_name='sender_set')
|
||||
# The destination objects of this message. Stored as a
|
||||
# comma-separated string of object dbrefs. Can be defined along
|
||||
# with channels below.
|
||||
|
|
@ -103,6 +101,8 @@ class Msg(SharedMemoryModel):
|
|||
# should itself handle eventual headers etc.
|
||||
db_message = models.TextField()
|
||||
db_date_sent = models.DateTimeField(editable=False, auto_now_add=True)
|
||||
# lock storage
|
||||
db_lock_storage = models.TextField(blank=True)
|
||||
# These are settable by senders/receivers/channels respectively.
|
||||
# Stored as a comma-separated string of dbrefs. Can be used by the
|
||||
# game to mask out messages from being visible in the archive (no
|
||||
|
|
@ -110,12 +110,16 @@ class Msg(SharedMemoryModel):
|
|||
db_hide_from_sender = models.BooleanField(default=False)
|
||||
db_hide_from_receivers = models.CharField(max_length=255, null=True, blank=True)
|
||||
db_hide_from_channels = models.CharField(max_length=255, null=True, blank=True)
|
||||
# permission strings, separated by commas
|
||||
db_permissions = models.CharField(max_length=255, blank=True)
|
||||
# Storage of lock strings
|
||||
db_lock_storage = models.TextField(null=True)
|
||||
|
||||
# Database manager
|
||||
objects = managers.MsgManager()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
SharedMemoryModel.__init__(self, *args, **kwargs)
|
||||
self.locks = LockHandler(self)
|
||||
|
||||
class Meta:
|
||||
"Define Django meta options"
|
||||
verbose_name = "Message"
|
||||
|
|
@ -278,29 +282,25 @@ class Msg(SharedMemoryModel):
|
|||
self.save()
|
||||
hide_from_channels = property(hide_from_channels_get, hide_from_channels_set, hide_from_channels_del)
|
||||
|
||||
# permissions property
|
||||
#@property
|
||||
def permissions_get(self):
|
||||
"Getter. Allows for value = self.permissions. Returns a list of permissions."
|
||||
if self.db_permissions:
|
||||
return [perm.strip() for perm in self.db_permissions.split(',')]
|
||||
return []
|
||||
#@permissions.setter
|
||||
def permissions_set(self, value):
|
||||
"Setter. Allows for self.permissions = value. Stores as a comma-separated string."
|
||||
if is_iter(value):
|
||||
value = ",".join([str(val).strip().lower() for val in value])
|
||||
self.db_permissions = value
|
||||
self.save()
|
||||
#@permissions.deleter
|
||||
def permissions_del(self):
|
||||
"Deleter. Allows for del self.permissions"
|
||||
self.db_permissions = ""
|
||||
# lock_storage property (wraps db_lock_storage)
|
||||
#@property
|
||||
def lock_storage_get(self):
|
||||
"Getter. Allows for value = self.lock_storage"
|
||||
return self.db_lock_storage
|
||||
#@nick.setter
|
||||
def lock_storage_set(self, value):
|
||||
"""Saves the lock_storagetodate. This is usually not called directly, but through self.lock()"""
|
||||
self.db_lock_storage = value
|
||||
self.save()
|
||||
permissions = property(permissions_get, permissions_set, permissions_del)
|
||||
#@nick.deleter
|
||||
def lock_storage_del(self):
|
||||
"Deleter is disabled. Use the lockhandler.delete (self.lock.delete) instead"""
|
||||
logger.log_errmsg("Lock_Storage (on %s) cannot be deleted. Use obj.lock.delete() instead." % self)
|
||||
lock_storage = property(lock_storage_get, lock_storage_set, lock_storage_del)
|
||||
|
||||
|
||||
#
|
||||
# Msg class method
|
||||
# Msg class methods
|
||||
#
|
||||
|
||||
def __str__(self):
|
||||
|
|
@ -313,6 +313,15 @@ class Msg(SharedMemoryModel):
|
|||
return "%s -> %s: %s" % (self.sender.key,
|
||||
", ".join([rec.key for rec in self.receivers]),
|
||||
self.message)
|
||||
def access(self, accessing_obj, access_type='read', default=False):
|
||||
"""
|
||||
Determines if another object has permission to access.
|
||||
accessing_obj - object trying to access this one
|
||||
access_type - type of access sought
|
||||
default - what to return if no lock of access_type was found
|
||||
"""
|
||||
return self.locks.check(accessing_obj, access_type=access_type, default=default)
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
|
|
@ -351,12 +360,16 @@ class Channel(SharedMemoryModel):
|
|||
db_aliases = models.CharField(max_length=255)
|
||||
# Whether this channel should remember its past messages
|
||||
db_keep_log = models.BooleanField(default=True)
|
||||
# Permission strings, separated by commas
|
||||
db_permissions = models.CharField(max_length=255, blank=True)
|
||||
# Storage of lock definitions
|
||||
db_lock_storage = models.TextField(blank=True)
|
||||
|
||||
# Database manager
|
||||
objects = managers.ChannelManager()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
SharedMemoryModel.__init__(self, *args, **kwargs)
|
||||
self.locks = LockHandler(self)
|
||||
|
||||
# Wrapper properties to easily set database fields. These are
|
||||
# @property decorators that allows to access these fields using
|
||||
# normal python operations (without having to remember to save()
|
||||
|
|
@ -436,26 +449,21 @@ class Channel(SharedMemoryModel):
|
|||
self.save()
|
||||
keep_log = property(keep_log_get, keep_log_set, keep_log_del)
|
||||
|
||||
# permissions property
|
||||
#@property
|
||||
def permissions_get(self):
|
||||
"Getter. Allows for value = self.permissions. Returns a list of permissions."
|
||||
if self.db_permissions:
|
||||
return [perm.strip() for perm in self.db_permissions.split(',')]
|
||||
return []
|
||||
#@permissions.setter
|
||||
def permissions_set(self, value):
|
||||
"Setter. Allows for self.permissions = value. Stores as a comma-separated string."
|
||||
if is_iter(value):
|
||||
value = ",".join([str(val).strip().lower() for val in value])
|
||||
self.db_permissions = value
|
||||
self.save()
|
||||
#@permissions.deleter
|
||||
def permissions_del(self):
|
||||
"Deleter. Allows for del self.permissions"
|
||||
self.db_permissions = ""
|
||||
# lock_storage property (wraps db_lock_storage)
|
||||
#@property
|
||||
def lock_storage_get(self):
|
||||
"Getter. Allows for value = self.lock_storage"
|
||||
return self.db_lock_storage
|
||||
#@nick.setter
|
||||
def lock_storage_set(self, value):
|
||||
"""Saves the lock_storagetodate. This is usually not called directly, but through self.lock()"""
|
||||
self.db_lock_storage = value
|
||||
self.save()
|
||||
permissions = property(permissions_get, permissions_set, permissions_del)
|
||||
#@nick.deleter
|
||||
def lock_storage_del(self):
|
||||
"Deleter is disabled. Use the lockhandler.delete (self.lock.delete) instead"""
|
||||
logger.log_errmsg("Lock_Storage (on %s) cannot be deleted. Use obj.lock.delete() instead." % self)
|
||||
lock_storage = property(lock_storage_get, lock_storage_set, lock_storage_del)
|
||||
|
||||
class Meta:
|
||||
"Define Django meta options"
|
||||
|
|
@ -515,7 +523,7 @@ class Channel(SharedMemoryModel):
|
|||
|
||||
def connect_to(self, player):
|
||||
"Connect the user to this channel"
|
||||
if not has_perm(player, self, 'chan_listen'):
|
||||
if not self.access(player, 'listen'):
|
||||
return False
|
||||
conn = ChannelConnection.objects.create_connection(player, self)
|
||||
if conn:
|
||||
|
|
@ -531,7 +539,14 @@ class Channel(SharedMemoryModel):
|
|||
for connection in Channel.objects.get_all_connections(self):
|
||||
connection.delete()
|
||||
super(Channel, self).delete()
|
||||
|
||||
def access(self, accessing_obj, access_type='listen', default=False):
|
||||
"""
|
||||
Determines if another object has permission to access.
|
||||
accessing_obj - object trying to access this one
|
||||
access_type - type of access sought
|
||||
default - what to return if no lock of access_type was found
|
||||
"""
|
||||
return self.locks.check(accessing_obj, access_type=access_type, default=default)
|
||||
|
||||
class ChannelConnection(SharedMemoryModel):
|
||||
"""
|
||||
|
|
@ -539,9 +554,8 @@ class ChannelConnection(SharedMemoryModel):
|
|||
The advantage of making it like this is that one can easily
|
||||
break the connection just by deleting this object.
|
||||
"""
|
||||
from src.players.models import PlayerDB
|
||||
# Player connected to a channel
|
||||
db_player = models.ForeignKey(PlayerDB)
|
||||
db_player = models.ForeignKey("players.PlayerDB")
|
||||
# Channel the player is connected to
|
||||
db_channel = models.ForeignKey(Channel)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue