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'))
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 __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 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()
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()
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 []
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")
def setUp(self): self.plugin = PluginController() self.config = ConfigController()
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())