From 659f6ebceb994ff15533dab9f273bc1d2e0decfa Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 3 Nov 2012 23:06:57 +0100 Subject: [PATCH] NickAuth: First commit. --- plugins/NickAuth/README.txt | 6 ++ plugins/NickAuth/__init__.py | 66 ++++++++++++ plugins/NickAuth/config.py | 52 +++++++++ plugins/NickAuth/local/__init__.py | 1 + plugins/NickAuth/plugin.py | 166 +++++++++++++++++++++++++++++ plugins/NickAuth/test.py | 92 ++++++++++++++++ 6 files changed, 383 insertions(+) create mode 100644 plugins/NickAuth/README.txt create mode 100644 plugins/NickAuth/__init__.py create mode 100644 plugins/NickAuth/config.py create mode 100644 plugins/NickAuth/local/__init__.py create mode 100644 plugins/NickAuth/plugin.py create mode 100644 plugins/NickAuth/test.py diff --git a/plugins/NickAuth/README.txt b/plugins/NickAuth/README.txt new file mode 100644 index 000000000..a004ece4d --- /dev/null +++ b/plugins/NickAuth/README.txt @@ -0,0 +1,6 @@ +This plugin allows users to use their network services account to +authenticate to the bot. + +They first have to use @nickauth nick add , then use @auth +every time they want to be authenticated. + diff --git a/plugins/NickAuth/__init__.py b/plugins/NickAuth/__init__.py new file mode 100644 index 000000000..e1ead8e4a --- /dev/null +++ b/plugins/NickAuth/__init__.py @@ -0,0 +1,66 @@ +### +# Copyright (c) 2012, Valentin Lorentz +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +### + +""" +Add a description of the plugin (to be presented to the user inside the wizard) +here. This should describe *what* the plugin does. +""" + +import supybot +import supybot.world as world + +# Use this for the version of this plugin. You may wish to put a CVS keyword +# in here if you're keeping the plugin in CVS or some similar system. +__version__ = "" + +# XXX Replace this with an appropriate author or supybot.Author instance. +__author__ = supybot.authors.unknown + +# This is a dictionary mapping supybot.Author instances to lists of +# contributions. +__contributors__ = {} + +# This is a url where the most recent plugin package can be downloaded. +__url__ = '' # 'http://supybot.com/Members/yourname/NickAuth/download' + +import config +import plugin +reload(plugin) # In case we're being reloaded. +# Add more reloads here if you add third-party modules and want them to be +# reloaded when this plugin is reloaded. Don't forget to import them as well! + +if world.testing: + import test + +Class = plugin.Class +configure = config.configure + + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/plugins/NickAuth/config.py b/plugins/NickAuth/config.py new file mode 100644 index 000000000..f8939e76f --- /dev/null +++ b/plugins/NickAuth/config.py @@ -0,0 +1,52 @@ +### +# Copyright (c) 2012, Valentin Lorentz +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +### + +import supybot.conf as conf +import supybot.registry as registry +from supybot.i18n import PluginInternationalization, internationalizeDocstring + +_ = PluginInternationalization('NickAuth') + +def configure(advanced): + # This will be called by supybot to configure this module. advanced is + # a bool that specifies whether the user identified himself as an advanced + # user or not. You should effect your configuration by manipulating the + # registry as appropriate. + from supybot.questions import expect, anything, something, yn + conf.registerPlugin('NickAuth', True) + + +NickAuth = conf.registerPlugin('NickAuth') +# This is where your configuration variables (if any) should go. For example: +# conf.registerGlobalValue(NickAuth, 'someConfigVariableName', +# registry.Boolean(False, _("""Help for someConfigVariableName."""))) + + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/plugins/NickAuth/local/__init__.py b/plugins/NickAuth/local/__init__.py new file mode 100644 index 000000000..e86e97b86 --- /dev/null +++ b/plugins/NickAuth/local/__init__.py @@ -0,0 +1 @@ +# Stub so local is a module, used for third-party modules diff --git a/plugins/NickAuth/plugin.py b/plugins/NickAuth/plugin.py new file mode 100644 index 000000000..82dee822a --- /dev/null +++ b/plugins/NickAuth/plugin.py @@ -0,0 +1,166 @@ +### +# Copyright (c) 2012, Valentin Lorentz +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +### + +import time + +import supybot.conf as conf +import supybot.utils as utils +import supybot.ircdb as ircdb +from supybot.commands import * +import supybot.ircmsgs as ircmsgs +import supybot.plugins as plugins +import supybot.ircutils as ircutils +import supybot.callbacks as callbacks +from supybot.i18n import PluginInternationalization, internationalizeDocstring + +_ = PluginInternationalization('NickAuth') + +@internationalizeDocstring +class NickAuth(callbacks.Plugin): + """Support authentication based on nicks and network services.""" + def __init__(self, irc): + super(NickAuth, self).__init__(irc) + self._requests = {} + class nick(callbacks.Commands): + def _check_auth(self, irc, msg, user): + if user is None: + irc.error(_('You are not authenticated.'), Raise=True) + if not user.checkHostmask(msg.prefix): + try: + u = ircdb.users.getUser(msg.prefix) + except KeyError: + irc.error(_('You are not authenticated.'), + Raise=True) + if not u._checkCapability('owner'): + irc.error(_('You must be owner to do that.'), + Raise=True) + + @internationalizeDocstring + def add(self, irc, msg, args, network, user, nick): + """[] + + Add to the list of nicks owned by the on the + . You have to register this nick to the network + services to be authenticated. + defaults to the current network. + """ + network = network.network or irc.network + user = user or ircdb.users.getUser(msg.prefix) + self._check_auth(irc, msg, user) + try: + user.addNick(network, nick) + except KeyError: + irc.error(_('This nick is already used by someone on this ' + 'network.'), Raise=True) + irc.replySuccess() + add = wrap(add, [optional('networkIrc'), + optional('otherUser'), + 'nick']) + + @internationalizeDocstring + def remove(self, irc, msg, args, network, user, nick): + """[] + + Remove from the list of nicks owned by the on the + . + defaults to the current network. + """ + network = network.network or irc.network + user = user or ircdb.users.getUser(msg.prefix) + self._check_auth(user, irc, msg) + try: + user.removeNick(network, nick) + except KeyError: + irc.error(_('This nick is not registered to you on this ' + 'network.'), Raise=True) + irc.replySuccess() + remove = wrap(remove, [optional('networkIrc'), + optional('otherUser'), + 'nick']) + + @internationalizeDocstring + def list(self, irc, msg, args, network, user): + """[] [] + + Lists nicks of the on the network. + defaults to the current network. + """ + network = network.network or irc.network + user = user or ircdb.users.getUser(msg.prefix) + self._check_auth(irc, msg, user) + try: + list_ = user.nicks[network] + if list_: + irc.reply(format('%L', list_)) + else: + raise KeyError + except KeyError: + irc.error(_('You have no recognized nick on this ' + 'network.'), Raise=True) + list = wrap(list, [optional('networkIrc'), + optional('otherUser')]) + + @internationalizeDocstring + def auth(self, irc, msg, args): + """takes no argument + + Tries to authenticate you using network services. + If you get no reply, it means you are not authenticated to the + network services.""" + nick = ircutils.toLower(msg.nick) + self._requests[(irc.network, msg.nick)] = (time.time(), msg.prefix, irc) + irc.queueMsg(ircmsgs.whois(nick, nick)) + auth = wrap(auth, []) + + def do330(self, irc, msg): + mynick, theirnick, theiraccount, garbage = msg.args + # I would like to use a dict comprehension, but we have to support + # Python 2.6 :( + self._requests = dict([(x,y) for x,y in self._requests.items() + if y[0]+60>time.time()]) + try: + (timestamp, prefix, irc) = self._requests.pop((irc.network, theirnick)) + except KeyError: + return + user = ircdb.users.getUserFromNick(irc.network, theiraccount) + if not user: + user = ircdb.users.getUserFromNick(irc.network, theirnick) + if user: + user.addAuth(prefix) + ircdb.users.setUser(user, flush=False) + irc.reply(_('You are now authenticated as %s.') % user.name) + else: + irc.error(_('No user has this nick on this network.')) + + +Class = NickAuth + + +# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/NickAuth/test.py b/plugins/NickAuth/test.py new file mode 100644 index 000000000..c3cca6bcb --- /dev/null +++ b/plugins/NickAuth/test.py @@ -0,0 +1,92 @@ +### +# Copyright (c) 2012, Valentin Lorentz +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +### + +import supybot.ircdb as ircdb +from supybot.test import * + +class NickAuthTestCase(PluginTestCase): + plugins = ('NickAuth', 'User') + + prefix1 = 'something!user@host.tld' + def _procedure(self, nickserv_reply): + self.assertNotError('register foobar 123') + self.assertResponse('user list', 'foobar') + self.assertNotError('hostmask remove foobar %s' % self.prefix) + self.assertNotError('identify foobar 123') + self.assertNotError('nick add foobar baz') + self.assertNotError('unidentify') + self.prefix = self.prefix1 + self.assertError('nick add foobar qux') + self.nick = self.prefix.split('!')[0] + + self.assertError('hostmask list') + self.irc.feedMsg(ircmsgs.privmsg(self.irc.nick, + 'auth', + prefix=self.prefix)) + self.assertIs(self.irc.takeMsg().command, 'WHOIS') + self.assertError('hostmask list') + + self.irc.feedMsg(ircmsgs.privmsg(self.irc.nick, + 'auth', + prefix=self.prefix)) + self.assertEqual(self.irc.takeMsg().command, 'WHOIS') + if nickserv_reply: + self.irc.feedMsg(ircmsgs.IrcMsg(':leguin.freenode.net 330 pgjrgrg ' + '%s baz :is logged in as' % self.nick)) + msg = self.irc.takeMsg() + self.assertNotEqual(msg, None) + self.assertEqual(msg.args[1], 'You are now authenticated as foobar.') + self.assertResponse('hostmask list', + 'foobar has no registered hostmasks.') + else: + msg = self.irc.takeMsg() + self.assertEqual(msg, None) + self.assertError('hostmask list') + + def testAuth(self): + self._procedure(True) + def testNoAuth(self): + self._procedure(False) + + def testList(self): + self.assertNotError('register foobar 123') + self.assertRegexp('nick list', 'You have no recognized nick') + self.assertNotError('nick add foo') + self.assertRegexp('nick list', 'foo') + self.assertNotError('nick add %s bar' % self.nick) + self.assertRegexp('nick list', 'foo and bar') + self.assertNotError('nick add %s %s baz' % (self.irc.network, self.nick)) + self.assertRegexp('nick list', 'foo, bar, and baz') + self.assertRegexp('nick list %s' % self.irc.network, 'foo, bar, and baz') + self.assertRegexp('nick list %s foobar' % self.irc.network, + 'foo, bar, and baz') + + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: