From 063dd24677096958fa922a9680855787268e5385 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Fri, 9 Apr 2010 15:56:16 -0400 Subject: [PATCH] change Topic to have a default required capability set, for all write operations. by default, now only allows chanops, and users with admin or channel,op capability to change topics Signed-off-by: James Vega --- plugins/Topic/config.py | 9 ++++- plugins/Topic/plugin.py | 79 +++++++++++++++++++++++++++++++++++++++++ plugins/Topic/test.py | 20 +++++++++-- 3 files changed, 105 insertions(+), 3 deletions(-) diff --git a/plugins/Topic/config.py b/plugins/Topic/config.py index 6ab0ef3fc..f07870554 100644 --- a/plugins/Topic/config.py +++ b/plugins/Topic/config.py @@ -63,7 +63,14 @@ conf.registerGroup(Topic, 'undo') conf.registerChannelValue(Topic.undo, 'max', registry.NonNegativeInteger(10, """Determines the number of previous topics to keep around in case the undo command is called.""")) - +conf.registerChannelValue(Topic, 'requireManageCapability', + registry.String('admin; channel,op', + """Determines the + capabilities required (if any) to make any topic changes, + (everything except for read-only operations). Use 'channel,capab' for + channel-level capabilities. + Note that absence of an explicit anticapability means user has + capability.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/Topic/plugin.py b/plugins/Topic/plugin.py index ac9fef272..3c843aa6d 100644 --- a/plugins/Topic/plugin.py +++ b/plugins/Topic/plugin.py @@ -37,6 +37,7 @@ import supybot.ircmsgs as ircmsgs import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks +import supybot.ircdb as ircdb def canChangeTopic(irc, msg, args, state): assert not state.channel @@ -161,6 +162,30 @@ class Topic(callbacks.Plugin): irc.queueMsg(ircmsgs.topic(channel, newTopic)) irc.noReply() + def _checkManageCapabilities(self, irc, msg, channel): + """Check if the user has any of the required capabilities to manage + the channel topic. + + The list of required capabilities is in requireManageCapability + channel config. + + Also allow if the user is a chanop. Since he can change the topic + manually anyway. + """ + c = irc.state.channels[channel] + if msg.nick in c.ops: + return True + capabilities = self.registryValue('requireManageCapability') + if capabilities: + for capability in re.split(r'\s*;\s*', capabilities): + if capability.startswith('channel,'): + capability = ircdb.makeChannelCapability(channel, capability[8:]) + if capability and ircdb.checkCapability(msg.prefix, capability): + return True + return False + else: + return True + def doJoin(self, irc, msg): if ircutils.strEqual(msg.nick, irc.nick): # We're joining a channel, let's watch for the topic. @@ -189,6 +214,9 @@ class Topic(callbacks.Plugin): Adds to the topics for . is only necessary if the message isn't sent in the channel itself. """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) topics = self._splitTopic(irc.state.getTopic(channel), channel) topics.append(topic) self._sendTopics(irc, channel, topics) @@ -202,6 +230,9 @@ class Topic(callbacks.Plugin): is only necessary if the message isn't sent in the channel itself. """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) topics = self._splitTopic(irc.state.getTopic(channel), channel) topics.append(topic) self._sendTopics(irc, channel, topics, fit=True) @@ -212,6 +243,9 @@ class Topic(callbacks.Plugin): Replaces topic with . """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) topics = self._splitTopic(irc.state.getTopic(channel), channel) topics[i] = topic self._sendTopics(irc, channel, topics) @@ -224,6 +258,9 @@ class Topic(callbacks.Plugin): currently on . is only necessary if the message isn't sent in the channel itself. """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) topics = self._splitTopic(irc.state.getTopic(channel), channel) topics.insert(0, topic) self._sendTopics(irc, channel, topics) @@ -235,6 +272,9 @@ class Topic(callbacks.Plugin): Shuffles the topics in . is only necessary if the message isn't sent in the channel itself. """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) topics = self._splitTopic(irc.state.getTopic(channel), channel) if len(topics) == 0 or len(topics) == 1: irc.error('I can\'t shuffle 1 or fewer topics.', Raise=True) @@ -255,6 +295,9 @@ class Topic(callbacks.Plugin): is only necessary if the message isn't sent in the channel itself. """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) topics = self._splitTopic(irc.state.getTopic(channel), channel) num = len(topics) if num == 0 or num == 1: @@ -290,6 +333,9 @@ class Topic(callbacks.Plugin): index into the topics. is only necessary if the message isn't sent in the channel itself. """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) topics = self._splitTopic(irc.state.getTopic(channel), channel) irc.reply(topics[number]) get = wrap(get, ['inChannel', 'topicNumber']) @@ -303,6 +349,9 @@ class Topic(callbacks.Plugin): s/regexp/replacement/flags. is only necessary if the message isn't sent in the channel itself. """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) topics = self._splitTopic(irc.state.getTopic(channel), channel) topics[number] = replacer(topics[number]) self._sendTopics(irc, channel, topics) @@ -315,6 +364,9 @@ class Topic(callbacks.Plugin): sets the entire topic. is only necessary if the message isn't sent in the channel itself. """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) if number is not None: topics = self._splitTopic(irc.state.getTopic(channel), channel) topics[number] = topic @@ -333,6 +385,9 @@ class Topic(callbacks.Plugin): to topics starting the from the end of the topic. is only necessary if the message isn't sent in the channel itself. """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) topics = self._splitTopic(irc.state.getTopic(channel), channel) topic = topics.pop(number) self._sendTopics(irc, channel, topics) @@ -344,6 +399,9 @@ class Topic(callbacks.Plugin): Locks the topic (sets the mode +t) in . is only necessary if the message isn't sent in the channel itself. """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) irc.queueMsg(ircmsgs.mode(channel, '+t')) irc.noReply() lock = wrap(lock, ['channel', ('haveOp', 'lock the topic')]) @@ -354,6 +412,9 @@ class Topic(callbacks.Plugin): Locks the topic (sets the mode +t) in . is only necessary if the message isn't sent in the channel itself. """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) irc.queueMsg(ircmsgs.mode(channel, '-t')) irc.noReply() unlock = wrap(unlock, ['channel', ('haveOp', 'unlock the topic')]) @@ -364,6 +425,9 @@ class Topic(callbacks.Plugin): Restores the topic to the last topic set by the bot. is only necessary if the message isn't sent in the channel itself. """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) try: topics = self.lastTopics[channel] except KeyError: @@ -379,6 +443,9 @@ class Topic(callbacks.Plugin): set it. is only necessary if the message isn't sent in the channel itself. """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) self._addRedo(channel, self._getUndo(channel)) # current topic. topics = self._getUndo(channel) # This is the topic list we want. if topics is not None: @@ -393,6 +460,9 @@ class Topic(callbacks.Plugin): Undoes the last undo. is only necessary if the message isn't sent in the channel itself. """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) topics = self._getRedo(channel) if topics is not None: self._sendTopics(irc, channel, topics, isDo=True) @@ -407,6 +477,9 @@ class Topic(callbacks.Plugin): is only necessary if the message isn't sent in the channel itself. """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) topics = self._splitTopic(irc.state.getTopic(channel), channel) if first == second: irc.error('I refuse to swap the same topic with itself.') @@ -424,6 +497,9 @@ class Topic(callbacks.Plugin): default topic for a channel may be configured via the configuration variable supybot.plugins.Topic.default. """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) topic = self.registryValue('default', channel) if topic: self._sendTopics(irc, channel, [topic]) @@ -438,6 +514,9 @@ class Topic(callbacks.Plugin): Sets the topic separator for to Converts the current topic appropriately. """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) topics = self._splitTopic(irc.state.getTopic(channel), channel) self.setRegistryValue('separator', separator, channel) self._sendTopics(irc, channel, topics) diff --git a/plugins/Topic/test.py b/plugins/Topic/test.py index 0f09ec07b..9d13b3264 100644 --- a/plugins/Topic/test.py +++ b/plugins/Topic/test.py @@ -30,7 +30,7 @@ from supybot.test import * class TopicTestCase(ChannelPluginTestCase): - plugins = ('Topic',) + plugins = ('Topic','User',) def testRemove(self): self.assertError('topic remove 1') _ = self.getMsg('topic add foo') @@ -70,7 +70,23 @@ class TopicTestCase(ChannelPluginTestCase): self.assertEqual(m.command, 'TOPIC') self.assertEqual(m.args[0], self.channel) self.assertEqual(m.args[1], 'foo (test) || bar (test)') - + + def testManageCapabilities(self): + try: + world.testing = False + origuser = self.prefix + self.prefix = 'stuff!stuff@stuff' + self.assertNotError('register nottester stuff', private=True) + + self.assertError('topic add foo') + origconf = conf.supybot.plugins.Topic.requireManageCapability() + conf.supybot.plugins.Topic.requireManageCapability.setValue('') + self.assertNotError('topic add foo') + finally: + world.testing = True + self.prefix = origuser + conf.supybot.plugins.Topic.requireManageCapability.setValue(origconf) + def testInsert(self): m = self.getMsg('topic add foo') self.assertEqual(m.args[1], 'foo (test)')