示例#1
0
    def testMasterAccess(self):
        config = ConfigController()
        config.load_defaults()
        config.get('masters').append("*[email protected]")

        identityMaster = IRCUser('[email protected]')
        identityNormal = IRCUser('[email protected]')

        self.assertTrue(self.acl.check_access(identityMaster, 'something'))
        self.assertFalse(self.acl.check_access(identityNormal, 'something'))
示例#2
0
    def __init__(self, database = 'data/acl.db'):
        self.db = database
        self.config = ConfigController()

        try:
            conn = sqlite3.connect(self.db)
            #cursor = conn.cursor()

            # ACL
            fp = open("resources/acl/acl.sql", "r")
            data = fp.read()
            fp.close()

            # Triggers
            fp = open("resources/acl/triggers.sql", "r")
            data += fp.read()
            fp.close()

            conn.executescript(data)
            conn.commit()

            if not self._user_exists("any"):
                self.add_user("any")
                self.user_add_hostmask("any", "*!*@*")

        except sqlite3.Error as e:
            Logger.fatal("Unable to create ACL database: %s" % e)
示例#3
0
    def __init__(self):
        self.eventcontroller   = EventController()
        self.plugincontroller  = PluginController()
        self.ircnetscontroller = IRCNetsController()

        self.config = ConfigController()
        self.config.reload()

        self.quit_event = threading.Event()
        self.eventcontroller.register_system_event("BOT_QUIT", self.system_quit)
示例#4
0
    def construct(self, eventcontroller = None, configcontroller = None):
        self._loaded_plugins = {}

        if eventcontroller:
            self._eventcontroller = eventcontroller
        else:
            self._eventcontroller = EventController()

        if configcontroller:
            self._config = configcontroller
        else:
            self._config = ConfigController()
示例#5
0
class NeuBot:
    """NeuBot"""
    def __init__(self):
        self.eventcontroller   = EventController()
        self.plugincontroller  = PluginController()
        self.ircnetscontroller = IRCNetsController()

        self.config = ConfigController()
        self.config.reload()

        self.quit_event = threading.Event()
        self.eventcontroller.register_system_event("BOT_QUIT", self.system_quit)

    def system_quit(self, params):
        self.quit_event.set()

    def start(self):
        # Initialize data store
        DatastoreController().set_driver(self.config.get('datastore'))

        Logger.set_loglevel(self.config.get('log.level'))

        for plugin in self.config.get('coreplugins'):
            Logger.info("Loading core plugin '%s'" % plugin)
            self.plugincontroller.load_plugin(plugin, 'core')

        for plugin in self.config.get('plugins'):
            Logger.info("Loading plugin '%s'" % plugin)
            self.plugincontroller.load_plugin(plugin)

        if len(self.config.get('ircnets')) == 0:
            raise Exception("There has to be at least one ircnet to connect to")

        for net in self.config.get('ircnets'):
            irc = IRCController(self.eventcontroller)
            irc.set_configuration(net)

            Logger.info("Connecting to %s..." % irc.get_ircnet_name())
            irc.connect()

            self.ircnetscontroller.add_ircnet(irc.get_ircnet_name(), irc)

    def stop(self):
        self.ircnetscontroller.disconnect_all()
        self.plugincontroller.unload_all()
示例#6
0
class ACLController:
    def __init__(self, database = 'data/acl.db'):
        self.db = database
        self.config = ConfigController()

        try:
            conn = sqlite3.connect(self.db)
            #cursor = conn.cursor()

            # ACL
            fp = open("resources/acl/acl.sql", "r")
            data = fp.read()
            fp.close()

            # Triggers
            fp = open("resources/acl/triggers.sql", "r")
            data += fp.read()
            fp.close()

            conn.executescript(data)
            conn.commit()

            if not self._user_exists("any"):
                self.add_user("any")
                self.user_add_hostmask("any", "*!*@*")

        except sqlite3.Error as e:
            Logger.fatal("Unable to create ACL database: %s" % e)

    def check_access(self, identity, context):
        masters = self.config.get('masters')

        # Check if user has master access
        for hostmask in masters:
            if identity.is_matching(hostmask):
                return True

        result = self._query("SELECT hostmask FROM list_access WHERE context = ?", (context,))
        if result:
            for (row,) in result:
                if identity.is_matching(row):
                    return True

        return False

    def _query(self, querystring, parameters = ()):
        conn  = sqlite3.connect(self.db)
        cursor = conn.cursor()

        try:
            cursor.execute(querystring, parameters)
            result = cursor.fetchall()
            conn.commit()

            return result
        except sqlite3.Error as e:
            Logger.warning("Query error (%s) %s: %s" % (querystring, parameters, e))
            return None

    def _user_exists(self, username):
        result = self._query("SELECT name FROM acl_aro WHERE name = ? AND type = 'user'", (username,))
        if not result is None:
            if len(result) != 0:
                return True

        return False

    def _group_exists(self, groupname):
        result = self._query("SELECT name FROM acl_aro WHERE name = ? AND type = 'group'", (groupname,))
        if not result is None:
            if len(result) != 0:
                return True

        return False

    def _hostmask_exists(self, username, hostmask):
        result = self._query("SELECT hostmask FROM acl_user_hostmasks WHERE aro_name = ? AND hostmask = ?", (username, hostmask))
        if not result is None:
            if len(result) != 0:
                return True

        return False

    def add_group(self, groupname):
        if self._group_exists(groupname):
            raise Exception("Group already exists")

        if self._query("INSERT INTO acl_aro (name, type) VALUES (?, ?)", (groupname, "group")) is None:
            raise Exception("Unable to add group %s" % (groupname))

    def del_group(self, groupname):
        if not self._group_exists(groupname):
            raise Exception("No such group")

        if self._query("DELETE FROM acl_aro WHERE name = ? AND type = 'group'", (groupname,)) is None:
            raise Exception("Unable to remove group %s" % (groupname))

    def add_user(self, username):
        if self._user_exists(username):
            raise Exception("User already exists")

        if self._query("INSERT INTO acl_aro (name, type) VALUES (?, ?)", (username, "user")) is None:
            raise Exception("Unable to add user %s" % (username))

    def del_user(self, username):
        if not self._user_exists(username):
            raise Exception("No such user")

        if self._query("DELETE FROM acl_aro WHERE name = ? AND type = 'user'", (username,)) is None:
            raise Exception("Unable to remove user %s" % (username))

    def user_add_hostmask(self, username, hostmask):
        if not self._user_exists(username):
            raise Exception("No such user")

        if not hostmask or len(hostmask) == 0:
            raise Exception("Empty hostmask")

        if IRCUser.whoparser.match(hostmask) == None:
            raise Exception("Invalid hostmask")

        if self._query("INSERT INTO acl_user_hostmasks (aro_name, hostmask) VALUES (?, ?)", (username, hostmask)) is None:
            raise Exception("Unable to add hostmask")

    def user_del_hostmask(self, username, hostmask):
        if not self._user_exists(username):
            raise Exception("No such user")

        if not self._hostmask_exists(username, hostmask):
            raise Exception("No such hostmask for user '%s'" % username)

        if self._query("DELETE FROM acl_user_hostmasks WHERE aro_name = ? AND hostmask = ?", (username, hostmask)) is None:
            raise Exception("Unable to remove hostmask")

    def group_add_user(self, username, groupname):
        if not self._user_exists(username):
            raise Exception("No such user")

        if not self._group_exists(groupname):
            raise Exception("No such group")

        result = self._query("SELECT aro_name_1 FROM acl_memberships WHERE aro_name_1 = ? AND aro_name_2 = ?", (username, groupname))
        if not result is None:
            if len(result) != 0:
                raise Exception("User %s is already a member of %s" % (username, groupname))

        if self._query("INSERT INTO acl_memberships (aro_name_1, aro_name_2) VALUES (?, ?)", (username, groupname)) is None:
            raise Exception("Unable to add user to group")

    def group_del_user(self, username, groupname):
        if not self._user_exists(username):
            raise Exception("No such user")

        if not self._group_exists(groupname):
            raise Exception("No such group")

        result = self._query("SELECT aro_name_1 FROM acl_memberships WHERE aro_name_1 = ? AND aro_name_2 = ?", (username, groupname))
        if not result is None:
            if len(result) != 1:
                raise Exception("User %s is not a member %s" % (username, groupname))

        if self._query("DELETE FROM acl_memberships WHERE aro_name_1 = ? AND aro_name_2 = ?", (username, groupname)) is None:
            raise Exception("Unable to remove user from group")

    def access_allow(self, context, aro):
        isGroup = self._group_exists(aro)
        isUser  = self._user_exists(aro)
        if not isGroup and not isUser:
            raise Exception("No such user or group")

        # Add context if it doesn't exist
        context = context.lower()
        self._query("INSERT INTO acl_aco (name) VALUES (?)", (context,))

        # Clear any old access
        self._query("DELETE FROM acl_access WHERE aro_name = ? AND aco_name = ?", (aro, context))

        if self._query("INSERT INTO acl_access (aro_name, aco_name, access) VALUES (?, ?, 1)", (aro, context)) is None:
            raise Exception("Unable to grant access")

    def access_deny(self, context, aro):
        isGroup = self._group_exists(aro)
        isUser  = self._user_exists(aro)
        if not isGroup and not isUser:
            raise Exception("No such user or group")

        # Add context if it doesn't exist
        context = context.lower()
        self._query("INSERT INTO acl_aco (name) VALUES (?)", (context,))

        # Clear any old access
        self._query("DELETE FROM acl_access WHERE aro_name = ? AND aco_name = ?", (aro, context))

        if self._query("INSERT INTO acl_access (aro_name, aco_name, access) VALUES (?, ?, 0)", (aro, context)) is None:
            raise Exception("Unable to deny access")

    def access_remove(self, context, aro):
        isGroup = self._group_exists(aro)
        isUser  = self._user_exists(aro)
        if not isGroup and not isUser:
            raise Exception("No such user or group")

        self._query("DELETE FROM acl_access WHERE aro_name = ? AND aco_name = ?", (aro, context))

    def access_clear(self, context):
        self._query("DELETE FROM acl_aco WHERE name = ?", (context.lower(),))

    def get_user_info(self, username):
        if not self._user_exists(username):
            raise Exception("No such user")

        info = {
            "username": username,
            "hostmasks": [],
            "groups": [],
            "access": []
        }

        # Get hostmasks
        result = self._query("SELECT hostmask FROM user_hostmasks WHERE username = ?", (username, ))
        if not result is None:
            info["hostmasks"] = [row[0] for row in result]

        # Get group memberships
        result = self._query("SELECT groupname FROM user_groups WHERE username = ?", (username, ))
        if not result is None:
            info["groups"] = [row[0] for row in result]

        # Get command access
        result = self._query("SELECT aro_name, username, context FROM list_access WHERE username = ? OR aro_name = ? GROUP BY context", (username, username))
        if not result is None:
            for (access_aro, member_username, context) in result:
                if member_username is None:
                    info["access"].append((context, None))
                else:
                    info["access"].append((context, access_aro))

        return info

    def get_group_members(self, groupname):
        result = self._query("SELECT aro_name_1 FROM acl_memberships WHERE aro_name_2 = ?", (groupname,))

        if result is None or len(result) == 0:
            raise Exception("No such group. Or group have no members.")
        else:
            return [row[0] for row in result]

    def get_users(self):
        result = self._query("SELECT name FROM acl_aro WHERE type = 'user'")

        if not result is None and len(result) != 0:
            return [row[0] for row in result]
        else:
            return []

    def get_groups(self):
        result = self._query("SELECT name FROM acl_aro WHERE type = 'group'")

        if not result is None and len(result) != 0:
            return [row[0] for row in result]
        else:
            return []
示例#7
0
class PluginController(Singleton):
    def construct(self, eventcontroller = None, configcontroller = None):
        self._loaded_plugins = {}

        if eventcontroller:
            self._eventcontroller = eventcontroller
        else:
            self._eventcontroller = EventController()

        if configcontroller:
            self._config = configcontroller
        else:
            self._config = ConfigController()

    ##
    # Unload all loaded plugins
    def unload_all(self):
        for plugin in self._loaded_plugins.keys():
            self.unload_plugin(plugin)

    ##
    # Reload a plugin by name
    # @note The plugin will be unloaded but not loaded again if it's in a non-standard path
    def reload_plugin(self, name, search_dir = None):
        try:
            self.unload_plugin(name)
        except:
            pass

        return self.load_plugin(name, search_dir)

    def unload_plugin(self, name):
        name = name.strip().lower()

        if not self._loaded_plugins.has_key(name):
            raise PluginUnloadError("No such plugin loaded")

        basename, instance, import_name = self._loaded_plugins[name]

        # Release events related to this plugin
        self._eventcontroller.release_related(instance)

        # Try to call Cleanup if it exists
        try:
            instance.cleanup()
        except:
            pass

        # Delete instance
        del instance
        del self._loaded_plugins[name]

        for module in sys.modules.keys():
            if module.startswith(import_name):
                del sys.modules[module]

        return True

    ##
    # Retrieve the names of all loaded plugins
    def get_loaded_plugins(self):
        return self._loaded_plugins.keys()

    ##
    # Generator for all candidate plugins
    # This will search through the given directory (or the plugin_paths if not specified)
    # and yield all candidate plugins in the form (path, name)
    #
    # @param search_dir Optional search directory. If not specified the plugin_paths from the
    #                   configuration will be used
    def plugin_candidates(self, search_dir = None):
        if search_dir:
            search_dirs = [search_dir]
        else:
            search_dirs = self._config.get('plugin_paths')

        ignore = shutil.ignore_patterns("*.pyc", "__init__.py", ".*")
        for search_dir in search_dirs:
            for root, dirs, files in os.walk(search_dir):
                ignored_files = ignore(root, files)
                files = [f for f in files if f not in ignored_files]

                # Look for plugins contained in directories
                for directory in dirs:
                    path = root + "." + directory
                    yield (root, directory)

                # We don't want to recurse
                dirs[:] = []

                # Replace path separators with dots to allow
                # it to be loaded directly
                root = root.replace(os.sep, ".")

                # Look for plugins containes as single files
                for filename in files:
                    base = filename.partition(".")[0]
                    path = root + "." + base
                    yield (root, base)

    ##
    # Find a plugin by name
    #
    # @param name Name of the plugin to search for
    # @param search_dir Optional search directory. If not specified the plugin_paths from the
    #                   configuration will be used
    def find_plugin(self, name, search_dir = None):
        for path, candidate_name in self.plugin_candidates(search_dir):
            if candidate_name.lower() == name.lower():
                module_name = path + "." + candidate_name
                Logger.debug("Candidate plugin '%s'" % module_name)
                return module_name

    ##
    # Load a specified plugin
    # @param name Name of plugin file (case insensitive)
    # @param search_dir Directory too look for plugin. If not specified, plugin_path
    #                   from the configuration will be used
    def load_plugin(self, name, search_dir = None):
        name = name.strip()
        import_name = None

        if self._loaded_plugins.has_key(name):
            raise PluginLoadError("Plugin is already loaded")

        import_name = self.find_plugin(name, search_dir)

        if not import_name:
            raise PluginLoadError("No such plugin")

        basename = import_name.rpartition(".")[2]

        try:
            mod = __import__(import_name)
            cls = getattr(mod, basename)
        except Exception as e:
            # Remove the system-entry
            if import_name and sys.modules.has_key(import_name):
                del sys.modules[import_name]

            Logger.log_traceback(self)
            raise PluginLoadError("Failed to load " + import_name)

        # Find the plugin entry point
        for objname in dir(cls):
            obj = getattr(cls, objname)
            if objname != 'Plugin' and type(obj) == types.ClassType and issubclass(obj, Plugin):
                Logger.debug("Plugin entry is '%s'" % objname)
                instance = new.instance(obj)

                # Initialize plugin instance
                instance.store  = DatastoreController().get_store(basename)
                instance.event  = self._eventcontroller
                instance.config = self._config
                instance.nets   = IRCNetsController()
                instance.plugin = self
                instance.__init__()

                self._loaded_plugins[basename.lower()] = (basename, instance, import_name)

                return True

        del sys.modules[import_name]
        raise PluginLoadError("Unable to find entry point")
示例#8
0
 def setUp(self):
     self.plugin = PluginController()
     self.config = ConfigController()
示例#9
0
class TestPluginController(unittest.TestCase):
    def setUp(self):
        self.plugin = PluginController()
        self.config = ConfigController()

    def tearDown(self):
        self.plugin = None

    def testLoadInvalidPluginException(self):
        self.assertRaises(PluginLoadError, self.plugin.load_plugin, 'nonexistantplugin')
        self.assertRaises(PluginLoadError, self.plugin.load_plugin, '')

    def testLoadUnloadCore(self):
        self.assertTrue(self.plugin.load_plugin('corecommands', 'core'))

        # No need to specify path, it should work anyway
        self.assertTrue(self.plugin.unload_plugin('corecommands'))

        # To make sure that it was unloaded, load it again
        self.assertTrue(self.plugin.load_plugin('corecommands', 'core'))
        self.assertTrue(self.plugin.unload_plugin('corecommands'))

    def testLoadUnloadDuplicate(self):
        self.assertTrue(self.plugin.load_plugin('normalplugin', 'test_plugins'))
        self.assertRaises(PluginLoadError, self.plugin.load_plugin, 'normalplugin', 'test_plugins')

        self.assertTrue(self.plugin.unload_plugin('normalplugin'))
        self.assertRaises(PluginUnloadError, self.plugin.unload_plugin, 'normalplugin')

    def testNormalPlugin(self):
        self.assertTrue(self.plugin.load_plugin('normalplugin', 'test_plugins'))
        self.assertTrue(self.plugin.unload_plugin('normalplugin'))

        self.assertTrue(self.plugin.load_plugin('  normalplugin', 'test_plugins'))
        self.assertTrue(self.plugin.unload_plugin('normalplugin  '))

    def testBrokenPlugin(self):
        self.assertRaises(PluginLoadError, self.plugin.load_plugin, 'pluginwithsyntaxerror', 'test_plugins')
        self.assertRaises(PluginLoadError, self.plugin.load_plugin, 'pluginrasesininit', 'test_plugins')

    def testPluginPaths(self):
        # Make sure we have a know state
        old_value = self.config.get("plugin_paths")
        self.config.set("plugin_paths", ["plugins"])

        # This plugin is not in the default path and should not be found
        self.assertRaises(PluginLoadError, self.plugin.load_plugin, 'normalplugin')

        # Now add it to the path
        self.config.set("plugin_paths", ["plugins", "test_plugins"])
        self.assertTrue(self.plugin.load_plugin('normalplugin'))
        self.assertTrue(self.plugin.unload_plugin('normalplugin'))

        # Restore previous value
        self.config.set("plugin_paths", old_value)

    def testLoadUnloadAllPlugins(self):
        self.assertEqual([], self.plugin.get_loaded_plugins())

        # Load a plugin
        self.assertTrue(self.plugin.load_plugin('normalplugin', 'test_plugins'))
        self.assertTrue(self.plugin.load_plugin('corecommands', 'core'))
        self.assertEqual(['corecommands', 'normalplugin'], self.plugin.get_loaded_plugins())

        # Unload all plugins
        self.plugin.unload_all()
        self.assertEqual([], self.plugin.get_loaded_plugins())

    def testLoadPluginWithoutEntry(self):
        self.assertRaises(PluginLoadError, self.plugin.load_plugin, 'pluginwithoutentry', 'test_plugins')

    def testReloadPlugin(self):
        class TestStub:
            def register_event(self, event, callback):
                # Trigger the callback
                self.value = callback(self)

            def release_related(self, *args):
                pass

        # Create our plugin
        code = [
            "from lib.plugin import Plugin",
            "class TestReloadPlugin(Plugin):",
            "    def __init__(self):",
            "        self._test_value = 'foo'",
            "        self.event.register_event('test', self.event_foo)",
            "    def event_foo(self, irc):",
            "        return self._test_value"
        ]
        plugin = open("test_plugins/testreloadplugin.py", "w")
        plugin.writelines("\n".join(code))
        plugin.close()

        teststub = TestStub()
        self.plugin.construct(eventcontroller = teststub)

        self.assertTrue(self.plugin.load_plugin('testreloadplugin', 'test_plugins'))
        self.assertEqual(teststub.value, 'foo')

        # Modify the line above that sets the value
        code[3] = "        self._test_value = 'bar'"
        plugin = open("test_plugins/testreloadplugin.py", "w")
        plugin.writelines("\n".join(code))
        plugin.close()

        # Delete the pyc-file, otherwise this might be loaded instead
        # This is simply because the tests runs so fast that Python thinks the pyc-file is up to date
        os.unlink("test_plugins/testreloadplugin.pyc")

        # Reload the plugin and make sure that our changes have taken effect
        self.assertTrue(self.plugin.reload_plugin('testreloadplugin', 'test_plugins'))
        self.assertEqual(teststub.value, 'bar')

        self.assertTrue(self.plugin.unload_plugin('testreloadplugin'))

        # Remove the test plugin
        os.unlink("test_plugins/testreloadplugin.pyc")
        os.unlink("test_plugins/testreloadplugin.py")

        # Reset the plugin controller
        self.plugin.construct()

    def testReloadNotLoadedPlugin(self):
        self.assertEqual([], self.plugin.get_loaded_plugins())

        self.assertTrue(self.plugin.reload_plugin('normalplugin', 'test_plugins'))
        self.assertEqual(['normalplugin'], self.plugin.get_loaded_plugins())

        self.assertTrue(self.plugin.unload_plugin('normalplugin'))
        self.assertEqual([], self.plugin.get_loaded_plugins())