# permissions.py # -*- coding: utf-8 -*- """A plugin for irc3 that provides a permission system using TinyDB.""" import irc3 from irc3.plugins.command import command from tinydb import Query, TinyDB import fnmatch from ircstyle import style @irc3.plugin class TinyDBPermissions: """Main permission system plugin handling storage and commands.""" def __init__(self, bot): self.bot = bot self.permission_db = TinyDB('permissions.json') self.bot.permission_db = self.permission_db self.User = Query() self.bot.log.info("TinyDB permissions plugin initialized") @command(permission='admin') def perm(self, mask, target, args): """Manage permissions through command interface. Usage: %%perm --add %%perm --del %%perm --list [] """ if args['--add']: self._add_permission(target, args[''], args['']) elif args['--del']: self._del_permission(target, args[''], args['']) elif args['--list']: self._list_permissions(target, args['']) else: error_msg = style( "Invalid syntax. Use --add, --del, or --list.", fg='red', bold=True ) self.bot.privmsg(target, error_msg) @command(permission='admin') def ignore(self, mask, target, args): """Manage user ignore list. Usage: %%ignore --add %%ignore --del """ nick = args[''] user_mask = f"{nick}!*@*" if args['--add']: if self.permission_db.contains( (self.User.mask == user_mask) & (self.User.permission == 'ignore') ): msg = style(f"{nick} already ignored", fg='yellow', bold=True) else: self.permission_db.insert({'mask': user_mask, 'permission': 'ignore'}) msg = style(f"Ignored {nick}", fg='green', bold=True) elif args['--del']: removed = self.permission_db.remove( (self.User.mask == user_mask) & (self.User.permission == 'ignore') ) msg = style(f"Unignored {nick} ({len(removed)} entries)", fg='green', bold=True) else: msg = style("Invalid syntax", fg='red', bold=True) self.bot.privmsg(target, msg) def _add_permission(self, target, user_mask, perm): """Add a permission to the database.""" existing = self.permission_db.search( (self.User.mask == user_mask) & (self.User.permission == perm) ) if existing: msg = style( f"Permission '{perm}' already exists for {user_mask}", fg='yellow', bold=True ) else: self.permission_db.insert({'mask': user_mask, 'permission': perm}) msg = style( f"Added permission '{perm}' for {user_mask}", fg='green', bold=True ) self.bot.privmsg(target, msg) def _del_permission(self, target, user_mask, perm): """Remove a permission from the database.""" removed = self.permission_db.remove( (self.User.mask == user_mask) & (self.User.permission == perm) ) if removed: msg = style( f"Removed {len(removed)} '{perm}' permission(s) for {user_mask}", fg='green', bold=True ) else: msg = style( f"No '{perm}' permissions found for {user_mask}", fg='red', bold=True ) self.bot.privmsg(target, msg) def _list_permissions(self, target, mask_filter): """List permissions matching a filter pattern.""" mask_filter = mask_filter or '*' regex = fnmatch.translate(mask_filter).split('(?ms)')[0].rstrip('\\Z') entries = self.permission_db.search( self.User.mask.matches(regex) ) if not entries: msg = style("No permissions found", fg='red', bold=True) self.bot.privmsg(target, msg) return for entry in entries: msg = style( f"{entry['mask']}: {entry['permission']}", fg='blue', bold=True ) self.bot.privmsg(target, msg) class TinyDBPolicy: """Authorization system for command access control.""" def __init__(self, bot): self.bot = bot self.User = Query() def has_permission(self, client_mask, permission): """Check if a client has required permissions.""" # Check ignore list first ignored = self.bot.permission_db.search( self.User.permission == 'ignore' ) for entry in ignored: if fnmatch.fnmatch(client_mask, entry['mask']): return False # Check permissions if not ignored if permission is None: return True # Check for matching permissions using fnmatch entries = self.bot.permission_db.search( self.User.permission.test(lambda p: p in (permission, 'all_permissions')) ) for entry in entries: if fnmatch.fnmatch(client_mask, entry['mask']): return True return False def __call__(self, predicates, meth, client, target, args): """Enforce command permissions.""" cmd_name = predicates.get('name', meth.__name__) client_hostmask = str(client) if self.has_permission(client_hostmask, predicates.get('permission')): return meth(client, target, args) error_msg = style( f"Access denied for '{cmd_name}' command", fg='red', bold=True ) self.bot.privmsg(client.nick, error_msg)