From 2fe2e9c8c4a5e5f254291c7a18068c123655c781 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 30 Apr 2016 16:54:11 -0700 Subject: [PATCH] core: move isOper, checkAuthenticated to Irc (#199) --- classes.py | 37 ++++++++++++++++++++++++++++++++++++- coreplugin.py | 8 ++++---- plugins/bots.py | 12 ++++++------ plugins/commands.py | 8 ++++---- plugins/exec.py | 8 ++++---- plugins/networks.py | 8 ++++---- plugins/opercmds.py | 12 ++++++------ plugins/relay.py | 22 +++++++++++----------- utils.py | 35 ----------------------------------- 9 files changed, 75 insertions(+), 75 deletions(-) diff --git a/classes.py b/classes.py index 0defbec..cab1e6c 100644 --- a/classes.py +++ b/classes.py @@ -15,6 +15,7 @@ import threading import ssl import hashlib from copy import deepcopy +import inspect from log import * import world @@ -26,6 +27,13 @@ import structures class ProtocolError(Exception): pass +class NotAuthenticatedError(Exception): + """ + Exception raised by checkAuthenticated() when a user fails authentication + requirements. + """ + pass + ### Internal classes (users, servers, channels) class Irc(): @@ -469,7 +477,7 @@ class Irc(): for func in world.commands[cmd]: try: func(self, source, cmd_args) - except utils.NotAuthenticatedError: + except NotAuthenticatedError: self.msg(self.called_by or source, 'Error: You are not authorized to perform this operation.') except Exception as e: log.exception('Unhandled exception caught in command %r', cmd) @@ -848,6 +856,33 @@ class Irc(): return '%s!%s@%s' % (nick, ident, host) + def isOper(self, uid, allowAuthed=True, allowOper=True): + """ + Returns whether the given user has operator status on PyLink. This can be achieved + by either identifying to PyLink as admin (if allowAuthed is True), + or having user mode +o set (if allowOper is True). At least one of + allowAuthed or allowOper must be True for this to give any meaningful + results. + """ + if uid in self.users: + if allowOper and ("o", None) in self.users[uid].modes: + return True + elif allowAuthed and self.users[uid].identified: + return True + return False + + def checkAuthenticated(self, uid, allowAuthed=True, allowOper=True): + """ + Checks whether the given user has operator status on PyLink, raising + NotAuthenticatedError and logging the access denial if not. + """ + lastfunc = inspect.stack()[1][3] + if not self.isOper(uid, allowAuthed=allowAuthed, allowOper=allowOper): + log.warning('(%s) Access denied for %s calling %r', self.name, + self.getHostmask(uid), lastfunc) + raise NotAuthenticatedError("You are not authenticated!") + return True + class IrcUser(): """PyLink IRC user class.""" def __init__(self, nick, ts, uid, ident='null', host='null', diff --git a/coreplugin.py b/coreplugin.py index 24ebc34..860ba27 100644 --- a/coreplugin.py +++ b/coreplugin.py @@ -221,7 +221,7 @@ def shutdown(irc, source, args): """takes no arguments. Exits PyLink by disconnecting all networks.""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) u = irc.users[source] log.info('(%s) SHUTDOWN requested by "%s!%s@%s", exiting...', irc.name, u.nick, @@ -233,7 +233,7 @@ def load(irc, source, args): """. Loads a plugin from the plugin folder.""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) try: name = args[0] except IndexError: @@ -262,7 +262,7 @@ def unload(irc, source, args): """. Unloads a currently loaded plugin.""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) try: name = args[0] except IndexError: @@ -386,7 +386,7 @@ def rehash(irc, source, args): Reloads the configuration file for PyLink, (dis)connecting added/removed networks. Plugins must be manually reloaded.""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) try: _rehash() except Exception as e: # Something went wrong, abort. diff --git a/plugins/bots.py b/plugins/bots.py index 88f94f6..0f74ea7 100644 --- a/plugins/bots.py +++ b/plugins/bots.py @@ -16,7 +16,7 @@ def spawnclient(irc, source, args): Admin-only. Spawns the specified PseudoClient on the PyLink server. Note: this doesn't check the validity of any fields you give it!""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) try: nick, ident, host = args[:3] except ValueError: @@ -29,7 +29,7 @@ def quit(irc, source, args): """ [] Admin-only. Quits the PyLink client with nick , if one exists.""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) try: nick = args[0] @@ -55,7 +55,7 @@ def joinclient(irc, source, args): """[] ,[], etc. Admin-only. Joins , the nick of a PyLink client, to a comma-separated list of channels. If is not given, it defaults to the main PyLink client.""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) try: # Check if the first argument is an existing PyLink client. If it is not, @@ -100,7 +100,7 @@ def nick(irc, source, args): """[] Admin-only. Changes the nick of , a PyLink client, to . If is not given, it defaults to the main PyLink client.""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) try: nick = args[0] @@ -134,7 +134,7 @@ def part(irc, source, args): """[] ,[],... [] Admin-only. Parts , the nick of a PyLink client, from a comma-separated list of channels. If is not given, it defaults to the main PyLink client.""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) try: nick = args[0] @@ -180,7 +180,7 @@ def msg(irc, source, args): """[] Admin-only. Sends message from , where is the nick of a PyLink client. If is not given, it defaults to the main PyLink client.""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) # Because we want the source nick to be optional, this argument parsing gets a bit tricky. try: diff --git a/plugins/commands.py b/plugins/commands.py index 6e99df5..17b26ba 100644 --- a/plugins/commands.py +++ b/plugins/commands.py @@ -18,7 +18,7 @@ def status(irc, source, args): irc.reply('You are identified as \x02%s\x02.' % identified) else: irc.reply('You are not identified as anyone.') - irc.reply('Operator access: \x02%s\x02' % bool(utils.isOper(irc, source))) + irc.reply('Operator access: \x02%s\x02' % bool(irc.isOper(source))) def listcommands(irc, source, args): """takes no arguments. @@ -81,7 +81,7 @@ def showuser(irc, source, args): u = irc.nickToUid(target) or target # Only show private info if the person is calling 'showuser' on themselves, # or is an oper. - verbose = utils.isOper(irc, source) or u == source + verbose = irc.isOper(source) or u == source if u not in irc.users: irc.reply('Error: Unknown user %r.' % target) return @@ -127,7 +127,7 @@ def showchan(irc, source, args): f = lambda s: irc.msg(source, s) c = irc.channels[channel] # Only show verbose info if caller is oper or is in the target channel. - verbose = source in c.users or utils.isOper(irc, source) + verbose = source in c.users or irc.isOper(source) secret = ('s', None) in c.modes if secret and not verbose: # Hide secret channels from normal users. @@ -178,7 +178,7 @@ def loglevel(irc, source, args): Sets the log level to the given . must be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. If no log level is given, shows the current one.""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) try: level = args[0].upper() try: diff --git a/plugins/exec.py b/plugins/exec.py index 9cbd780..efa877e 100644 --- a/plugins/exec.py +++ b/plugins/exec.py @@ -21,7 +21,7 @@ def _exec(irc, source, args): Admin-only. Executes in the current PyLink instance. This command performs backslash escaping of characters, so things like \\n and \\ will work. \x02**WARNING: THIS CAN BE DANGEROUS IF USED IMPROPERLY!**\x02""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) # Allow using \n in the code, while escaping backslashes correctly otherwise. args = bytes(' '.join(args), 'utf-8').decode("unicode_escape") @@ -40,7 +40,7 @@ def _eval(irc, source, args): Admin-only. Evaluates the given Python expression and returns the result. \x02**WARNING: THIS CAN BE DANGEROUS IF USED IMPROPERLY!**\x02""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) args = ' '.join(args) if not args.strip(): @@ -58,7 +58,7 @@ def raw(irc, source, args): Admin-only. Sends raw text to the uplink IRC server. \x02**WARNING: THIS CAN BREAK YOUR NETWORK IF USED IMPROPERLY!**\x02""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) args = ' '.join(args) if not args.strip(): @@ -77,7 +77,7 @@ def inject(irc, source, args): Admin-only. Injects raw text into the running PyLink protocol module, replying with the hook data returned. \x02**WARNING: THIS CAN BREAK YOUR NETWORK IF USED IMPROPERLY!**\x02""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) args = ' '.join(args) if not args.strip(): diff --git a/plugins/networks.py b/plugins/networks.py index 24da096..ccf326e 100644 --- a/plugins/networks.py +++ b/plugins/networks.py @@ -16,7 +16,7 @@ def disconnect(irc, source, args): Disconnects the network . When all networks are disconnected, PyLink will automatically exit. Note: This does not affect the autoreconnect settings of any network, so the network will likely just reconnect unless autoconnect is disabled (see the 'autoconnect' command).""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) try: netname = args[0] network = world.networkobjects[netname] @@ -39,7 +39,7 @@ def connect(irc, source, args): """ Initiates a connection to the network .""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) try: netname = args[0] network = world.networkobjects[netname] @@ -69,7 +69,7 @@ def autoconnect(irc, source, args): Sets the autoconnect time for to . You can disable autoconnect for a network by setting to a negative value.""" - utils.checkAuthenticated(irc, source) + irc.checkAuthenticated(source) try: netname = args[0] seconds = float(args[1]) @@ -91,7 +91,7 @@ def remote(irc, source, args): """ Runs on the remote network . No replies are sent back due to protocol limitations.""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) try: netname = args[0] diff --git a/plugins/opercmds.py b/plugins/opercmds.py index 07f7c76..d53c145 100644 --- a/plugins/opercmds.py +++ b/plugins/opercmds.py @@ -22,7 +22,7 @@ def checkban(irc, source, args): """ [] Oper only. If a nick or hostmask is given, return whether the given banmask will match it. Otherwise, returns a list of connected users that would be affected by such a ban, up to 50 results.""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) if ircmatch is None: irc.reply("Error: missing ircmatch module (install it via 'pip install ircmatch').") @@ -87,7 +87,7 @@ def jupe(irc, source, args): Oper-only, jupes the given server.""" # Check that the caller is either opered or logged in as admin. - utils.checkAuthenticated(irc, source) + irc.checkAuthenticated(source) try: servername = args[0] @@ -114,7 +114,7 @@ def kick(irc, source, args): """ [] Admin only. Kicks from via , where is either the nick of a PyLink client or the SID of a PyLink server.""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) try: sourcenick = args[0] channel = args[1] @@ -155,7 +155,7 @@ def kill(irc, source, args): """ [] Admin only. Kills via , where is either the nick of a PyLink client or the SID of a PyLink server.""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) try: sourcenick = args[0] target = args[1] @@ -194,7 +194,7 @@ def mode(irc, source, args): Oper-only, sets modes on the target channel.""" # Check that the caller is either opered or logged in as admin. - utils.checkAuthenticated(irc, source) + irc.checkAuthenticated(source) try: target, modes = args[0], args[1:] @@ -232,7 +232,7 @@ def topic(irc, source, args): """ Admin only. Updates the topic in a channel.""" - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) try: channel = args[0] topic = ' '.join(args[1:]) diff --git a/plugins/relay.py b/plugins/relay.py index aa83139..19b1a13 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -1235,7 +1235,7 @@ def create(irc, source, args): if source not in irc.channels[channel].users: irc.reply('Error: You must be in %r to complete this operation.' % channel) return - utils.checkAuthenticated(irc, source) + irc.checkAuthenticated(source) # Check to see whether the channel requested is already part of a different # relay. @@ -1276,10 +1276,10 @@ def destroy(irc, source, args): if network == irc.name: # If we're destroying a channel on the current network, only oper is needed. - utils.checkAuthenticated(irc, source) + irc.checkAuthenticated(source) else: # Otherwise, we'll need to be logged in as admin. - utils.checkAuthenticated(irc, source, allowOper=False) + irc.checkAuthenticated(source, allowOper=False) entry = (network, channel) @@ -1320,7 +1320,7 @@ def link(irc, source, args): if source not in irc.channels[localchan].users: irc.reply('Error: You must be in %r to complete this operation.' % localchan) return - utils.checkAuthenticated(irc, source) + irc.checkAuthenticated(source) if remotenet not in world.networkobjects: irc.reply('Error: No network named %r exists.' % remotenet) return @@ -1363,7 +1363,7 @@ def delink(irc, source, args): remotenet = args[1] except IndexError: remotenet = None - utils.checkAuthenticated(irc, source) + irc.checkAuthenticated(source) if not utils.isChannel(channel): irc.reply('Error: Invalid channel %r.' % channel) return @@ -1421,7 +1421,7 @@ def linked(irc, source, args): if ('s', None) in c.modes or ('p', None) in c.modes: # Only show secret channels to opers, and tag them with # [secret]. - if utils.isOper(irc, source): + if irc.isOper(source): s += '\x02[secret]\x02 ' else: continue @@ -1434,7 +1434,7 @@ def linked(irc, source, args): irc.msg(source, s) - if utils.isOper(irc, source): + if irc.isOper(source): s = '' # If the caller is an oper, we can show the hostmasks of people @@ -1460,7 +1460,7 @@ def linkacl(irc, source, args): Allows blocking / unblocking certain networks from linking to a relay, based on a blacklist. LINKACL LIST returns a list of blocked networks for a channel, while the ALLOW and DENY subcommands allow manipulating this blacklist.""" missingargs = "Error: Not enough arguments. Needs 2-3: subcommand (ALLOW/DENY/LIST), channel, remote network (for ALLOW/DENY)." - utils.checkAuthenticated(irc, source) + irc.checkAuthenticated(source) try: cmd = args[0].lower() channel = utils.toLower(irc, args[1]) @@ -1530,7 +1530,7 @@ def showuser(irc, source, args): relay = getRelay((irc.name, ch)) if relay: relaychannels.append(''.join(relay)) - if relaychannels and (utils.isOper(irc, source) or u == source): + if relaychannels and (irc.isOper(source) or u == source): irc.msg(source, "\x02Relay channels\x02: %s" % ' '.join(relaychannels)) @utils.add_cmd @@ -1538,7 +1538,7 @@ def save(irc, source, args): """takes no arguments. Saves the relay database to disk.""" - utils.checkAuthenticated(irc, source) + irc.checkAuthenticated(source) exportDB() irc.reply('Done.') @@ -1549,7 +1549,7 @@ def claim(irc, source, args): Sets the CLAIM for a channel to a case-sensitive list of networks. If no list of networks is given, shows which networks have claim over the channel. A single hyphen (-) can also be given as a list of networks to remove claim from the channel entirely. CLAIM is a way of enforcing network ownership for a channel, similarly to Janus. Unless the list is empty, only networks on the CLAIM list for a channel (plus the creating network) are allowed to override kicks, mode changes, and topic changes in it - attempts from other networks' opers to do this are simply blocked or reverted.""" - utils.checkAuthenticated(irc, source) + irc.checkAuthenticated(source) try: channel = utils.toLower(irc, args[0]) except IndexError: diff --git a/utils.py b/utils.py index c87890a..80e492f 100644 --- a/utils.py +++ b/utils.py @@ -7,7 +7,6 @@ framework. import string import re -import inspect import importlib import os @@ -15,13 +14,6 @@ from log import log import world import conf -class NotAuthenticatedError(Exception): - """ - Exception raised by checkAuthenticated() when a user fails authentication - requirements. - """ - pass - class IncrementalUIDGenerator(): """ Incremental UID Generator module, adapted from InspIRCd source: @@ -132,33 +124,6 @@ def applyModes(irc, target, changedmodes): log.warning("(%s) utils.applyModes is deprecated. Use irc.applyModes() instead!", irc.name) return irc.applyModes(target, changedmodes) -def isOper(irc, uid, allowAuthed=True, allowOper=True): - """ - Returns whether the given user has operator status on PyLink. This can be achieved - by either identifying to PyLink as admin (if allowAuthed is True), - or having user mode +o set (if allowOper is True). At least one of - allowAuthed or allowOper must be True for this to give any meaningful - results. - """ - if uid in irc.users: - if allowOper and ("o", None) in irc.users[uid].modes: - return True - elif allowAuthed and irc.users[uid].identified: - return True - return False - -def checkAuthenticated(irc, uid, allowAuthed=True, allowOper=True): - """ - Checks whether the given user has operator status on PyLink, raising - NotAuthenticatedError and logging the access denial if not. - """ - lastfunc = inspect.stack()[1][3] - if not isOper(irc, uid, allowAuthed=allowAuthed, allowOper=allowOper): - log.warning('(%s) Access denied for %s calling %r', irc.name, - getHostmask(irc, uid), lastfunc) - raise NotAuthenticatedError("You are not authenticated!") - return True - def loadModuleFromFolder(name, folder): """ Imports and returns a module, if existing, from a specific folder.