From f890ddac1b6f1cb08e5dbe9407edae9cd6883fe0 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 25 Aug 2016 11:41:37 -0700 Subject: [PATCH] permissions, automode: work on default permissions & add example permissions config (#190) - Fix possible type errors in add/removeDefaultPermissions by converting permlist values to sets. - Fix wrong permission string being checked in automode..#channel - automode: register and unregister default permissions on load/unload. - permissions: add an 'also_show' argument to checkPermissions(), to display alternative permissions that weren't directly checked. --- coremods/permissions.py | 17 +++++++++++------ example-permissions.yml | 35 +++++++++++++++++++++++++++++++++++ plugins/automode.py | 13 +++++++++++-- 3 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 example-permissions.yml diff --git a/coremods/permissions.py b/coremods/permissions.py index 9268b8f..624e4c0 100644 --- a/coremods/permissions.py +++ b/coremods/permissions.py @@ -7,7 +7,7 @@ import threading # Global variables: these store mappings of hostmasks/exttargets to lists of permissions each target has. default_permissions = defaultdict(set) -permissions = defaultdict(set) +permissions = defaultdict(set, {'$pylinkacc': {'*'}}) # Only allow one thread to change the permissions index at once. permissions_lock = threading.Lock() @@ -23,7 +23,12 @@ def resetPermissions(): with permissions_lock: global permissions log.debug('permissions.resetPermissions: old perm list: %s', permissions) - permissions = conf.conf.get('permissions', default_permissions) + + if not conf.conf.get('permissions_merge_defaults', True): + log.debug('permissions.resetPermissions: clearing perm list due to permissions_merge_defaults set False.') + permissions.clear() + + permissions.update(conf.conf.get('permissions', default_permissions)) log.debug('permissions.resetPermissions: new perm list: %s', permissions) def addDefaultPermissions(perms): @@ -31,16 +36,16 @@ def addDefaultPermissions(perms): with permissions_lock: global permissions for target, permlist in perms.items(): - permissions[target] |= permlist + permissions[target] |= set(permlist) def removeDefaultPermissions(perms): """Remove default permissions from the index.""" with permissions_lock: global permissions for target, permlist in perms.items(): - permissions[target] -= permlist + permissions[target] -= set(permlist) -def checkPermissions(irc, uid, perms): +def checkPermissions(irc, uid, perms, also_show=[]): """ Checks permissions of the caller. If the caller has any of the permissions listed in perms, this function returns True. Otherwise, NotAuthorizedError is raised. @@ -58,7 +63,7 @@ def checkPermissions(irc, uid, perms): if any(irc.matchHost(perm, p) for p in perms): return True raise utils.NotAuthorizedError("You are missing one of the following permissions: %s" % - (', '.join(perms))) + (', '.join(perms+also_show))) # This is called on first import. diff --git a/example-permissions.yml b/example-permissions.yml new file mode 100644 index 0000000..5c703b6 --- /dev/null +++ b/example-permissions.yml @@ -0,0 +1,35 @@ +# This file is an example of the permissions system in PyLink. Should you wish, +# you may copy the contents of this file and paste it into the configuration you're +# using. +# Permissions work by mapping hostmasks or exttargets to list of permissions, allowing +# you to fine tune which users have access to which commands. + +# The permissions API is new, and optional for plugins. Currently, only Automode uses it. + +# If you do not specify any permissions block in your configuration, PyLink will default to a +# permission set defined by plugins, which usually correspond to the list below, but can be +# changed on every release. + +# This determines whether we should merge the plugin-default permissions with the ones specified +# in the permissions: block. Disabling this allows you greater control over the permissions +# PyLink gives, but you should check this file on every major update to see if any new permissions +# were added for commands. Otherwise, commands that were available before may cease to function! +permissions_merge_defaults: true + +permissions: + # Note: It is a good idea to quote any exttargets or hostmasks so the configuration parser knows + # they are raw strings. + + "$ircop": + # The default set of Automode permissions allow you to manage any channels you own in Relay. + # If Relay is not loaded, this check will fail. This has the ability of allowing local opers + # to manage their channels, but not abusing Automode to hack modes in other networks' relay + # channels. + - automode.manage.relay_owned + - automode.sync.relay_owned + "*!*@*": + # Everyone can use /msg Automode list on any channel. + - automode.list + "$pylinkacc": + # Those with an admin login in PyLink can do anything. + - "*" diff --git a/plugins/automode.py b/plugins/automode.py index afd4e42..3efc270 100644 --- a/plugins/automode.py +++ b/plugins/automode.py @@ -24,6 +24,10 @@ exportdb_timer = None save_delay = conf.conf['bot'].get('save_delay', 300) +# The default set of Automode permissions. +default_permissions = {"$ircop": ['automode.manage.relay_owned', 'automode.sync.relay_owned'], + "*!*@*": ['automode.list']} + def loadDB(): """Loads the Automode database, silently creating a new one if this fails.""" global db @@ -66,6 +70,9 @@ def main(irc=None): # Schedule periodic exports of the automode database. scheduleExport(starting=True) + # Register our permissions. + permissions.addDefaultPermissions(default_permissions) + # Queue joins to all channels where Automode has entries. for entry in db: netname, channel = entry.split('#', 1) @@ -94,6 +101,7 @@ def die(sourceirc): log.debug("Automode: cancelling exportDB timer thread %s due to die()", threading.get_ident()) exportdb_timer.cancel() + permissions.removeDefaultPermissions(default_permissions) utils.unregisterService('automode') def checkAccess(irc, uid, channel, command): @@ -111,10 +119,11 @@ def checkAccess(irc, uid, channel, command): baseperm = 'automode.%s' % command try: # First, check the catch all and channel permissions. - return permissions.checkPermissions(irc, uid, [baseperm, baseperm+'.*', '%s.%s' % (command, channel)]) + perms = [baseperm, baseperm+'.*', '%s.%s' % (baseperm, channel)] + return permissions.checkPermissions(irc, uid, perms) except utils.NotAuthorizedError: log.debug('(%s) Automode: falling back to automode.%s.relay_owned', irc.name, command) - permissions.checkPermissions(irc, uid, [baseperm+'.relay_owned']) + permissions.checkPermissions(irc, uid, [baseperm+'.relay_owned'], also_show=perms) relay = world.plugins.get('relay') if relay is None: