Add channel sub-permission checks for admin/manage switches
This commit is contained in:
parent
d118fb17b8
commit
adf484b9df
9 changed files with 186 additions and 642 deletions
|
|
@ -339,7 +339,6 @@ def _init():
|
|||
CMD_NOINPUT - no input was given on command line
|
||||
CMD_NOMATCH - no valid command key was found
|
||||
CMD_MULTIMATCH - multiple command matches were found
|
||||
CMD_CHANNEL - the command name is a channel name
|
||||
CMD_LOGINSTART - this command will be called as the very
|
||||
first command when an account connects to
|
||||
the server.
|
||||
|
|
@ -354,7 +353,6 @@ def _init():
|
|||
CMD_NOINPUT = cmdhandler.CMD_NOINPUT
|
||||
CMD_NOMATCH = cmdhandler.CMD_NOMATCH
|
||||
CMD_MULTIMATCH = cmdhandler.CMD_MULTIMATCH
|
||||
CMD_CHANNEL = cmdhandler.CMD_CHANNEL
|
||||
CMD_LOGINSTART = cmdhandler.CMD_LOGINSTART
|
||||
del cmdhandler
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,6 @@ command line. The processing of a command works as follows:
|
|||
|
||||
1. The calling object (caller) is analyzed based on its callertype.
|
||||
2. Cmdsets are gathered from different sources:
|
||||
- channels: all available channel names are auto-created into a cmdset, to allow
|
||||
for giving the channel name and have the following immediately
|
||||
sent to the channel. The sending is performed by the CMD_CHANNEL
|
||||
system command.
|
||||
- object cmdsets: all objects at caller's location are scanned for non-empty
|
||||
cmdsets. This includes cmdsets on exits.
|
||||
- caller: the caller is searched for its own currently active cmdset.
|
||||
|
|
@ -72,8 +68,6 @@ CMD_NOINPUT = "__noinput_command"
|
|||
CMD_NOMATCH = "__nomatch_command"
|
||||
# command to call if multiple command matches were found
|
||||
CMD_MULTIMATCH = "__multimatch_command"
|
||||
# command to call if found command is the name of a channel
|
||||
CMD_CHANNEL = "__send_to_channel_command"
|
||||
# command to call as the very first one when the user connects.
|
||||
# (is expected to display the login screen)
|
||||
CMD_LOGINSTART = "__unloggedin_look_command"
|
||||
|
|
@ -757,18 +751,6 @@ def cmdhandler(
|
|||
sysarg += _(' Type "help" for help.')
|
||||
raise ExecSystemCommand(syscmd, sysarg)
|
||||
|
||||
# Check if this is a Channel-cmd match.
|
||||
if hasattr(cmd, "is_channel") and cmd.is_channel:
|
||||
# even if a user-defined syscmd is not defined, the
|
||||
# found cmd is already a system command in its own right.
|
||||
syscmd = yield cmdset.get(CMD_CHANNEL)
|
||||
if syscmd:
|
||||
# replace system command with custom version
|
||||
cmd = syscmd
|
||||
cmd.session = session
|
||||
sysarg = "%s:%s" % (cmdname, args)
|
||||
raise ExecSystemCommand(cmd, sysarg)
|
||||
|
||||
# A normal command.
|
||||
ret = yield _run_command(cmd, cmdname, args, raw_cmdname, cmdset, session, account)
|
||||
returnValue(ret)
|
||||
|
|
|
|||
|
|
@ -170,11 +170,34 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
Creates a new channel (or destroys one you control).
|
||||
|
||||
## lock and unlock
|
||||
|
||||
Usage: channel/lock channelname = lockstring
|
||||
channel/unlock channelname = lockstring
|
||||
|
||||
Note: this is an admin command.
|
||||
|
||||
A lockstring is on the form locktype:lockfunc(). Channels understand three
|
||||
locktypes:
|
||||
listen - who may listen or join the channel.
|
||||
send - who may send messages to the channel
|
||||
control - who controls the channel. This is usually the one creating
|
||||
the channel.
|
||||
|
||||
Common lockfuncs are all() and perm(). To make a channel everyone can listen
|
||||
to but only builders can talk on, use this:
|
||||
|
||||
listen:all()
|
||||
send: perm(Builders)
|
||||
|
||||
"""
|
||||
key = "channel"
|
||||
aliases = ["chan", "channels"]
|
||||
locks = "cmd: not pperm(channel_banned)"
|
||||
help_category = "Comms"
|
||||
# these cmd: lock controls access to the channel command itself
|
||||
# the admin: lock controls access to /boot/ban/unban switches
|
||||
# the manage: lock controls access to /create/destroy/desc/lock/unlock switches
|
||||
locks = "cmd:not pperm(channel_banned);admin:all();manage:all();changelocks:perm(Admin)"
|
||||
switch_options = (
|
||||
"list", "all", "history", "sub", "unsub", "mute", "unmute", "alias", "unalias",
|
||||
"create", "destroy", "desc", "lock", "unlock", "boot", "ban", "unban", "who",)
|
||||
|
|
@ -186,6 +209,12 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
channel_msg_nick_alias = r"{alias}\s*?|{alias}\s+?(?P<arg1>.+?)"
|
||||
channel_msg_nick_replacement = "channel {channelname} = $1"
|
||||
|
||||
# to make it easier to override help functionality, we add the ability to
|
||||
# tweak access to different sub-functionality. Note that the system will
|
||||
# still check control lock etc even if you can use this functionality.
|
||||
# changing these does not change access to this command itself (that's the
|
||||
# locks property)
|
||||
|
||||
def search_channel(self, channelname, exact=False, handle_errors=True):
|
||||
"""
|
||||
Helper function for searching for a single channel with some error
|
||||
|
|
@ -721,20 +750,26 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
"""
|
||||
comtable = self.styled_table(
|
||||
"|wchannel|n",
|
||||
"|wmy aliases|n",
|
||||
"|wdescription|n",
|
||||
"channel",
|
||||
"my aliases",
|
||||
"locks",
|
||||
"description",
|
||||
align="l",
|
||||
maxwidth=_DEFAULT_WIDTH
|
||||
)
|
||||
|
||||
for chan in subscribed:
|
||||
|
||||
locks = "-"
|
||||
if chan.access(self.caller, "control"):
|
||||
locks = chan.locks
|
||||
|
||||
my_aliases = ", ".join(self.get_channel_aliases(chan))
|
||||
comtable.add_row(
|
||||
*("{}{}".format(
|
||||
chan.key,
|
||||
"({})".format(",".join(chan.aliases.all())) if chan.aliases.all() else ""),
|
||||
my_aliases,
|
||||
locks,
|
||||
chan.db.desc))
|
||||
return comtable
|
||||
|
||||
|
|
@ -744,7 +779,6 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
Args:
|
||||
subscribed (list): List of subscribed channels
|
||||
|
||||
Returns:
|
||||
EvTable: Table to display.
|
||||
|
||||
|
|
@ -752,33 +786,30 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
caller = self.caller
|
||||
|
||||
comtable = self.styled_table(
|
||||
"|wsub|n",
|
||||
"|wchannel|n",
|
||||
"|wmy aliases|n",
|
||||
"|wlocks|n",
|
||||
"|wdescription|n",
|
||||
"sub",
|
||||
"channel",
|
||||
"aliases",
|
||||
"my aliases",
|
||||
"description",
|
||||
maxwidth=_DEFAULT_WIDTH,
|
||||
)
|
||||
channels = subscribed + available
|
||||
|
||||
for chan in channels:
|
||||
my_aliases = ", ".join(self.get_channel_aliases(chan))
|
||||
if chan not in subscribed:
|
||||
substatus = "|rNo|n"
|
||||
elif caller in chan.mutelist:
|
||||
substatus = "|rMuting|n"
|
||||
else:
|
||||
substatus = "|gYes|n"
|
||||
my_aliases = ", ".join(self.get_channel_aliases(chan))
|
||||
comtable.add_row(
|
||||
*(substatus,
|
||||
"{}{}".format(
|
||||
chan.key,
|
||||
"({})".format(",".join(chan.aliases.all())) if chan.aliases.all() else ""),
|
||||
chan.key,
|
||||
",".join(chan.aliases.all()) if chan.aliases.all() else "",
|
||||
my_aliases,
|
||||
str(chan.locks),
|
||||
chan.db.desc))
|
||||
comtable.reformat_column(0, width=9)
|
||||
comtable.reformat_column(3, width=14)
|
||||
comtable.reformat_column(0, width=8)
|
||||
|
||||
return comtable
|
||||
|
||||
|
|
@ -819,6 +850,11 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
if 'create' in switches:
|
||||
# create a new channel
|
||||
|
||||
if not self.access(caller, "manage"):
|
||||
self.msg("You don't have access to use channel/create.")
|
||||
return
|
||||
|
||||
config = self.lhs
|
||||
if not config:
|
||||
self.msg("To create: channel/create name[;aliases][:typeclass] [= description]")
|
||||
|
|
@ -836,7 +872,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
if 'unalias' in switches:
|
||||
# remove a personal alias (no channel needed)
|
||||
alias = self.rhs
|
||||
alias = self.args.strip()
|
||||
if not alias:
|
||||
self.msg("Specify the alias to remove as channel/unalias <alias>")
|
||||
return
|
||||
|
|
@ -976,12 +1012,17 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
if 'destroy' in switches or 'delete' in switches:
|
||||
# destroy a channel we control
|
||||
reason = self.rhs or None
|
||||
|
||||
if not self.access(caller, "manage"):
|
||||
self.msg("You don't have access to use channel/destroy.")
|
||||
return
|
||||
|
||||
if not channel.access(caller, "control"):
|
||||
self.msg("You can only delete channels you control.")
|
||||
return
|
||||
|
||||
reason = self.rhs or None
|
||||
|
||||
def _perform_delete(caller, *args, **kwargs):
|
||||
self.destroy_channel(channel, message=reason)
|
||||
self.msg(f"Channel {channel.key} was successfully deleted.")
|
||||
|
|
@ -997,12 +1038,17 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
if 'desc' in switches:
|
||||
# set channel description
|
||||
desc = self.rhs.strip()
|
||||
|
||||
if not self.access(caller, "manage"):
|
||||
self.msg("You don't have access to use channel/desc.")
|
||||
return
|
||||
|
||||
if not channel.access(caller, "control"):
|
||||
self.msg("You can only change description of channels you control.")
|
||||
return
|
||||
|
||||
desc = self.rhs.strip()
|
||||
|
||||
if not desc:
|
||||
self.msg("Usage: /desc channel = description")
|
||||
return
|
||||
|
|
@ -1012,12 +1058,17 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
if 'lock' in switches:
|
||||
# add a lockstring to channel
|
||||
lockstring = self.rhs.strip()
|
||||
|
||||
if not self.access(caller, "changelocks"):
|
||||
self.msg("You don't have access to use channel/lock.")
|
||||
return
|
||||
|
||||
if not channel.access(caller, "control"):
|
||||
self.msg("You need 'control'-access to change locks on this channel.")
|
||||
return
|
||||
|
||||
lockstring = self.rhs.strip()
|
||||
|
||||
if not lockstring:
|
||||
self.msg("Usage: channel/lock channelname = lockstring")
|
||||
return
|
||||
|
|
@ -1031,16 +1082,21 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
if 'unlock' in switches:
|
||||
# remove/update lockstring from channel
|
||||
lockstring = self.rhs.strip()
|
||||
|
||||
if not lockstring:
|
||||
self.msg("Usage: channel/unlock channelname = lockstring")
|
||||
if not self.access(caller, "changelocks"):
|
||||
self.msg("You don't have access to use channel/unlock.")
|
||||
return
|
||||
|
||||
if not channel.access(caller, "control"):
|
||||
self.msg("You need 'control'-access to change locks on this channel.")
|
||||
return
|
||||
|
||||
lockstring = self.rhs.strip()
|
||||
|
||||
if not lockstring:
|
||||
self.msg("Usage: channel/unlock channelname = lockstring")
|
||||
return
|
||||
|
||||
success, err = self.unset_lock(channel, self.rhs)
|
||||
if success:
|
||||
self.msg("Removed lock from channel.")
|
||||
|
|
@ -1051,6 +1107,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
if 'boot' in switches:
|
||||
# boot a user from channel(s)
|
||||
|
||||
if not self.access(caller, "admin"):
|
||||
self.msg("You don't have access to use channel/boot.")
|
||||
return
|
||||
|
||||
if not self.rhs:
|
||||
self.msg("Usage: channel/boot channel[,channel,...] = username [:reason]")
|
||||
return
|
||||
|
|
@ -1095,6 +1155,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
if 'ban' in switches:
|
||||
# ban a user from channel(s)
|
||||
|
||||
if not self.access(caller, "admin"):
|
||||
self.msg("You don't have access to use channel/ban.")
|
||||
return
|
||||
|
||||
if not self.rhs:
|
||||
# view bans for channels
|
||||
|
||||
|
|
@ -1104,7 +1168,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
bans = ["Channel bans "
|
||||
"(to ban, use channel/ban channel[,channel,...] = username [:reason]"]
|
||||
bans.expand(self.channel_list_bans(channel))
|
||||
bans.extend(self.channel_list_bans(channel))
|
||||
self.msg("\n".join(bans))
|
||||
return
|
||||
|
||||
|
|
@ -1146,6 +1210,11 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
if 'unban' in switches:
|
||||
# unban a previously banned user from channel
|
||||
|
||||
if not self.access(caller, "admin"):
|
||||
self.msg("You don't have access to use channel/unban.")
|
||||
return
|
||||
|
||||
target_str = self.rhs.strip()
|
||||
|
||||
if not target_str:
|
||||
|
|
@ -1596,7 +1665,7 @@ class CmdClock(CmdChannel):
|
|||
|
||||
key = "clock"
|
||||
aliases = ["clock"]
|
||||
locks = "cmd:not pperm(channel_banned)"
|
||||
locks = "cmd:not pperm(channel_banned) and perm(Admin)"
|
||||
help_category = "Comms"
|
||||
|
||||
# this is used by the COMMAND_DEFAULT_CLASS parent
|
||||
|
|
@ -1705,7 +1774,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
|
|||
caller = self.caller
|
||||
|
||||
# get the messages we've sent (not to channels)
|
||||
pages_we_sent = Msg.objects.get_messages_by_sender(caller, exclude_channel_messages=True)
|
||||
pages_we_sent = Msg.objects.get_messages_by_sender(caller)
|
||||
# get last messages we've got
|
||||
pages_we_got = Msg.objects.get_messages_by_receiver(caller)
|
||||
targets, message, number = [], None, None
|
||||
|
|
@ -1745,7 +1814,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
|
|||
# a single-word message
|
||||
message = message[0].strip()
|
||||
|
||||
pages = pages_we_sent + pages_we_got
|
||||
pages = list(pages_we_sent) + list(pages_we_got)
|
||||
pages = sorted(pages, key=lambda page: page.date_created)
|
||||
|
||||
if message:
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ from evennia.utils.utils import at_search_result
|
|||
from evennia.commands.cmdhandler import CMD_NOINPUT
|
||||
from evennia.commands.cmdhandler import CMD_NOMATCH
|
||||
from evennia.commands.cmdhandler import CMD_MULTIMATCH
|
||||
from evennia.commands.cmdhandler import CMD_CHANNEL
|
||||
from evennia.utils import utils
|
||||
|
||||
from django.conf import settings
|
||||
|
|
@ -104,50 +103,3 @@ class SystemMultimatch(COMMAND_DEFAULT_CLASS):
|
|||
matches = self.matches
|
||||
# at_search_result will itself msg the multimatch options to the caller.
|
||||
at_search_result([match[2] for match in matches], self.caller, query=matches[0][0])
|
||||
|
||||
|
||||
# Command called when the command given at the command line
|
||||
# was identified as a channel name, like there existing a
|
||||
# channel named 'ooc' and the user wrote
|
||||
# > ooc Hello!
|
||||
|
||||
|
||||
class SystemSendToChannel(COMMAND_DEFAULT_CLASS):
|
||||
"""
|
||||
This is a special command that the cmdhandler calls
|
||||
when it detects that the command given matches
|
||||
an existing Channel object key (or alias).
|
||||
"""
|
||||
|
||||
key = CMD_CHANNEL
|
||||
locks = "cmd:all()"
|
||||
|
||||
def parse(self):
|
||||
channelname, msg = self.args.split(":", 1)
|
||||
self.args = channelname.strip(), msg.strip()
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
Create a new message and send it to channel, using
|
||||
the already formatted input.
|
||||
"""
|
||||
caller = self.caller
|
||||
channelkey, msg = self.args
|
||||
if not msg:
|
||||
caller.msg("Say what?")
|
||||
return
|
||||
channel = ChannelDB.objects.get_channel(channelkey)
|
||||
if not channel:
|
||||
caller.msg("Channel '%s' not found." % channelkey)
|
||||
return
|
||||
if not channel.has_connection(caller):
|
||||
string = "You are not connected to channel '%s'."
|
||||
caller.msg(string % channelkey)
|
||||
return
|
||||
if not channel.access(caller, "send"):
|
||||
string = "You are not permitted to send to channel '%s'."
|
||||
caller.msg(string % channelkey)
|
||||
return
|
||||
msg = "[%s] %s: %s" % (channel.key, caller.name, msg)
|
||||
msgobj = create.create_message(caller, msg, channels=[channel])
|
||||
channel.msg(msgobj)
|
||||
|
|
|
|||
|
|
@ -1861,7 +1861,7 @@ class TestCommsChannel(CommandTest):
|
|||
# remove alias
|
||||
self.call(
|
||||
self.cmdchannel(),
|
||||
"/unalias testchannel = foo",
|
||||
"/unalias foo",
|
||||
"Removed your channel alias 'foo'"
|
||||
)
|
||||
self.assertEqual(self.char1.nicks.get('foo $1', category="channel"), None)
|
||||
|
|
@ -2053,12 +2053,3 @@ class TestSystemCommands(CommandTest):
|
|||
multimatch.matches = matches
|
||||
|
||||
self.call(multimatch, "look", "")
|
||||
|
||||
@patch("evennia.commands.default.syscommands.ChannelDB")
|
||||
def test_channelcommand(self, mock_channeldb):
|
||||
channel = MagicMock()
|
||||
channel.msg = MagicMock()
|
||||
mock_channeldb.objects.get_channel = MagicMock(return_value=channel)
|
||||
|
||||
self.call(syscommands.SystemSendToChannel(), "public:Hello")
|
||||
channel.msg.assert_called()
|
||||
|
|
|
|||
|
|
@ -447,19 +447,17 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
|||
# send to each individual subscriber
|
||||
|
||||
try:
|
||||
message = receiver.at_pre_channel_msg(message, self, **send_kwargs)
|
||||
if message in (None, False):
|
||||
recv_message = receiver.at_pre_channel_msg(message, self, **send_kwargs)
|
||||
if recv_message in (None, False):
|
||||
return
|
||||
|
||||
receiver.channel_msg(message, self, **send_kwargs)
|
||||
receiver.channel_msg(recv_message, self, **send_kwargs)
|
||||
|
||||
receiver.at_post_channel_msg(message, self, **send_kwargs)
|
||||
receiver.at_post_channel_msg(recv_message, self, **send_kwargs)
|
||||
|
||||
except Exception:
|
||||
logger.log_trace(f"Error sending channel message to {receiver}.")
|
||||
|
||||
|
||||
|
||||
# post-send hook
|
||||
self.at_post_msg(message, **send_kwargs)
|
||||
|
||||
|
|
|
|||
|
|
@ -144,14 +144,6 @@ class TestCreateMessage(EvenniaTest):
|
|||
self.assertEqual(msg.header, "TestHeader")
|
||||
self.assertEqual(msg.senders, [self.char1])
|
||||
|
||||
def test_create_msg__channel(self):
|
||||
chan1 = create.create_channel("DummyChannel1")
|
||||
chan2 = create.create_channel("DummyChannel2")
|
||||
msg = create.create_message(
|
||||
self.char1, self.msgtext, channels=[chan1, chan2], header="TestHeader"
|
||||
)
|
||||
self.assertEqual(list(msg.channels), [chan1, chan2])
|
||||
|
||||
def test_create_msg__custom(self):
|
||||
locks = "foo:false();bar:true()"
|
||||
tags = ["tag1", "tag2", "tag3"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue