def help_for_command(self, irc, command): acl = ACLController() callbacks = self.event.get_command_callbacks(command) found_help = False if callbacks is None: irc.reply("No such command") return for plugin, callback, privileged in callbacks: # Don't show help for privileged commands if the user doesn't have access if privileged and not acl.check_access(irc.message.source, command): continue doc = callback.__doc__ if doc: found_help = True if hasattr(plugin, "name"): irc.notice(irc.message.source.nick, plugin.name + " - " + command) # Filter out empty lines lines = [line for line in doc.splitlines() if len(line) > 0] # Find minimum whitespace so we can normalize doc-comment min_whitespace = min([len(x) - len(x.lstrip()) for x in lines]) for line in lines: # Tweak whitespace and replace tabs with spaces line = line[min_whitespace:].replace("\t", " ") irc.notice(irc.message.source.nick, line) if not found_help: irc.reply("No help available")
def list_commands(self, irc): acl = ACLController() commands = self.event.get_all_commands() num_columns = 6 cmdlist = [] for command in commands: callbacks = self.event.get_command_callbacks(command) masters = self.config.get('masters') if masters: masterAccess = reduce(lambda x, y : x or y, [irc.message.source.is_matching(hostmask) for hostmask in masters]) else: masterAccess = False for _, _, privileged in callbacks: if privileged and not (masterAccess or acl.check_access(irc.message.source, command)): pass else: if privileged: cmdlist.append("*" + command) else: cmdlist.append(" " + command) table = TableFormatter(['']*num_columns, 'Available commands') while len(cmdlist) > 0: row = cmdlist[0:num_columns] row += [' '] * (num_columns - len(row)) table.add_row(row) del cmdlist[0:num_columns] for row in table.get_table(column_headers = False): irc.notice(irc.message.source.nick, row)
def __init__(self): self.author = "Jim Persson" self.name = "ACL Commands (Core)" self.acl = ACLController() self.parseTree = { "add": { "user": {"$username" : self.add_user}, "group": {"$groupname" : self.add_group}, }, "del": { "user": {"$username" : self.del_user}, "group": {"$groupname" : self.del_group}, }, "user": { "$username": { "show": self.show_user, "addmask": {"$hostmask": self.user_add_hostmask}, "delmask": {"$hostmask": self.user_del_hostmask} } }, "group": { "$groupname": { "show": self.show_group, "adduser": {"$username": self.group_add_user}, "deluser": {"$username": self.group_del_user} } }, "access": { "$context": { "allow": {"$aro": self.access_allow}, "deny" : {"$aro": self.access_deny}, "remove": {"$aro": self.access_remove}, "clear": self.access_clear } }, "show": { "users": self.get_users, "groups": self.get_groups } } self.event.register_command("acl", self.cmd_acl, True)
def construct(self): self.commandCallbacks = {} # "command" : [(object_instance, callback, privileged), ...] self.eventCallbacks = {} # "event" : [(object_instance, callback), ...] self.syseventCallbacks = {} # "sysevent": [(object_instance, callback), ...] # {obj: timer} self.moduleTimers = {} self.config = ConfigController() self.acl = ACLController() self.command_prefix = "!" # Number of active dispatched event threads self._pending_events = 0 self._pending_events_lock = threading.RLock() self._pending_events_event = threading.Event()
class EventController(Singleton): ## @brief Constructor def construct(self): self.commandCallbacks = {} # "command" : [(object_instance, callback, privileged), ...] self.eventCallbacks = {} # "event" : [(object_instance, callback), ...] self.syseventCallbacks = {} # "sysevent": [(object_instance, callback), ...] # {obj: timer} self.moduleTimers = {} self.config = ConfigController() self.acl = ACLController() self.command_prefix = "!" # Number of active dispatched event threads self._pending_events = 0 self._pending_events_lock = threading.RLock() self._pending_events_event = threading.Event() ## # Increment the number of active event threads def _increment_event_threads(self): with self._pending_events_lock: self._pending_events += 1 self._pending_events_event.clear() ## # Decrement the number of active event threads # and signal any waiters if the number of event # threads reaches zero def _decrement_event_threads(self): with self._pending_events_lock: self._pending_events -= 1 if self._pending_events == 0: self._pending_events_event.set() ## # Block until all dispatched events have finished. # This is primarily used by the simulator. def wait_for_pending_events(self): self._pending_events_event.wait() ## @brief Release callbacks registered by a module # @param obj Module instance for which callbacks should be released # @warning This should NEVER be called directly by a module def release_related(self, obj): for table in [self.commandCallbacks, self.eventCallbacks, self.syseventCallbacks]: for key in table.keys(): entries = [entry for entry in table[key] if entry[0] == obj] for entry in entries: table[key].remove(entry) if len(table[key]) == 0: del table[key] # Stop timers if self.moduleTimers.has_key(obj): for timer in self.moduleTimers[obj]: timer.cancel() del self.moduleTimers[obj] ## @brief Run a specified function after a specified time # @param callback Function to call # @param interval Time to wait before calling the callback function # @param args (Optional) Tuple containing arguments in the order they should be passed # @param kwargs (Optional) Dict containing named arguments to pass to the callback def register_timer(self, callback, interval, args = (), kwargs = {}): def do_timeout(data): callback = data["callback"] try: callback(*data["args"], **data["kwargs"]) except Exception as e: Logger.warning("Timer caught exception: %s" % e) # Free timer if self.moduleTimers.has_key(callback.im_self): for timer in self.moduleTimers[callback.im_self]: if timer == threading.currentThread(): self.moduleTimers[callback.im_self].remove(timer) if len(self.moduleTimers[callback.im_self]) == 0: del self.moduleTimers[callback.im_self] data = { "callback": callback, "args" : args, "kwargs": kwargs, } if not hasattr(callback, "im_self"): raise Exception("Only class methods can be registered as timer callbacks") # Create timer object with associated data timer = threading.Timer(interval, do_timeout, kwargs = {"data": data}) # Insert timer in list of active timers if not self.moduleTimers.has_key(callback.im_self): self.moduleTimers[callback.im_self] = [] self.moduleTimers[callback.im_self].append(timer) # Dispatch timer timer.start() ## @brief Register command callback # @param command Command excluding command prefix. Example: "mycommand" # @param callback Function to execute when this command is dispatched # @param privileged Whether or not the command is privileged or not and subject to ACL # # @note # <pre> # Callback functions should have the form # def callback(ModuleInterface, params) # Where params will be the parameters of the command. Example: "!mycommand param1 param2 param3" will yield "param1 param2 param3" # </pre> def register_command(self, command, callback, privileged = False): key = command.lower() if not self.commandCallbacks.has_key(key): self.commandCallbacks[key] = [] self.commandCallbacks[key].append((callback.im_self, callback, privileged)) ## # Retrieve all callbacks associated with a command # @param command Command to find associated callbacks for # @return List of tuples (classinstance, callback, privileged?) def get_command_callbacks(self, command): key = command.lower() if not self.commandCallbacks.has_key(key): return [] return self.commandCallbacks[key] ## # Retrieve a list of all available commands def get_all_commands(self): return self.commandCallbacks.keys() ## @brief Dispatch a command # @param irc IRCController instance # @param msg IRCMessage instance # @param command The command. Example: "mycommand" # @param params Parameters to the command. Example: "param1 param2 param3" def dispatch_command(self, irc, msg, command, params): def dispatcher_command_thread(callback, interface, params): try: callback(interface, params) except: Logger.log_traceback(callback.im_self) finally: self._decrement_event_threads() params = Arguments(params) callbacks = self.get_command_callbacks(command) Logger.debug1("Found %d receiver(s) for command '%s'" % (len(callbacks), command)) if callbacks: interface = IRCMessageController(irc, msg) for (obj, callback, privileged) in callbacks: try: # This will abort instantly as soon as a command without access is found if privileged: if not self.acl.check_access(msg.source, command.lower()): interface.reply("Access denied") return thread = threading.Thread(target = dispatcher_command_thread, kwargs = {"callback": callback, "interface": interface, "params": params}) self._increment_event_threads() thread.start() except Exception as e: Logger.log_traceback(callback.im_self) ## @brief Register event callback # Register callback for an IRC-event, this could be any command the server sends, including numerics. # @param callback Function to execute when this event is dispatched # @param event Event to register. Example: "PRIVMSG", "PART", "QUIT", "001" # # @note # <pre> # Callback functions should have the form # def callback(IRCMessageController) # </pre> def register_event(self, event, callback): key = str(event).upper() if not self.eventCallbacks.has_key(key): self.eventCallbacks[key] = [] self.eventCallbacks[key].append((callback.im_self, callback)) ## # Register a system event callback # @param event Event code # @param callback Callback function to receive the event def register_system_event(self, event, callback): key = str(event).upper() if not self.syseventCallbacks.has_key(key): self.syseventCallbacks[key] = [] self.syseventCallbacks[key].append((callback.im_self, callback)) ## # Dispatch an bot internal event # @param event Event code # @param params Parameters to send to the event handler (optional) def dispatch_system_event(self, event, params = []): key = str(event).upper() def dispatcher_event_thread(callback, params): try: callback(params) except: Logger.log_traceback(callback.im_self) if self.syseventCallbacks.has_key(key): for (_, callback) in self.syseventCallbacks[key]: thread = threading.Thread(target = dispatcher_event_thread, kwargs = {"callback": callback, "params": params}) thread.start() ## @brief Dispatch an event # @param irc IRCController instance # @param msg IRCMessage instance. IRCMessage.command contains the event. def dispatch_event(self, irc, msg): def dispatcher_event_thread(callback, interface): try: callback(interface) except: Logger.log_traceback(callback.im_self) finally: self._decrement_event_threads() if not msg.command: return # Dispatch the event key = msg.command.upper() if self.eventCallbacks.has_key(key): interface = IRCMessageController(irc, msg) for (obj, callback) in self.eventCallbacks[key]: thread = threading.Thread(target = dispatcher_event_thread, kwargs = {"callback": callback, "interface": interface}) self._increment_event_threads() thread.start() # Check if it may be a command if msg.command == "PRIVMSG": match = re.match("^%s([^ ]+)(?:$| (.*)$)" % self.command_prefix, msg.params) if match: command, params = match.groups() self.dispatch_command(irc, msg, command, params)
def setUp(self): self.tempdir = tempfile.mkdtemp() self.acl = ACLController(self.tempdir + "/acl.db")
class TestACLController(unittest.TestCase): def setUp(self): self.tempdir = tempfile.mkdtemp() self.acl = ACLController(self.tempdir + "/acl.db") def tearDown(self): shutil.rmtree(self.tempdir) def testCreateDeleteUsers(self): self.acl.add_user('bill') self.acl.add_user('bob') self.acl.add_user('anna') self.acl.add_user('linda') self.assertRaises(Exception, self.acl.add_user, 'linda') self.assertRaises(Exception, self.acl.del_user, 'user_that_doesnt_exist') def testAddDelHostMask(self): self.acl.add_user('theuser') self.acl.user_add_hostmask('theuser', '*[email protected]') self.acl.user_add_hostmask('theuser', '*[email protected]') self.assertRaises(Exception, self.acl.user_add_hostmask, 'theuser', 'invalid hostmask') self.assertRaises(Exception, self.acl.user_add_hostmask, 'theuser', '*!*invalid hostmask') self.assertRaises(Exception, self.acl.user_add_hostmask, 'theuser', 'invalid!hostmask') self.assertRaises(Exception, self.acl.user_add_hostmask, 'theuser', '') self.assertRaises(Exception, self.acl.user_add_hostmask, 'user_that_doesnt_exist', 'anyhostmask') self.assertRaises(Exception, self.acl.user_del_hostmask, 'user_that_doesnt_exist', 'anyhostmask') self.assertRaises(Exception, self.acl.user_del_hostmask, 'theuser', 'hostmask_that_doesnt_exist') self.acl.user_del_hostmask('theuser', '*[email protected]') def testAddDelGroups(self): self.acl.add_group("mygroup") self.acl.del_group("mygroup") self.acl.add_group("duplicate") self.assertRaises(Exception, self.acl.add_group, "duplicate") self.assertRaises(Exception, self.acl.del_group, "nonexistant") def testMatchHostmasks(self): self.acl.add_user('theuser') self.acl.user_add_hostmask('theuser', '[email protected]') self.acl.add_user('superuser') self.acl.user_add_hostmask('superuser', '[email protected]') self.acl.add_user('baduser') self.acl.user_add_hostmask('baduser', '[email protected]') identityNormal = IRCUser('[email protected]') identitySuper = IRCUser('[email protected]') identityBad = IRCUser('[email protected]') self.assertRaises(Exception, self.acl.access_allow, 'secret', 'nosuchuser') self.acl.access_allow('secret', 'superuser') self.assertFalse(self.acl.check_access(identityNormal, 'secret')) self.assertTrue(self.acl.check_access(identitySuper, 'secret')) self.assertFalse(self.acl.check_access(identitySuper, 'SECRET')) self.assertFalse(self.acl.check_access(identitySuper, 'secret2')) # Test group access self.acl.add_group('superusers') self.acl.group_add_user('superuser', 'superusers') self.acl.add_group('normalusers') self.acl.group_add_user('theuser', 'normalusers') # "any"-group self.acl.access_allow('foreveryone', 'any') self.assertTrue(self.acl.check_access(identityNormal, 'foreveryone')) # Exclusion (Ticket #1) #self.assertTrue(self.acl.check_access(identityBad, 'foreveryone')) #self.acl.access_deny('foreveryone', 'baduser') #self.assertFalse(self.acl.check_access(identityBad, 'foreveryone')) # Group membership self.acl.access_allow('supersecret', 'superusers') self.assertTrue(self.acl.check_access(identitySuper, 'supersecret')) self.assertFalse(self.acl.check_access(identityNormal, 'supersecret')) self.assertFalse(self.acl.check_access(identityBad, 'supersecret')) 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'))
class ACLCommands(Plugin): def __init__(self): self.author = "Jim Persson" self.name = "ACL Commands (Core)" self.acl = ACLController() self.parseTree = { "add": { "user": {"$username" : self.add_user}, "group": {"$groupname" : self.add_group}, }, "del": { "user": {"$username" : self.del_user}, "group": {"$groupname" : self.del_group}, }, "user": { "$username": { "show": self.show_user, "addmask": {"$hostmask": self.user_add_hostmask}, "delmask": {"$hostmask": self.user_del_hostmask} } }, "group": { "$groupname": { "show": self.show_group, "adduser": {"$username": self.group_add_user}, "deluser": {"$username": self.group_del_user} } }, "access": { "$context": { "allow": {"$aro": self.access_allow}, "deny" : {"$aro": self.access_deny}, "remove": {"$aro": self.access_remove}, "clear": self.access_clear } }, "show": { "users": self.get_users, "groups": self.get_groups } } self.event.register_command("acl", self.cmd_acl, True) def add_user(self, username): self.acl.add_user(username) return ["Added user %s" % username] def add_group(self, groupname): self.acl.add_group(groupname) return ["Added group %s" % groupname] def del_user(self, username): self.acl.del_user(username) return ["Deleted user %s" % username] def del_group(self, groupname): self.acl.del_group(groupname) return ["Deleted group %s" % groupname] def user_add_hostmask(self, username, hostmask): self.acl.user_add_hostmask(username, hostmask) return ["Added hostmask %s to user %s" % (hostmask, username)] def user_del_hostmask(self, username, hostmask): self.acl.user_del_hostmask(username, hostmask) return ["Removed hostmask %s to user %s" % (hostmask, username)] def group_add_user(self, username, groupname): self.acl.group_add_user(username, groupname) return ["User %s is now a member of group %s" % (username, groupname)] def group_del_user(self, username, groupname): self.acl.group_del_user(username, groupname) return ["User %s is no longer a member of group %s" % (username, groupname)] def show_user(self, username): info = self.acl.get_user_info(username) data = [] data += ["User: %s" % username] data += ["Associated Hostmasks:"] result = info["hostmasks"] if len(result) == 0: haveHostmask = False data += [" None"] else: haveHostmask = True for row in result: data += [" " + row] data += ["Group Memberships:"] result = info["groups"] if result is None: data += [" None"] else: for row in result: data += [" " + row] data += ["Command Access:"] if haveHostmask: result = info["access"] for (context, group) in result: if group is None: data += [" %s (direct access)" % (context)] else: data += [" %s (member in '%s')" % (context, group)] else: data += [" No hostmasks for this user, unable to grant access"] return data def show_group(self, groupname): members = self.acl.get_group_members(groupname) users = TableFormatter([""]*5, "Group Members") members.sort() members.reverse() while len(members) != 0: row = [] for _ in range(0, 5): if len(members) != 0: user = members.pop() row.append(user) row += [""]*(5-len(row)) users.add_row(row) return users.get_table() def get_users(self): users = self.acl.get_users() return ["Found %d users:" % len(users)] + users def get_groups(self): groups = self.acl.get_groups() return ["Found %d groups" % len(groups)] + groups def access_allow(self, context, aro): self.acl.access_allow(context, aro) return ["%s now have access to '%s'" % (aro, context)] def access_deny(self, context, aro): self.acl.access_deny(context, aro) return ["Denied access to '%s' for %s'" % (context, aro)] def access_remove(self, context, aro): self.acl.access_remove(context, aro) return ["Ok"] def access_clear(self, context): self.acl.access_clear(context) return ["Ok"] def cmd_acl(self, irc, params): """ Available operations add [user|group] <name> del [user|group] <name> user <username> [show | addmask <hostmask> | delmask <hostmask>] group <groupname> [show | adduser <username> | deluser <username>] access <context> [allow|deny|remove] <group_or_user> access <context> clear show [users|groups]""" try: result = CommandParser(self.parseTree).parse(str(params)) if result: for line in result: irc.reply(line) except CommandError as e: irc.reply(e) except Exception as e: irc.reply(e)