def _internal_init(self): """Internal: Initialize all class members to their default value""" if not os.path.exists(self.profile): os.makedirs(self.profile) # Preferences files written to self.written_prefs = set() # Our magic markers nonce = '%s %s' % (str(time.time()), uuid.uuid4()) self.delimeters = ('#MozRunner Prefs Start %s' % nonce, '#MozRunner Prefs End %s' % nonce) # If sub-classes want to set default preferences if hasattr(self.__class__, 'preferences'): self.set_preferences(self.__class__.preferences) # Set additional preferences self.set_preferences(self._preferences) self.permissions = Permissions(self.profile, self._locations) prefs_js, user_js = self.permissions.network_prefs(self._proxy) self.set_preferences(prefs_js, 'prefs.js') self.set_preferences(user_js) # handle add-on installation self.addon_manager = AddonManager(self.profile, restore=self.restore) self.addon_manager.install_addons(self._addons, self._addon_manifests) # handle webapps self.webapps = WebappCollection(profile=self.profile, apps=self._apps) self.webapps.update_manifests()
def get_permissions(cls, reference): perm = Permissions(0) perm.set_mode('u', 'r', True) perm.set_mode('u', 'w', True) perm.set_mode('g', 'r', True) perm.set_mode('o', 'r', True) return perm
def __init__( self, profile=None, # Path to the profile addons=None, # String of one or list of addons to install addon_manifests=None, # Manifest for addons, see http://ahal.ca/blog/2011/bulk-installing-fx-addons/ preferences=None, # Dictionary or class of preferences locations=None, # locations to proxy proxy=None, # setup a proxy - dict of server-loc,server-port,ssl-port restore=True # If true remove all installed addons preferences when cleaning up ): # if true, remove installed addons/prefs afterwards self.restore = restore # prefs files written to self.written_prefs = set() # our magic markers nonce = '%s %s' % (str(time.time()), uuid.uuid4()) self.delimeters = ('#MozRunner Prefs Start %s' % nonce, '#MozRunner Prefs End %s' % nonce) # Handle profile creation self.create_new = not profile if profile: # Ensure we have a full path to the profile self.profile = os.path.abspath(os.path.expanduser(profile)) if not os.path.exists(self.profile): os.makedirs(self.profile) else: self.profile = self.create_new_profile() # set preferences if hasattr(self.__class__, 'preferences'): # class preferences self.set_preferences(self.__class__.preferences) self._preferences = preferences if preferences: # supplied preferences if isinstance(preferences, dict): # unordered preferences = preferences.items() # sanity check assert not [i for i in preferences if len(i) != 2] else: preferences = [] self.set_preferences(preferences) # set permissions self._locations = locations # store this for reconstruction self._proxy = proxy self.permissions = Permissions(self.profile, locations) prefs_js, user_js = self.permissions.network_prefs(proxy) self.set_preferences(prefs_js, 'prefs.js') self.set_preferences(user_js) # handle addon installation self.addon_manager = AddonManager(self.profile) self.addon_manager.install_addons(addons, addon_manifests)
def on_server_sync(self, msg): self.max_bandwidth = msg.max_bandwidth self.welcome_text = msg.welcome_text if msg.permissions: if not self.permissions: self.permissions = Permissions(msg.permissions) else: self.permissions.update(msg.permissions) self.bot.connected()
def invoke(update, *_): # raw_user should not be able to log out e_user raw_user = raw_whois(update) if raw_user == Permissions.e_whois(update): raise DispatcherHandlerContinue # If invoked by e_user try: Permissions.remove_user_translation(raw_user) reply(update, 'Success') except AssertionError as err: logging.error(str(err)) reply(update, 'Error: ' + str(err))
def test_authorized_scopes(): permissions = Permissions() for scope in permissions.authorized_scopes: permissions.grant(scope, 'persona', 'topic') assert permissions.authorize(scope, 'persona', 'topic') is True assert permissions.authorize('*unknown*', 'persona', 'topic') is False assert permissions.authorize(scope, '*unknown*', 'topic') is False assert permissions.authorize(scope, 'persona', '*unknown*') is False
def get_permissions(cls, reference): path = unicode(reference.path) try: mode = stat(path)[0] except UnicodeEncodeError: mode = stat(path.encode('utf-8'))[0] return Permissions(mode)
def _internal_init(self): """Internal: Initialize all class members to their default value""" if not os.path.exists(self.profile): os.makedirs(self.profile) # Preferences files written to self.written_prefs = set() # Our magic markers nonce = '%s %s' % (str(time.time()), uuid.uuid4()) self.delimeters = ('#MozRunner Prefs Start %s' % nonce, '#MozRunner Prefs End %s' % nonce) # If sub-classes want to set default preferences if hasattr(self.__class__, 'preferences'): self.set_preferences(self.__class__.preferences) # Set additional preferences self.set_preferences(self._preferences) self.permissions = Permissions(self.profile, self._locations) prefs_js, user_js = self.permissions.network_prefs(self._proxy) self.set_preferences(prefs_js, 'prefs.js') self.set_preferences(user_js) # handle add-on installation self.addon_manager = AddonManager(self.profile, restore=self.restore) self.addon_manager.install_addons(self._addons, self._addon_manifests)
def __init__(self, permissions=None, store=None, emitter=None): self.secret_attributes = ['password'] self.permissions = permissions or Permissions( path='fixtures/permissions.yaml') self.store = store or Users() self.emitter = emitter self.replay_stamp = None self.page_size = 10 self.record_maximum_size = 100000 self.routes = [] self._add_url_rule('/login', 'login', self.login, methods=['POST']) self._add_url_rule('/signin', 'signin', self.signin, methods=['POST']) self._add_url_rule('/check', 'check', self.check_bearer, methods=['GET', 'POST']) self._add_url_rule('/renew', 'renew', self.renew_bearer, methods=['GET', 'POST']) self._add_url_rule('/users', 'index', self.index) self._add_url_rule('/users/page/<token>', 'page', self.page) self._add_url_rule('/register', 'post', self.post, methods=['POST']) self._add_url_rule('/users/<id>', 'get', self.get) self._add_url_rule('/users/<id>', 'put', self.put, methods=['PUT']) self._add_url_rule('/users/<id>', 'delete', self.delete, methods=['DELETE'])
def delete_item_by_id(category_id, item_id): """ HTML endpoint providing a form to delete an item """ if not UserUtils.is_authenticated(): UserUtils.set_preauthentication_url() flash('sign in to delete an item') return redirect('/login') item = session.query(Item).filter_by(id=item_id).one() # Users may delete only items they created if not Permissions.get_user_permissions_for_item(item).delete: flash('you may delete only items you created') return redirect(url_for('get_category_by_id', category_id=category_id)) if request.method == 'POST': session.delete(item) session.commit() flash('item deleted') return redirect(url_for('get_category_by_id', category_id=category_id)) else: category = session.query(Category).filter_by(id=category_id).one() return UserUtils.render_user_template('item_delete.html', category=category, category_id=category_id, item=item, page_title="%s %s Item" % ("Delete", item.title))
def delete_category_by_id(category_id): """ HTML endpoint providing a form to delete a category """ if not UserUtils.is_authenticated(): UserUtils.set_preauthentication_url() flash('sign in to delete categories') return redirect('/login') category = session.query(Category).filter_by(id=category_id).one() if not Permissions.get_user_permissions_for_category(category).delete: flash('you may delete only empty categories you created') return redirect(url_for( 'get_categories')) if request.method == 'POST': session.delete(category) session.commit() flash('category deleted') return redirect(url_for( 'get_categories')) else: return UserUtils.render_user_template( 'category_delete.html', category=category, page_title="%s %s Category" % ("Delete", category.name))
def __init__(self, profile=None, addons=None, addon_manifests=None, preferences=None, locations=None, proxy=None, restore=True): """ :param profile: Path to the profile :param addons: String of one or list of addons to install :param addon_manifests: Manifest for addons, see http://ahal.ca/blog/2011/bulk-installing-fx-addons/ :param preferences: Dictionary or class of preferences :param locations: locations to proxy :param proxy: setup a proxy - dict of server-loc,server-port,ssl-port :param restore: If true remove all installed addons preferences when cleaning up """ # if true, remove installed addons/prefs afterwards self.restore = restore # prefs files written to self.written_prefs = set() # our magic markers nonce = '%s %s' % (str(time.time()), uuid.uuid4()) self.delimeters = ('#MozRunner Prefs Start %s' % nonce,'#MozRunner Prefs End %s' % nonce) # Handle profile creation self.create_new = not profile if profile: # Ensure we have a full path to the profile self.profile = os.path.abspath(os.path.expanduser(profile)) if not os.path.exists(self.profile): os.makedirs(self.profile) else: self.profile = self.create_new_profile() # set preferences if hasattr(self.__class__, 'preferences'): # class preferences self.set_preferences(self.__class__.preferences) self._preferences = preferences if preferences: # supplied preferences if isinstance(preferences, dict): # unordered preferences = preferences.items() # sanity check assert not [i for i in preferences if len(i) != 2] else: preferences = [] self.set_preferences(preferences) # set permissions self._locations = locations # store this for reconstruction self._proxy = proxy self.permissions = Permissions(self.profile, locations) prefs_js, user_js = self.permissions.network_prefs(proxy) self.set_preferences(prefs_js, 'prefs.js') self.set_preferences(user_js) # handle addon installation self.addon_manager = AddonManager(self.profile) self.addon_manager.install_addons(addons, addon_manifests)
def __init__(self, permissions=None, users=None, channels=None, state_file=None): self.permissions = permissions or Permissions( path='fixtures/permissions.yaml') self.users = users self.channels = channels self.state_file = state_file or 'fixtures/state.yaml' if state_file: try: with open(state_file, 'r') as stream: logger.info(f"loading '{state_file}'...") self.import_content(stream) except FileNotFoundError: logger.warning(f"can not load '{state_file}'") self.routes = [] self._add_url_rule('/ping', 'ping', self.ping, methods=['GET', 'POST']) self._add_url_rule('/snapshot', 'snapshot', self.snapshot, methods=['GET', 'POST']) self._add_url_rule('/restore', 'restore', self.restore, methods=['POST'])
def invoke(update, context, _): if 'logout' not in Loader.loaded(): reply(update, 'Refusing to login, logout module is not loaded') return try: user = Permissions.e_whois(update) new_u = context.args[0] try: Permissions.add_user_translation(user, new_u) msg = '@' + user + ' logged in as @' + new_u logging.info(msg) reply(update, msg) except AssertionError as err: reply(update, 'Error: ' + str(err)) except IndexError: reply(update, 'usage: /login <user>')
def _internal_init(self): """Internal: Initialize all class members to their default value""" if not os.path.exists(self.profile): os.makedirs(self.profile) # Preferences files written to self.written_prefs = set() # Our magic markers nonce = '%s %s' % (str(time.time()), uuid.uuid4()) self.delimeters = ('#MozRunner Prefs Start %s' % nonce, '#MozRunner Prefs End %s' % nonce) # If sub-classes want to set default preferences if hasattr(self.__class__, 'preferences'): self.set_preferences(self.__class__.preferences) # Set additional preferences self.set_preferences(self._preferences) self.permissions = Permissions(self.profile, self._locations) prefs_js, user_js = self.permissions.network_prefs(self._proxy) if self._whitelistpaths: # On macOS we don't want to support a generalized read whitelist, # and the macOS sandbox policy language doesn't have support for # lists, so we handle these specially. if platform.system() == "Darwin": assert len(self._whitelistpaths) <= 2 if len(self._whitelistpaths) == 2: prefs_js.append( ("security.sandbox.content.mac.testing_read_path2", self._whitelistpaths[1])) prefs_js.append( ("security.sandbox.content.mac.testing_read_path1", self._whitelistpaths[0])) else: prefs_js.append( ("security.sandbox.content.read_path_whitelist", ",".join(self._whitelistpaths))) self.set_preferences(prefs_js, 'prefs.js') self.set_preferences(user_js) # handle add-on installation self.addon_manager = AddonManager(self.profile, restore=self.restore) self.addon_manager.install_addons(self._addons, self._addon_manifests)
def update_item_by_id(category_id, item_id): """ HTML endpoint providing a form to edit an item """ if not UserUtils.is_authenticated(): UserUtils.set_preauthentication_url() flash('sign in to edit an item') return redirect('/login') item = session.query(Item).filter_by(id=item_id).one() # Users may update only items they created if not Permissions.get_user_permissions_for_item(item).update: flash('you may edit only items you created') return redirect(url_for('get_category_by_id', category_id=category_id)) category = session.query(Category).filter_by(id=category_id).one() if request.method == 'POST': # Extract and validate the form inputs (title, title_error) = \ extract_and_validate_item_title(request.form) (description, description_error) = \ extract_and_validate_item_description(request.form) if title_error or description_error: return UserUtils.render_user_template( 'item_update.html', category=category, category_id=category_id, item=item, page_title="%s %s Item" % ("Edit", item.title), title=title, title_error=title_error, description=description, description_error=description_error) # Create the item in the data store item.title = title item.description = description session.add(item) session.commit() flash('item updated') return redirect(url_for('get_category_by_id', category_id=category_id)) else: return UserUtils.render_user_template('item_update.html', category=category, category_id=category_id, item=item, page_title="%s %s Item" % ("Edit", item.title), title=item.title, description=item.description)
def __init__(self, profile=None, addons=None, addon_manifests=None, apps=None, preferences=None, locations=None, proxy=None, restore=True): # if true, remove installed addons/prefs afterwards self.restore = restore # prefs files written to self.written_prefs = set() # our magic markers nonce = '%s %s' % (str(time.time()), uuid.uuid4()) self.delimeters = ('#MozRunner Prefs Start %s' % nonce,'#MozRunner Prefs End %s' % nonce) # Handle profile creation self.create_new = not profile if profile: # Ensure we have a full path to the profile self.profile = os.path.abspath(os.path.expanduser(profile)) if not os.path.exists(self.profile): os.makedirs(self.profile) else: self.profile = self.create_new_profile() # set preferences if hasattr(self.__class__, 'preferences'): # class preferences self.set_preferences(self.__class__.preferences) self._preferences = preferences if preferences: # supplied preferences if isinstance(preferences, dict): # unordered preferences = preferences.items() # sanity check assert not [i for i in preferences if len(i) != 2] else: preferences = [] self.set_preferences(preferences) # set permissions self._locations = locations # store this for reconstruction self._proxy = proxy self.permissions = Permissions(self.profile, locations) prefs_js, user_js = self.permissions.network_prefs(proxy) self.set_preferences(prefs_js, 'prefs.js') self.set_preferences(user_js) # handle addon installation self.addon_manager = AddonManager(self.profile, restore=self.restore) self.addon_manager.install_addons(addons, addon_manifests) # handle webapps self.webapps = WebappCollection(profile=self.profile, apps=apps) self.webapps.update_manifests()
def invoke(update, context, _): # Ignore unprivileged people who = Permissions.e_whois(update) if not Permissions.is_user(who): return # If /help admin was requested admin_mods = Permissions.admin_modules() if Permissions.is_admin and len(context.args) > 0: if context.args[0] == 'admin': help_admin(update, admin_mods) return # Get descriptions of the loaded modules that the user may invoke mods = set(Loader.loaded()) & set(Permissions.user_modules(who)) mods -= set(['start', 'logout']) mod_list = list(mods - admin_mods) if who != raw_whois(update): mod_list.append('logout') descs = ['/' + i + ': ' + Permissions.info(i) for i in mod_list] # Reply if Permissions.is_admin(who): descs.insert(0, 'Admin: /help admin for admin modules') reply(update, '@' + who + ' may use:' + djoin(descs))
def __init__(self, config_file): self.config_file = config_file self.config = json.load(open(self.config_file)) self.me = self.config["me"] self.net = self.config["network"] self.module_manager = ModuleManager(self) self.hook_manager = HookManager(self) self.perms = Permissions(self) self.connection = IRCConnection(self.net["address"], self.net["port"], self.net["ssl"], self.config["proxies"].get(self.net.get("proxy", "none"), None), self.net.get("flood_interval", 0.0)) self.running = True self.state = {} # Dict used to hold stuff like last line received and last message etc... self.db = Database("etc/buhirc.db") self.db.connect() logging.basicConfig(level=getattr(logging, self.config["misc"]["loglevel"]), format='[%(asctime)s] %(levelname)s: %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p') self.requests_session = requests.session() if self.config["misc"].get("http_proxy", "none") != "none": proxy = self.config["proxies"].get(self.config["misc"]["http_proxy"], "none") if proxy != "none": self.requests_session.proxies = {"http": proxy, "https": proxy} self.flood_verbs = [x.lower() for x in self.net.get("flood_verbs", [])] self.help = {}
def main(_): setup_logs() # For security if not is_in_container(): logging.error('Error: Not executing from container!') sys.exit(1) # Telegram API setup updater = None with open('token.priv') as f: updater = Updater(f.read().strip(), use_context=True) dp = updater.dispatcher # Create handler registration Permissions.setup(permisions_f) invoke_extra_args = { 'mmj_lic': ro_path, 'pubkey': ro_path, 'shopping': rw_path + '/shopping/', 'log': log_file } Loader.setup(dp, module_ordered_path, invoke_extra_args, unknown, error) # Load modules for mod in Permissions.modules(): try: Loader.load(mod) except ModuleNotFoundError as err: logging.warning(str(err)) # Start the Bot logging.info('Initiating polling.') updater.start_polling() # Block until you press Ctrl-C or the process receives SIGINT, SIGTERM or # SIGABRT. This should be used most of the time, since start_polling() is # non-blocking and will stop the bot gracefully. updater.idle()
def get_category_by_id(category_id): """ HTML endpoint providing details for a given category """ category = session.query(Category).filter_by(id=category_id).one() items = session.query(Item).filter_by(category_id=category_id).all() return UserUtils.render_user_template( 'category_items.html', category=category, items=items, page_title="%s Category" % category.name, can=Permissions.get_user_permissions_for_category(category))
def reset_modules(update, _): for i in list(Loader.loaded()): Loader.unload(i) reply(update, 'All modules unloaded. Loading modules.') success = [] for mod in Permissions.modules(): try: Loader.load(mod) success.append(mod) except ModuleNotFoundError as err: reply(update, str(err)) logging.warning(str(err)) if len(success) > 0: msg = 'Successfully loaded:' + djoin(success) reply(update, msg)
def get_item_by_id(category_id, item_id): """ HTML endpoint providing details for a given item within a category """ category = session.query(Category).filter_by(id=category_id).one() item = session.query(Item).filter_by(id=item_id).one() return UserUtils.render_user_template( 'item_read.html', category=category, category_id=category_id, item=item, page_title="%s Item" % item.title, can=Permissions.get_user_permissions_for_item(item))
def __init__(self, name='', permissions=None, store=None, emitter=None): self.name = name or 'universe' self.prefix = '' if name == 'universe' else name self.permissions = permissions or Permissions( path='fixtures/permissions.yaml') self.store = store or Records() self.emitter = emitter self.replay_stamp = None self.record_maximum_size = 1000000 self.page_size = 10 self.routes = [] self._add_url_rule('/', 'index', self.index) self._add_url_rule('/page/<token>', 'page', self.page) self._add_url_rule('/', 'post', self.post, methods=['POST']) self._add_url_rule('/<id>', 'get', self.get) self._add_url_rule('/<id>', 'put', self.put, methods=['PUT']) self._add_url_rule('/<id>', 'delete', self.delete, methods=['DELETE'])
def update_category_by_id(category_id): """ HTML endpoint providing a form to edit a category """ if not UserUtils.is_authenticated(): UserUtils.set_preauthentication_url() flash('sign in to edit categories') return redirect('/login') category = session.query(Category).filter_by(id=category_id).one() if not Permissions.get_user_permissions_for_category(category).update: flash('you may edit only categories you created') return redirect(url_for( 'get_categories')) if request.method == 'POST': # Extract and validate the form inputs (name, name_error) = \ extract_and_validate_category_name(request.form) if name_error: return UserUtils.render_user_template( 'category_update.html', category=category, page_title="%s %s Category" % ("Edit", category.name), name=name, name_error=name_error) # Create the item in the data store category.name = name session.add(category) session.commit() flash('category updated') return redirect(url_for( 'get_category_by_id', category_id=category_id)) else: return UserUtils.render_user_template( 'category_update.html', category=category, page_title="%s %s Category" % ("Edit", category.name), name=category.name)
def load(cls, module): assert module not in cls._installed, 'module already loaded' assert Permissions.is_protected(module), 'module not authorized' # Priority given to earlier dirs for dir_ in cls._ordered_dirs: try: cls._load(module, dir_) logging.info('Successfully loaded ' + module) # Only one module of this name peritted return except ModuleNotFoundError: pass except Exception as err: logging.error('Error loading ' + module + traceback.format_exc()) break msg = 'Load("' + module + '") failed' raise ModuleNotFoundError(msg)
def load_module(update, args, *, cmd='load'): mod = '' try: mod = args[0] Loader.load(mod) reply(update, mod + ' successfully loaded') return True except (AssertionError, IndexError) as e: msg = 'Usage: /module ' + cmd + ' <module>' unloaded = [ i for i in Permissions.modules() if i not in Loader.loaded() ] msg += '\nUnloaded modules:' + djoin(unloaded) reply(update, msg) return False except ModuleNotFoundError as err: logging.error('Failed to load module "' + mod + '" with error: ' + str(err)) reply(update, 'Failed to load module.') raise
def _install_handler(cls, module, install_me): # Secure the function and ensure dispatch termination when complete def packaged_fn(update, context): try: extra_args = cls._pass[module] if module in cls._pass else None install_me(update, context, extra_args) except DispatcherHandlerContinue: return except DispatcherHandlerStop: raise except Exception as err: msg = 'Error invoking ' + module + '.invoke: ' msg += traceback.format_exc() logging.error(msg) reply(update, 'Internal error.') raise DispatcherHandlerStop() cls._pre_protection_fns[module] = packaged_fn safe = Permissions.secure_module(module, packaged_fn) # Install the handler c_handler = CommandHandler(module, safe) cls._dp.add_handler(c_handler, cls._g) cls._installed[module] = c_handler
class Profile(object): """Handles all operations regarding profile. Created new profiles, installs extensions, sets preferences and handles cleanup.""" def __init__(self, profile=None, addons=None, addon_manifests=None, apps=None, preferences=None, locations=None, proxy=None, restore=True): """ :param profile: Path to the profile :param addons: String of one or list of addons to install :param addon_manifests: Manifest for addons, see http://ahal.ca/blog/2011/bulk-installing-fx-addons/ :param apps: Dictionary or class of webapps to install :param preferences: Dictionary or class of preferences :param locations: locations to proxy :param proxy: setup a proxy - dict of server-loc,server-port,ssl-port :param restore: If true remove all installed addons preferences when cleaning up """ # if true, remove installed addons/prefs afterwards self.restore = restore # prefs files written to self.written_prefs = set() # our magic markers nonce = '%s %s' % (str(time.time()), uuid.uuid4()) self.delimeters = ('#MozRunner Prefs Start %s' % nonce, '#MozRunner Prefs End %s' % nonce) # Handle profile creation self.create_new = not profile if profile: # Ensure we have a full path to the profile self.profile = os.path.abspath(os.path.expanduser(profile)) if not os.path.exists(self.profile): os.makedirs(self.profile) else: self.profile = self.create_new_profile() # set preferences if hasattr(self.__class__, 'preferences'): # class preferences self.set_preferences(self.__class__.preferences) self._preferences = preferences if preferences: # supplied preferences if isinstance(preferences, dict): # unordered preferences = preferences.items() # sanity check assert not [i for i in preferences if len(i) != 2] else: preferences = [] self.set_preferences(preferences) # set permissions self._locations = locations # store this for reconstruction self._proxy = proxy self.permissions = Permissions(self.profile, locations) prefs_js, user_js = self.permissions.network_prefs(proxy) self.set_preferences(prefs_js, 'prefs.js') self.set_preferences(user_js) # handle addon installation self.addon_manager = AddonManager(self.profile) self.addon_manager.install_addons(addons, addon_manifests) # handle webapps self.webapps = WebappCollection(profile=self.profile, apps=apps) self.webapps.update_manifests() def exists(self): """returns whether the profile exists or not""" return os.path.exists(self.profile) def reset(self): """ reset the profile to the beginning state """ self.cleanup() if self.create_new: profile = None else: profile = self.profile self.__init__(profile=profile, addons=self.addon_manager.installed_addons, addon_manifests=self.addon_manager.installed_manifests, preferences=self._preferences, locations=self._locations, proxy=self._proxy) @classmethod def clone(cls, path_from, path_to=None, **kwargs): """Instantiate a temporary profile via cloning - path: path of the basis to clone - kwargs: arguments to the profile constructor """ if not path_to: tempdir = tempfile.mkdtemp() # need an unused temp dir name rmtree(tempdir) # copytree requires that dest does not exist path_to = tempdir copytree(path_from, path_to) def cleanup_clone(fn): """Deletes a cloned profile when restore is True""" def wrapped(self): fn(self) if self.restore and os.path.exists(self.profile): rmtree(self.profile, onerror=self._cleanup_error) return wrapped c = cls(path_to, **kwargs) c.__del__ = c.cleanup = types.MethodType(cleanup_clone(cls.cleanup), c) return c def create_new_profile(self): """Create a new clean temporary profile which is a simple empty folder""" return tempfile.mkdtemp(suffix='.mozrunner') ### methods for preferences def set_preferences(self, preferences, filename='user.js'): """Adds preferences dict to profile preferences""" # append to the file prefs_file = os.path.join(self.profile, filename) f = open(prefs_file, 'a') if preferences: # note what files we've touched self.written_prefs.add(filename) # opening delimeter f.write('\n%s\n' % self.delimeters[0]) # write the preferences Preferences.write(f, preferences) # closing delimeter f.write('%s\n' % self.delimeters[1]) f.close() def pop_preferences(self, filename): """ pop the last set of preferences added returns True if popped """ lines = file(os.path.join(self.profile, filename)).read().splitlines() def last_index(_list, value): """ returns the last index of an item; this should actually be part of python code but it isn't """ for index in reversed(range(len(_list))): if _list[index] == value: return index s = last_index(lines, self.delimeters[0]) e = last_index(lines, self.delimeters[1]) # ensure both markers are found if s is None: assert e is None, '%s found without %s' % (self.delimeters[1], self.delimeters[0]) return False # no preferences found elif e is None: assert s is None, '%s found without %s' % (self.delimeters[0], self.delimeters[1]) # ensure the markers are in the proper order assert e > s, '%s found at %s, while %s found at %s' % ( self.delimeters[1], e, self.delimeters[0], s) # write the prefs cleaned_prefs = '\n'.join(lines[:s] + lines[e + 1:]) f = file(os.path.join(self.profile, 'user.js'), 'w') f.write(cleaned_prefs) f.close() return True def clean_preferences(self): """Removed preferences added by mozrunner.""" for filename in self.written_prefs: if not os.path.exists(os.path.join(self.profile, filename)): # file has been deleted break while True: if not self.pop_preferences(filename): break ### cleanup def _cleanup_error(self, function, path, excinfo): """ Specifically for windows we need to handle the case where the windows process has not yet relinquished handles on files, so we do a wait/try construct and timeout if we can't get a clear road to deletion """ try: from exceptions import WindowsError from time import sleep def is_file_locked(): return excinfo[0] is WindowsError and excinfo[1].winerror == 32 if excinfo[0] is WindowsError and excinfo[1].winerror == 32: # Then we're on windows, wait to see if the file gets unlocked # we wait 10s count = 0 while count < 10: sleep(1) try: function(path) break except: count += 1 except ImportError: # We can't re-raise an error, so we'll hope the stuff above us will throw pass def cleanup(self): """Cleanup operations for the profile.""" if self.restore: if self.create_new: if os.path.exists(self.profile): rmtree(self.profile, onerror=self._cleanup_error) else: self.clean_preferences() self.addon_manager.clean_addons() self.permissions.clean_db() self.webapps.clean() __del__ = cleanup
class Profile(object): """Handles all operations regarding profile. Created new profiles, installs extensions, sets preferences and handles cleanup.""" def __init__(self, profile=None, addons=None, addon_manifests=None, preferences=None, locations=None, proxy=None, restore=True): """ :param profile: Path to the profile :param addons: String of one or list of addons to install :param addon_manifests: Manifest for addons, see http://ahal.ca/blog/2011/bulk-installing-fx-addons/ :param preferences: Dictionary or class of preferences :param locations: locations to proxy :param proxy: setup a proxy - dict of server-loc,server-port,ssl-port :param restore: If true remove all installed addons preferences when cleaning up """ # if true, remove installed addons/prefs afterwards self.restore = restore # prefs files written to self.written_prefs = set() # our magic markers nonce = '%s %s' % (str(time.time()), uuid.uuid4()) self.delimeters = ('#MozRunner Prefs Start %s' % nonce,'#MozRunner Prefs End %s' % nonce) # Handle profile creation self.create_new = not profile if profile: # Ensure we have a full path to the profile self.profile = os.path.abspath(os.path.expanduser(profile)) if not os.path.exists(self.profile): os.makedirs(self.profile) else: self.profile = self.create_new_profile() # set preferences if hasattr(self.__class__, 'preferences'): # class preferences self.set_preferences(self.__class__.preferences) self._preferences = preferences if preferences: # supplied preferences if isinstance(preferences, dict): # unordered preferences = preferences.items() # sanity check assert not [i for i in preferences if len(i) != 2] else: preferences = [] self.set_preferences(preferences) # set permissions self._locations = locations # store this for reconstruction self._proxy = proxy self.permissions = Permissions(self.profile, locations) prefs_js, user_js = self.permissions.network_prefs(proxy) self.set_preferences(prefs_js, 'prefs.js') self.set_preferences(user_js) # handle addon installation self.addon_manager = AddonManager(self.profile) self.addon_manager.install_addons(addons, addon_manifests) def exists(self): """returns whether the profile exists or not""" return os.path.exists(self.profile) def reset(self): """ reset the profile to the beginning state """ self.cleanup() if self.create_new: profile = None else: profile = self.profile self.__init__(profile=profile, addons=self.addon_manager.installed_addons, addon_manifests=self.addon_manager.installed_manifests, preferences=self._preferences, locations=self._locations, proxy = self._proxy) def create_new_profile(self): """Create a new clean profile in tmp which is a simple empty folder""" profile = tempfile.mkdtemp(suffix='.mozrunner') return profile ### methods for preferences def set_preferences(self, preferences, filename='user.js'): """Adds preferences dict to profile preferences""" # append to the file prefs_file = os.path.join(self.profile, filename) f = open(prefs_file, 'a') if preferences: # note what files we've touched self.written_prefs.add(filename) if isinstance(preferences, dict): # order doesn't matter preferences = preferences.items() # write the preferences f.write('\n%s\n' % self.delimeters[0]) _prefs = [(json.dumps(k), json.dumps(v) ) for k, v in preferences] for _pref in _prefs: f.write('user_pref(%s, %s);\n' % _pref) f.write('%s\n' % self.delimeters[1]) f.close() def pop_preferences(self, filename): """ pop the last set of preferences added returns True if popped """ lines = file(os.path.join(self.profile, filename)).read().splitlines() def last_index(_list, value): """ returns the last index of an item; this should actually be part of python code but it isn't """ for index in reversed(range(len(_list))): if _list[index] == value: return index s = last_index(lines, self.delimeters[0]) e = last_index(lines, self.delimeters[1]) # ensure both markers are found if s is None: assert e is None, '%s found without %s' % (self.delimeters[1], self.delimeters[0]) return False # no preferences found elif e is None: assert s is None, '%s found without %s' % (self.delimeters[0], self.delimeters[1]) # ensure the markers are in the proper order assert e > s, '%s found at %s, while %s found at %s' % (self.delimeters[1], e, self.delimeters[0], s) # write the prefs cleaned_prefs = '\n'.join(lines[:s] + lines[e+1:]) f = file(os.path.join(self.profile, 'user.js'), 'w') f.write(cleaned_prefs) f.close() return True def clean_preferences(self): """Removed preferences added by mozrunner.""" for filename in self.written_prefs: if not os.path.exists(os.path.join(self.profile, filename)): # file has been deleted break while True: if not self.pop_preferences(filename): break ### cleanup def _cleanup_error(self, function, path, excinfo): """ Specifically for windows we need to handle the case where the windows process has not yet relinquished handles on files, so we do a wait/try construct and timeout if we can't get a clear road to deletion """ try: from exceptions import WindowsError from time import sleep def is_file_locked(): return excinfo[0] is WindowsError and excinfo[1].winerror == 32 if excinfo[0] is WindowsError and excinfo[1].winerror == 32: # Then we're on windows, wait to see if the file gets unlocked # we wait 10s count = 0 while count < 10: sleep(1) try: function(path) break except: count += 1 except ImportError: # We can't re-raise an error, so we'll hope the stuff above us will throw pass def cleanup(self): """Cleanup operations for the profile.""" if self.restore: if self.create_new: if os.path.exists(self.profile): rmtree(self.profile, onerror=self._cleanup_error) else: self.clean_preferences() self.addon_manager.clean_addons() self.permissions.clean_db() __del__ = cleanup
class Profile(object): """Handles all operations regarding profile. Creating new profiles, installing add-ons, setting preferences and handling cleanup. """ def __init__(self, profile=None, addons=None, addon_manifests=None, apps=None, preferences=None, locations=None, proxy=None, restore=True): """ :param profile: Path to the profile :param addons: String of one or list of addons to install :param addon_manifests: Manifest for addons (see http://bit.ly/17jQ7i6) :param apps: Dictionary or class of webapps to install :param preferences: Dictionary or class of preferences :param locations: ServerLocations object :param proxy: Setup a proxy :param restore: Flag for removing all custom settings during cleanup """ self._addons = addons self._addon_manifests = addon_manifests self._apps = apps self._locations = locations self._proxy = proxy # Prepare additional preferences if preferences: if isinstance(preferences, dict): # unordered preferences = preferences.items() # sanity check assert not [i for i in preferences if len(i) != 2] else: preferences = [] self._preferences = preferences # Handle profile creation self.create_new = not profile if profile: # Ensure we have a full path to the profile self.profile = os.path.abspath(os.path.expanduser(profile)) else: self.profile = tempfile.mkdtemp(suffix='.mozrunner') self.restore = restore # Initialize all class members self._internal_init() def _internal_init(self): """Internal: Initialize all class members to their default value""" if not os.path.exists(self.profile): os.makedirs(self.profile) # Preferences files written to self.written_prefs = set() # Our magic markers nonce = '%s %s' % (str(time.time()), uuid.uuid4()) self.delimeters = ('#MozRunner Prefs Start %s' % nonce, '#MozRunner Prefs End %s' % nonce) # If sub-classes want to set default preferences if hasattr(self.__class__, 'preferences'): self.set_preferences(self.__class__.preferences) # Set additional preferences self.set_preferences(self._preferences) self.permissions = Permissions(self.profile, self._locations) prefs_js, user_js = self.permissions.network_prefs(self._proxy) self.set_preferences(prefs_js, 'prefs.js') self.set_preferences(user_js) # handle add-on installation self.addon_manager = AddonManager(self.profile, restore=self.restore) self.addon_manager.install_addons(self._addons, self._addon_manifests) # handle webapps self.webapps = WebappCollection(profile=self.profile, apps=self._apps) self.webapps.update_manifests() def __del__(self): self.cleanup() ### cleanup def cleanup(self): """Cleanup operations for the profile.""" if self.restore: # If copies of those class instances exist ensure we correctly # reset them all (see bug 934484) self.clean_preferences() if getattr(self, 'addon_manager', None) is not None: self.addon_manager.clean() if getattr(self, 'permissions', None) is not None: self.permissions.clean_db() if getattr(self, 'webapps', None) is not None: self.webapps.clean() # If it's a temporary profile we have to remove it if self.create_new: mozfile.remove(self.profile) def reset(self): """ reset the profile to the beginning state """ self.cleanup() self._internal_init() def clean_preferences(self): """Removed preferences added by mozrunner.""" for filename in self.written_prefs: if not os.path.exists(os.path.join(self.profile, filename)): # file has been deleted break while True: if not self.pop_preferences(filename): break @classmethod def clone(cls, path_from, path_to=None, **kwargs): """Instantiate a temporary profile via cloning - path: path of the basis to clone - kwargs: arguments to the profile constructor """ if not path_to: tempdir = tempfile.mkdtemp() # need an unused temp dir name mozfile.remove( tempdir) # copytree requires that dest does not exist path_to = tempdir copytree(path_from, path_to) def cleanup_clone(fn): """Deletes a cloned profile when restore is True""" def wrapped(self): fn(self) if self.restore and os.path.exists(self.profile): mozfile.remove(self.profile) return wrapped c = cls(path_to, **kwargs) c.__del__ = c.cleanup = types.MethodType(cleanup_clone(cls.cleanup), c) return c def exists(self): """returns whether the profile exists or not""" return os.path.exists(self.profile) ### methods for preferences def set_preferences(self, preferences, filename='user.js'): """Adds preferences dict to profile preferences""" # append to the file prefs_file = os.path.join(self.profile, filename) f = open(prefs_file, 'a') if preferences: # note what files we've touched self.written_prefs.add(filename) # opening delimeter f.write('\n%s\n' % self.delimeters[0]) # write the preferences Preferences.write(f, preferences) # closing delimeter f.write('%s\n' % self.delimeters[1]) f.close() def set_persistent_preferences(self, preferences): """ Adds preferences dict to profile preferences and save them during a profile reset """ # this is a dict sometimes, convert if isinstance(preferences, dict): preferences = preferences.items() # add new prefs to preserve them during reset for new_pref in preferences: # if dupe remove item from original list self._preferences = [ pref for pref in self._preferences if not new_pref[0] == pref[0] ] self._preferences.append(new_pref) self.set_preferences(preferences, filename='user.js') def pop_preferences(self, filename): """ pop the last set of preferences added returns True if popped """ path = os.path.join(self.profile, filename) with file(path) as f: lines = f.read().splitlines() def last_index(_list, value): """ returns the last index of an item; this should actually be part of python code but it isn't """ for index in reversed(range(len(_list))): if _list[index] == value: return index s = last_index(lines, self.delimeters[0]) e = last_index(lines, self.delimeters[1]) # ensure both markers are found if s is None: assert e is None, '%s found without %s' % (self.delimeters[1], self.delimeters[0]) return False # no preferences found elif e is None: assert s is None, '%s found without %s' % (self.delimeters[0], self.delimeters[1]) # ensure the markers are in the proper order assert e > s, '%s found at %s, while %s found at %s' % ( self.delimeters[1], e, self.delimeters[0], s) # write the prefs cleaned_prefs = '\n'.join(lines[:s] + lines[e + 1:]) with file(path, 'w') as f: f.write(cleaned_prefs) return True ### methods for introspection def summary(self, return_parts=False): """ returns string summarizing profile information. if return_parts is true, return the (Part_name, value) list of tuples instead of the assembled string """ parts = [('Path', self.profile)] # profile path # directory tree parts.append(('Files', '\n%s' % mozfile.tree(self.profile))) # preferences for prefs_file in ('user.js', 'prefs.js'): path = os.path.join(self.profile, prefs_file) if os.path.exists(path): # prefs that get their own section # This is currently only 'network.proxy.autoconfig_url' # but could be expanded to include others section_prefs = ['network.proxy.autoconfig_url'] line_length = 80 line_length_buffer = 10 # buffer for 80 character display: length = 80 - len(key) - len(': ') - line_length_buffer line_length_buffer += len(': ') def format_value(key, value): if key not in section_prefs: return value max_length = line_length - len(key) - line_length_buffer if len(value) > max_length: value = '%s...' % value[:max_length] return value prefs = Preferences.read_prefs(path) if prefs: prefs = dict(prefs) parts.append((prefs_file, '\n%s' % ('\n'.join([ '%s: %s' % (key, format_value(key, prefs[key])) for key in sorted(prefs.keys()) ])))) # Currently hardcorded to 'network.proxy.autoconfig_url' # but could be generalized, possibly with a generalized (simple) # JS-parser network_proxy_autoconfig = prefs.get( 'network.proxy.autoconfig_url') if network_proxy_autoconfig and network_proxy_autoconfig.strip( ): network_proxy_autoconfig = network_proxy_autoconfig.strip( ) lines = network_proxy_autoconfig.replace( ';', ';\n').splitlines() lines = [line.strip() for line in lines] origins_string = 'var origins = [' origins_end = '];' if origins_string in lines[0]: start = lines[0].find(origins_string) end = lines[0].find(origins_end, start) splitline = [ lines[0][:start], lines[0][start:start + len(origins_string) - 1], ] splitline.extend( lines[0][start + len(origins_string):end].replace( ',', ',\n').splitlines()) splitline.append(lines[0][end:]) lines[0:1] = [i.strip() for i in splitline] parts.append( ('Network Proxy Autoconfig, %s' % (prefs_file), '\n%s' % '\n'.join(lines))) if return_parts: return parts retval = '%s\n' % ('\n\n'.join( ['[%s]: %s' % (key, value) for key, value in parts])) return retval __str__ = summary
def test_load_file(): store = Permissions() store.load(open('fixtures/permissions.yaml', 'r')) assert store.authorize('access-content', 'x', 'universe') is False assert store.authorize('access-content', 'x', 'community') is False assert store.authorize('access-content', 'x', 'board') is False assert store.authorize('access-content', 'leader', 'universe') is True assert store.authorize('access-content', 'leader', 'community') is True assert store.authorize('access-content', 'leader', 'board') is True assert store.authorize('manage-content', 'member', 'universe') is False assert store.authorize('manage-content', 'member', 'community') is False assert store.authorize('manage-content', 'member', 'board') is False assert store.authorize('manage-content', 'leader', 'universe') is True assert store.authorize('manage-content', 'leader', 'community') is True assert store.authorize('manage-content', 'leader', 'board') is True assert store.authorize('manage-identities', 'leader', 'member') is False assert store.authorize('manage-identities', 'leader', 'leader') is False assert store.authorize('manage-identities', 'leader', 'support') is False assert store.authorize('manage-identities', 'leader', 'robot') is False assert store.authorize('manage-identities', 'support', 'member') is True assert store.authorize('manage-identities', 'support', 'leader') is True assert store.authorize('manage-identities', 'support', 'support') is False assert store.authorize('manage-identities', 'support', 'robot') is True assert store.authorize('manage-identities', 'leader', 'update-any-to-registered') is True assert store.authorize('manage-identities', 'leader', 'update-any-to-leader') is True assert store.authorize('manage-identities', 'leader', 'update-any-to-support') is False assert store.authorize('manage-identities', 'leader', 'update-any- to-robot') is False
def test_init(): permissions = Permissions() assert permissions.count() == 0
def test_grant_all(): scopes = ['alpha', 'beta', 'gamma'] items = [ dict(topic='with_list', personas=['a', 'b', 'c']), dict(topic='with_item', persona='a'), ] permissions = Permissions(authorized_scopes=scopes) for scope in scopes: # nothing is authorized assert permissions.authorize(scope, 'a', 'with_list') is False assert permissions.authorize(scope, 'b', 'with_list') is False assert permissions.authorize(scope, 'c', 'with_list') is False assert permissions.authorize(scope, 'x', 'with_list') is False assert permissions.authorize(scope, 'a', 'with_item') is False assert permissions.authorize(scope, 'x', 'with_item') is False for scope in scopes: # set permissions permissions.grant_all(scope, items) for scope in scopes: # some actions are authorized assert permissions.authorize(scope, 'a', 'with_list') is True assert permissions.authorize(scope, 'b', 'with_list') is True assert permissions.authorize(scope, 'c', 'with_list') is True assert permissions.authorize(scope, 'x', 'with_list') is False assert permissions.authorize(scope, 'a', 'with_item') is True assert permissions.authorize(scope, 'x', 'with_item') is False with py_raises(ValueError) as error: permissions.grant_all(scope='*alien*', items=items)
class BotState(object): def __init__(self, bot): self.bot = bot self.channels_by_id = {} self.users_by_session = {} self.users_by_id = {} self.root = None self.permissions = None # The user object representing the bot. self.user = None # The current channel the bot is in. self.channel = None def get_actor(self, session_id): if not session_id in self.users_by_session: LOGGER.warning('Invalid session ID: %d.' % session_id) return None else: return self.users_by_session[session_id] def get_channel(self, chan_id): if not chan_id in self.channels_by_id: LOGGER.warning('Invalid Channel ID: %d.' % chan_id) return None else: return self.channels_by_id[chan_id] def on_version(self, msg): self.version = msg.version self.release = msg.release self.os = msg.os self.os_version = msg.os_version def on_voice_ping(self, session_id): self.bot.on_voice_ping(session_id) def on_voice_talk(self, from_id, sequence, data): self.bot.on_voice_talk(self.get_actor(from_id), sequence, data) def on_voice_whisper_chan(self, from_id, sequence, data): pass def on_voice_whisper_self(self, from_id, sequence, data): pass def on_pingback(self, ping_msec, msg): self.ping = ping_msec self.packet_stats = (msg.good, msg.late, msg.lost) self.udp_stats = (msg.udp_packets, msg.udp_ping_avg, msg.udp_ping_var) def on_reject(self, msg): self.rejected = True self.reject_type = msg.type self.reject_reason = msg.reason self.bot.rejected() def on_server_config(self, msg): self.welcome_text = msg.welcome_text self.allow_html = msg.allow_html def on_server_sync(self, msg): self.max_bandwidth = msg.max_bandwidth self.welcome_text = msg.welcome_text if msg.permissions: if not self.permissions: self.permissions = Permissions(msg.permissions) else: self.permissions.update(msg.permissions) self.bot.connected() def on_channel_state(self, msg): if msg.channel_id not in self.channels_by_id: chan = Channel(self.bot, msg.channel_id) self.channels_by_id[msg.channel_id] = chan else: chan = self.channels_by_id[msg.channel_id] chan.update(msg) if msg.parent == msg.channel_id: if not msg.channel_id == 0: LOGGER.warning('Root channel not ID 0.') if self.root and self.root != chan: LOGGER.error('Received 2 different roots...?') raise Exception('Two roots.') self.root = chan elif chan.parent: if chan.parent.id != msg.parent: chan.parent.remove_child(chan) self.channels_by_id[msg.parent].add_child(chan) else: if not msg.parent in self.channels_by_id: LOGGER.error('Parent ID passed by server is not in the channel list.') raise Exception('Invalid Parent.') self.channels_by_id[msg.parent].add_child(chan) def on_user_state(self, msg): if msg.session not in self.users_by_session: user = User(self.bot, msg.session) self.users_by_session[msg.session] = user if msg.user_id is not None: self.users_by_id[msg.user_id] = user else: user = self.users_by_session[msg.session] user.update(msg) if self.user is None: self.user = user def on_text_message(self, msg): self.bot.on_text_message(from_user = self.get_actor(msg.actor), to_users = [self.get_actor(x) for x in msg.session], to_channels = [self.get_channel(x) for x in msg.channel_id], tree_ids = msg.tree_id, message = "%s" % msg.message) def on_crypt_setup(self, msg): pass def on_user_stats(self, msg): assert msg.session in self.users_by_session self.users_by_session[msg.session].update_stats(msg) def on_unknown(self, type, msg): LOGGER.warning("Unknown message received: type(%s), '%s'" % (type, msg))
class Profile(object): """Handles all operations regarding profile. Creating new profiles, installing add-ons, setting preferences and handling cleanup. The files associated with the profile will be removed automatically after the object is garbage collected: :: profile = Profile() print profile.profile # this is the path to the created profile del profile # the profile path has been removed from disk :meth:`cleanup` is called under the hood to remove the profile files. You can ensure this method is called (even in the case of exception) by using the profile as a context manager: :: with Profile() as profile: # do things with the profile pass # profile.cleanup() has been called here """ def __init__(self, profile=None, addons=None, addon_manifests=None, preferences=None, locations=None, proxy=None, restore=True): """ :param profile: Path to the profile :param addons: String of one or list of addons to install :param addon_manifests: Manifest for addons (see http://bit.ly/17jQ7i6) :param preferences: Dictionary or class of preferences :param locations: ServerLocations object :param proxy: Setup a proxy :param restore: Flag for removing all custom settings during cleanup """ self._addons = addons self._addon_manifests = addon_manifests self._locations = locations self._proxy = proxy # Prepare additional preferences if preferences: if isinstance(preferences, dict): # unordered preferences = preferences.items() # sanity check assert not [i for i in preferences if len(i) != 2] else: preferences = [] self._preferences = preferences # Handle profile creation self.create_new = not profile if profile: # Ensure we have a full path to the profile self.profile = os.path.abspath(os.path.expanduser(profile)) else: self.profile = tempfile.mkdtemp(suffix='.mozrunner') self.restore = restore # Initialize all class members self._internal_init() def _internal_init(self): """Internal: Initialize all class members to their default value""" if not os.path.exists(self.profile): os.makedirs(self.profile) # Preferences files written to self.written_prefs = set() # Our magic markers nonce = '%s %s' % (str(time.time()), uuid.uuid4()) self.delimeters = ('#MozRunner Prefs Start %s' % nonce, '#MozRunner Prefs End %s' % nonce) # If sub-classes want to set default preferences if hasattr(self.__class__, 'preferences'): self.set_preferences(self.__class__.preferences) # Set additional preferences self.set_preferences(self._preferences) self.permissions = Permissions(self.profile, self._locations) prefs_js, user_js = self.permissions.network_prefs(self._proxy) self.set_preferences(prefs_js, 'prefs.js') self.set_preferences(user_js) # handle add-on installation self.addon_manager = AddonManager(self.profile, restore=self.restore) self.addon_manager.install_addons(self._addons, self._addon_manifests) def __enter__(self): return self def __exit__(self, type, value, traceback): self.cleanup() def __del__(self): self.cleanup() # cleanup def cleanup(self): """Cleanup operations for the profile.""" if self.restore: # If copies of those class instances exist ensure we correctly # reset them all (see bug 934484) self.clean_preferences() if getattr(self, 'addon_manager', None) is not None: self.addon_manager.clean() if getattr(self, 'permissions', None) is not None: self.permissions.clean_db() # If it's a temporary profile we have to remove it if self.create_new: mozfile.remove(self.profile) def reset(self): """ reset the profile to the beginning state """ self.cleanup() self._internal_init() def clean_preferences(self): """Removed preferences added by mozrunner.""" for filename in self.written_prefs: if not os.path.exists(os.path.join(self.profile, filename)): # file has been deleted break while True: if not self.pop_preferences(filename): break @classmethod def clone(cls, path_from, path_to=None, **kwargs): """Instantiate a temporary profile via cloning - path: path of the basis to clone - kwargs: arguments to the profile constructor """ if not path_to: tempdir = tempfile.mkdtemp() # need an unused temp dir name mozfile.remove(tempdir) # copytree requires that dest does not exist path_to = tempdir copytree(path_from, path_to) c = cls(path_to, **kwargs) c.create_new = True # deletes a cloned profile when restore is True return c def exists(self): """returns whether the profile exists or not""" return os.path.exists(self.profile) # methods for preferences def set_preferences(self, preferences, filename='user.js'): """Adds preferences dict to profile preferences""" # append to the file prefs_file = os.path.join(self.profile, filename) f = open(prefs_file, 'a') if preferences: # note what files we've touched self.written_prefs.add(filename) # opening delimeter f.write('\n%s\n' % self.delimeters[0]) # write the preferences Preferences.write(f, preferences) # closing delimeter f.write('%s\n' % self.delimeters[1]) f.close() def set_persistent_preferences(self, preferences): """ Adds preferences dict to profile preferences and save them during a profile reset """ # this is a dict sometimes, convert if isinstance(preferences, dict): preferences = preferences.items() # add new prefs to preserve them during reset for new_pref in preferences: # if dupe remove item from original list self._preferences = [ pref for pref in self._preferences if not new_pref[0] == pref[0]] self._preferences.append(new_pref) self.set_preferences(preferences, filename='user.js') def pop_preferences(self, filename): """ pop the last set of preferences added returns True if popped """ path = os.path.join(self.profile, filename) with file(path) as f: lines = f.read().splitlines() def last_index(_list, value): """ returns the last index of an item; this should actually be part of python code but it isn't """ for index in reversed(range(len(_list))): if _list[index] == value: return index s = last_index(lines, self.delimeters[0]) e = last_index(lines, self.delimeters[1]) # ensure both markers are found if s is None: assert e is None, '%s found without %s' % (self.delimeters[1], self.delimeters[0]) return False # no preferences found elif e is None: assert s is None, '%s found without %s' % (self.delimeters[0], self.delimeters[1]) # ensure the markers are in the proper order assert e > s, '%s found at %s, while %s found at %s' % (self.delimeters[1], e, self.delimeters[0], s) # write the prefs cleaned_prefs = '\n'.join(lines[:s] + lines[e + 1:]) with file(path, 'w') as f: f.write(cleaned_prefs) return True # methods for introspection def summary(self, return_parts=False): """ returns string summarizing profile information. if return_parts is true, return the (Part_name, value) list of tuples instead of the assembled string """ parts = [('Path', self.profile)] # profile path # directory tree parts.append(('Files', '\n%s' % mozfile.tree(self.profile))) # preferences for prefs_file in ('user.js', 'prefs.js'): path = os.path.join(self.profile, prefs_file) if os.path.exists(path): # prefs that get their own section # This is currently only 'network.proxy.autoconfig_url' # but could be expanded to include others section_prefs = ['network.proxy.autoconfig_url'] line_length = 80 # buffer for 80 character display: # length = 80 - len(key) - len(': ') - line_length_buffer line_length_buffer = 10 line_length_buffer += len(': ') def format_value(key, value): if key not in section_prefs: return value max_length = line_length - len(key) - line_length_buffer if len(value) > max_length: value = '%s...' % value[:max_length] return value prefs = Preferences.read_prefs(path) if prefs: prefs = dict(prefs) parts.append((prefs_file, '\n%s' % ('\n'.join( ['%s: %s' % (key, format_value(key, prefs[key])) for key in sorted(prefs.keys())])))) # Currently hardcorded to 'network.proxy.autoconfig_url' # but could be generalized, possibly with a generalized (simple) # JS-parser network_proxy_autoconfig = prefs.get('network.proxy.autoconfig_url') if network_proxy_autoconfig and network_proxy_autoconfig.strip(): network_proxy_autoconfig = network_proxy_autoconfig.strip() lines = network_proxy_autoconfig.replace(';', ';\n').splitlines() lines = [line.strip() for line in lines] origins_string = 'var origins = [' origins_end = '];' if origins_string in lines[0]: start = lines[0].find(origins_string) end = lines[0].find(origins_end, start) splitline = [lines[0][:start], lines[0][start:start + len(origins_string) - 1], ] splitline.extend(lines[0][start + len(origins_string):end].replace( ',', ',\n').splitlines()) splitline.append(lines[0][end:]) lines[0:1] = [i.strip() for i in splitline] parts.append(('Network Proxy Autoconfig, %s' % (prefs_file), '\n%s' % '\n'.join(lines))) if return_parts: return parts retval = '%s\n' % ('\n\n'.join(['[%s]: %s' % (key, value) for key, value in parts])) return retval __str__ = summary
class Profile(object): """Handles all operations regarding profile. Creating new profiles, installing add-ons, setting preferences and handling cleanup. """ def __init__( self, profile=None, addons=None, addon_manifests=None, apps=None, preferences=None, locations=None, proxy=None, restore=True, ): """ :param profile: Path to the profile :param addons: String of one or list of addons to install :param addon_manifests: Manifest for addons (see http://bit.ly/17jQ7i6) :param apps: Dictionary or class of webapps to install :param preferences: Dictionary or class of preferences :param locations: ServerLocations object :param proxy: Setup a proxy :param restore: Flag for removing all custom settings during cleanup """ self._addons = addons self._addon_manifests = addon_manifests self._apps = apps self._locations = locations self._proxy = proxy # Prepare additional preferences if preferences: if isinstance(preferences, dict): # unordered preferences = preferences.items() # sanity check assert not [i for i in preferences if len(i) != 2] else: preferences = [] self._preferences = preferences # Handle profile creation self.create_new = not profile if profile: # Ensure we have a full path to the profile self.profile = os.path.abspath(os.path.expanduser(profile)) else: self.profile = tempfile.mkdtemp(suffix=".mozrunner") self.restore = restore # Initialize all class members self._internal_init() def _internal_init(self): """Internal: Initialize all class members to their default value""" if not os.path.exists(self.profile): os.makedirs(self.profile) # Preferences files written to self.written_prefs = set() # Our magic markers nonce = "%s %s" % (str(time.time()), uuid.uuid4()) self.delimeters = ("#MozRunner Prefs Start %s" % nonce, "#MozRunner Prefs End %s" % nonce) # If sub-classes want to set default preferences if hasattr(self.__class__, "preferences"): self.set_preferences(self.__class__.preferences) # Set additional preferences self.set_preferences(self._preferences) self.permissions = Permissions(self.profile, self._locations) prefs_js, user_js = self.permissions.network_prefs(self._proxy) self.set_preferences(prefs_js, "prefs.js") self.set_preferences(user_js) # handle add-on installation self.addon_manager = AddonManager(self.profile, restore=self.restore) self.addon_manager.install_addons(self._addons, self._addon_manifests) # handle webapps self.webapps = WebappCollection(profile=self.profile, apps=self._apps) self.webapps.update_manifests() def __del__(self): self.cleanup() ### cleanup def cleanup(self): """Cleanup operations for the profile.""" if self.restore: # If copies of those class instances exist ensure we correctly # reset them all (see bug 934484) self.clean_preferences() if getattr(self, "addon_manager", None) is not None: self.addon_manager.clean() if getattr(self, "permissions", None) is not None: self.permissions.clean_db() if getattr(self, "webapps", None) is not None: self.webapps.clean() # If it's a temporary profile we have to remove it if self.create_new: mozfile.remove(self.profile) def reset(self): """ reset the profile to the beginning state """ self.cleanup() self._internal_init() def clean_preferences(self): """Removed preferences added by mozrunner.""" for filename in self.written_prefs: if not os.path.exists(os.path.join(self.profile, filename)): # file has been deleted break while True: if not self.pop_preferences(filename): break @classmethod def clone(cls, path_from, path_to=None, **kwargs): """Instantiate a temporary profile via cloning - path: path of the basis to clone - kwargs: arguments to the profile constructor """ if not path_to: tempdir = tempfile.mkdtemp() # need an unused temp dir name mozfile.remove(tempdir) # copytree requires that dest does not exist path_to = tempdir copytree(path_from, path_to) def cleanup_clone(fn): """Deletes a cloned profile when restore is True""" def wrapped(self): fn(self) if self.restore and os.path.exists(self.profile): mozfile.remove(self.profile) return wrapped c = cls(path_to, **kwargs) c.__del__ = c.cleanup = types.MethodType(cleanup_clone(cls.cleanup), c) return c def exists(self): """returns whether the profile exists or not""" return os.path.exists(self.profile) ### methods for preferences def set_preferences(self, preferences, filename="user.js"): """Adds preferences dict to profile preferences""" # append to the file prefs_file = os.path.join(self.profile, filename) f = open(prefs_file, "a") if preferences: # note what files we've touched self.written_prefs.add(filename) # opening delimeter f.write("\n%s\n" % self.delimeters[0]) # write the preferences Preferences.write(f, preferences) # closing delimeter f.write("%s\n" % self.delimeters[1]) f.close() def pop_preferences(self, filename): """ pop the last set of preferences added returns True if popped """ path = os.path.join(self.profile, filename) with file(path) as f: lines = f.read().splitlines() def last_index(_list, value): """ returns the last index of an item; this should actually be part of python code but it isn't """ for index in reversed(range(len(_list))): if _list[index] == value: return index s = last_index(lines, self.delimeters[0]) e = last_index(lines, self.delimeters[1]) # ensure both markers are found if s is None: assert e is None, "%s found without %s" % (self.delimeters[1], self.delimeters[0]) return False # no preferences found elif e is None: assert s is None, "%s found without %s" % (self.delimeters[0], self.delimeters[1]) # ensure the markers are in the proper order assert e > s, "%s found at %s, while %s found at %s" % (self.delimeters[1], e, self.delimeters[0], s) # write the prefs cleaned_prefs = "\n".join(lines[:s] + lines[e + 1 :]) with file(path, "w") as f: f.write(cleaned_prefs) return True ### methods for introspection def summary(self, return_parts=False): """ returns string summarizing profile information. if return_parts is true, return the (Part_name, value) list of tuples instead of the assembled string """ parts = [("Path", self.profile)] # profile path # directory tree parts.append(("Files", "\n%s" % mozfile.tree(self.profile))) # preferences for prefs_file in ("user.js", "prefs.js"): path = os.path.join(self.profile, prefs_file) if os.path.exists(path): # prefs that get their own section # This is currently only 'network.proxy.autoconfig_url' # but could be expanded to include others section_prefs = ["network.proxy.autoconfig_url"] line_length = 80 line_length_buffer = ( 10 ) # buffer for 80 character display: length = 80 - len(key) - len(': ') - line_length_buffer line_length_buffer += len(": ") def format_value(key, value): if key not in section_prefs: return value max_length = line_length - len(key) - line_length_buffer if len(value) > max_length: value = "%s..." % value[:max_length] return value prefs = Preferences.read_prefs(path) if prefs: prefs = dict(prefs) parts.append( ( prefs_file, "\n%s" % ( "\n".join( ["%s: %s" % (key, format_value(key, prefs[key])) for key in sorted(prefs.keys())] ) ), ) ) # Currently hardcorded to 'network.proxy.autoconfig_url' # but could be generalized, possibly with a generalized (simple) # JS-parser network_proxy_autoconfig = prefs.get("network.proxy.autoconfig_url") if network_proxy_autoconfig and network_proxy_autoconfig.strip(): network_proxy_autoconfig = network_proxy_autoconfig.strip() lines = network_proxy_autoconfig.replace(";", ";\n").splitlines() lines = [line.strip() for line in lines] origins_string = "var origins = [" origins_end = "];" if origins_string in lines[0]: start = lines[0].find(origins_string) end = lines[0].find(origins_end, start) splitline = [lines[0][:start], lines[0][start : start + len(origins_string) - 1]] splitline.extend( lines[0][start + len(origins_string) : end].replace(",", ",\n").splitlines() ) splitline.append(lines[0][end:]) lines[0:1] = [i.strip() for i in splitline] parts.append(("Network Proxy Autoconfig, %s" % (prefs_file), "\n%s" % "\n".join(lines))) if return_parts: return parts retval = "%s\n" % ("\n\n".join(["[%s]: %s" % (key, value) for key, value in parts])) return retval __str__ = summary
class Profile(object): """Handles all operations regarding profile. Creating new profiles, installing add-ons, setting preferences and handling cleanup. """ def __init__(self, profile=None, addons=None, addon_manifests=None, apps=None, preferences=None, locations=None, proxy=None, restore=True): """ :param profile: Path to the profile :param addons: String of one or list of addons to install :param addon_manifests: Manifest for addons (see http://bit.ly/17jQ7i6) :param apps: Dictionary or class of webapps to install :param preferences: Dictionary or class of preferences :param locations: ServerLocations object :param proxy: Setup a proxy :param restore: Flag for removing all custom settings during cleanup """ self._addons = addons self._addon_manifests = addon_manifests self._apps = apps self._locations = locations self._proxy = proxy # Prepare additional preferences if preferences: if isinstance(preferences, dict): # unordered preferences = preferences.items() # sanity check assert not [i for i in preferences if len(i) != 2] else: preferences = [] self._preferences = preferences # Handle profile creation self.create_new = not profile if profile: # Ensure we have a full path to the profile self.profile = os.path.abspath(os.path.expanduser(profile)) else: self.profile = tempfile.mkdtemp(suffix='.mozrunner') self.restore = restore # Initialize all class members self._internal_init() def _internal_init(self): """Internal: Initialize all class members to their default value""" if not os.path.exists(self.profile): os.makedirs(self.profile) # Preferences files written to self.written_prefs = set() # Our magic markers nonce = '%s %s' % (str(time.time()), uuid.uuid4()) self.delimeters = ('#MozRunner Prefs Start %s' % nonce, '#MozRunner Prefs End %s' % nonce) # If sub-classes want to set default preferences if hasattr(self.__class__, 'preferences'): self.set_preferences(self.__class__.preferences) # Set additional preferences self.set_preferences(self._preferences) self.permissions = Permissions(self.profile, self._locations) prefs_js, user_js = self.permissions.network_prefs(self._proxy) self.set_preferences(prefs_js, 'prefs.js') self.set_preferences(user_js) # handle add-on installation self.addon_manager = AddonManager(self.profile, restore=self.restore) self.addon_manager.install_addons(self._addons, self._addon_manifests) # handle webapps self.webapps = WebappCollection(profile=self.profile, apps=self._apps) self.webapps.update_manifests() def __del__(self): self.cleanup() ### cleanup def cleanup(self): """Cleanup operations for the profile.""" if self.restore: # If copies of those class instances exist ensure we correctly # reset them all (see bug 934484) self.addon_manager.clean() self.clean_preferences() self.permissions.clean_db() self.webapps.clean() # If it's a temporary profile we have to remove it if self.create_new: if os.path.exists(self.profile): rmtree(self.profile, onerror=self._cleanup_error) def reset(self): """ reset the profile to the beginning state """ self.cleanup() self._internal_init() def _cleanup_error(self, function, path, excinfo): """ Specifically for windows we need to handle the case where the windows process has not yet relinquished handles on files, so we do a wait/try construct and timeout if we can't get a clear road to deletion """ try: from exceptions import WindowsError from time import sleep def is_file_locked(): return excinfo[0] is WindowsError and excinfo[1].winerror == 32 if excinfo[0] is WindowsError and excinfo[1].winerror == 32: # Then we're on windows, wait to see if the file gets unlocked # we wait 10s count = 0 while count < 10: sleep(1) try: function(path) break except: count += 1 except ImportError: # We can't re-raise an error, so we'll hope the stuff above us will throw pass def clean_preferences(self): """Removed preferences added by mozrunner.""" for filename in self.written_prefs: if not os.path.exists(os.path.join(self.profile, filename)): # file has been deleted break while True: if not self.pop_preferences(filename): break @classmethod def clone(cls, path_from, path_to=None, **kwargs): """Instantiate a temporary profile via cloning - path: path of the basis to clone - kwargs: arguments to the profile constructor """ if not path_to: tempdir = tempfile.mkdtemp() # need an unused temp dir name rmtree(tempdir) # copytree requires that dest does not exist path_to = tempdir copytree(path_from, path_to) def cleanup_clone(fn): """Deletes a cloned profile when restore is True""" def wrapped(self): fn(self) if self.restore and os.path.exists(self.profile): rmtree(self.profile, onerror=self._cleanup_error) return wrapped c = cls(path_to, **kwargs) c.__del__ = c.cleanup = types.MethodType(cleanup_clone(cls.cleanup), c) return c def exists(self): """returns whether the profile exists or not""" return os.path.exists(self.profile) ### methods for preferences def set_preferences(self, preferences, filename='user.js'): """Adds preferences dict to profile preferences""" # append to the file prefs_file = os.path.join(self.profile, filename) f = open(prefs_file, 'a') if preferences: # note what files we've touched self.written_prefs.add(filename) # opening delimeter f.write('\n%s\n' % self.delimeters[0]) # write the preferences Preferences.write(f, preferences) # closing delimeter f.write('%s\n' % self.delimeters[1]) f.close() def pop_preferences(self, filename): """ pop the last set of preferences added returns True if popped """ path = os.path.join(self.profile, filename) with file(path) as f: lines = f.read().splitlines() def last_index(_list, value): """ returns the last index of an item; this should actually be part of python code but it isn't """ for index in reversed(range(len(_list))): if _list[index] == value: return index s = last_index(lines, self.delimeters[0]) e = last_index(lines, self.delimeters[1]) # ensure both markers are found if s is None: assert e is None, '%s found without %s' % (self.delimeters[1], self.delimeters[0]) return False # no preferences found elif e is None: assert s is None, '%s found without %s' % (self.delimeters[0], self.delimeters[1]) # ensure the markers are in the proper order assert e > s, '%s found at %s, while %s found at %s' % (self.delimeters[1], e, self.delimeters[0], s) # write the prefs cleaned_prefs = '\n'.join(lines[:s] + lines[e+1:]) with file(path, 'w') as f: f.write(cleaned_prefs) return True ### methods for introspection def summary(self, return_parts=False): """ returns string summarizing profile information. if return_parts is true, return the (Part_name, value) list of tuples instead of the assembled string """ parts = [('Path', self.profile)] # profile path # directory tree parts.append(('Files', '\n%s' % tree(self.profile))) # preferences for prefs_file in ('user.js', 'prefs.js'): path = os.path.join(self.profile, prefs_file) if os.path.exists(path): # prefs that get their own section # This is currently only 'network.proxy.autoconfig_url' # but could be expanded to include others section_prefs = ['network.proxy.autoconfig_url'] line_length = 80 line_length_buffer = 10 # buffer for 80 character display: length = 80 - len(key) - len(': ') - line_length_buffer line_length_buffer += len(': ') def format_value(key, value): if key not in section_prefs: return value max_length = line_length - len(key) - line_length_buffer if len(value) > max_length: value = '%s...' % value[:max_length] return value prefs = Preferences.read_prefs(path) if prefs: prefs = dict(prefs) parts.append((prefs_file, '\n%s' %('\n'.join(['%s: %s' % (key, format_value(key, prefs[key])) for key in sorted(prefs.keys()) ])))) # Currently hardcorded to 'network.proxy.autoconfig_url' # but could be generalized, possibly with a generalized (simple) # JS-parser network_proxy_autoconfig = prefs.get('network.proxy.autoconfig_url') if network_proxy_autoconfig and network_proxy_autoconfig.strip(): network_proxy_autoconfig = network_proxy_autoconfig.strip() lines = network_proxy_autoconfig.replace(';', ';\n').splitlines() lines = [line.strip() for line in lines] origins_string = 'var origins = [' origins_end = '];' if origins_string in lines[0]: start = lines[0].find(origins_string) end = lines[0].find(origins_end, start); splitline = [lines[0][:start], lines[0][start:start+len(origins_string)-1], ] splitline.extend(lines[0][start+len(origins_string):end].replace(',', ',\n').splitlines()) splitline.append(lines[0][end:]) lines[0:1] = [i.strip() for i in splitline] parts.append(('Network Proxy Autoconfig, %s' % (prefs_file), '\n%s' % '\n'.join(lines))) if return_parts: return parts retval = '%s\n' % ('\n\n'.join(['[%s]: %s' % (key, value) for key, value in parts])) return retval __str__ = summary
class BuhIRC: def __init__(self, config_file): self.config_file = config_file self.config = json.load(open(self.config_file)) self.me = self.config["me"] self.net = self.config["network"] self.module_manager = ModuleManager(self) self.hook_manager = HookManager(self) self.perms = Permissions(self) self.connection = IRCConnection(self.net["address"], self.net["port"], self.net["ssl"], self.config["proxies"].get(self.net.get("proxy", "none"), None), self.net.get("flood_interval", 0.0)) self.running = True self.state = {} # Dict used to hold stuff like last line received and last message etc... self.db = Database("etc/buhirc.db") self.db.connect() logging.basicConfig(level=getattr(logging, self.config["misc"]["loglevel"]), format='[%(asctime)s] %(levelname)s: %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p') self.requests_session = requests.session() if self.config["misc"].get("http_proxy", "none") != "none": proxy = self.config["proxies"].get(self.config["misc"]["http_proxy"], "none") if proxy != "none": self.requests_session.proxies = {"http": proxy, "https": proxy} self.flood_verbs = [x.lower() for x in self.net.get("flood_verbs", [])] self.help = {} def run(self): self.connection.connect() if self.config["network"]["sasl"]["use"]: self.raw("CAP REQ :sasl") self.raw("NICK %s" % self.me["nicks"][0]) # Nicks thing is a temp hack self.raw("USER %s * * :%s" % (self.me["ident"], self.me["gecos"])) for module in self.config["modules"]: self.module_manager.load_module(module) while self.running: if not self.loop(): self.stop() def raw(self, line): """ Send a raw IRC line to the server. @param line: The raw line to send, without a trailing carriage return or newline. """ logging.debug("[IRC] <- %s" % line) ln = Line.parse(line) force = True # Whether we bypass flood protection or not. if ln.command.lower() in self.flood_verbs: force = False self.connection.write_line(line, force) def parse_line(self, ln): logging.debug("[IRC] -> %s" % ln.linestr) if ln.command == "PING": self.raw(ln.linestr.replace("PING", "PONG")) elif ln.command == "376": for channel in self.net["channels"]: self.join(channel) elif ln.command == "CAP": if ln.params[1] == "ACK" and ln.params[-1] == "sasl" and self.net["sasl"]["use"]: self.raw("AUTHENTICATE PLAIN") elif ln.command == "AUTHENTICATE": magic = "%s\x00%s\x00%s" % (self.net["sasl"]["username"], self.net["sasl"]["username"], self.net["sasl"]["password"]) magic = base64.b64encode(magic.encode("ascii")) self.raw("AUTHENTICATE %s" % magic) elif ln.command == "903": self.raw("CAP END") elif ln.command == "904": logging.warning("SASL authentication failed, continuing login anyways...") self.raw("CAP END") def loop(self): if not self.connection.loop(): return False for line in self.connection.buffer: ln = Line.parse(line) self.state["last_line"] = ln self.parse_line(ln) self.hook_manager.run_irc_hooks(ln) return True def stop(self): self.raw("QUIT :Bye!") self.connection.disconnect() self.running = False def rehash(self): """ Rehash (reread and reparse) the bot's configuration file. """ self.config = json.load(open(self.config_file)) # Helper functions def hook_command(self, cmd, callback, help_text=None): """ Register a command hook to the bot. @param cmd: Command name to hook. @param callback: Event callback function to call when this command is ran. @param help_text: Help text for this command, no help if not specified. @return: ID of the new hook. (Used for removal later) """ cmd = cmd.lower() if help_text: self.help[cmd] = help_text return self.hook_manager.add_hook(Hook("command_%s" % cmd, callback)) def hook_numeric(self, numeric, callback): """ Register a raw numeric hook to the bot. @param numeric: The raw IRC numeric (or command, such as PRIVMSG) to hook. @param callback: Event callback function to call when this numeric/command is received from the server. @return: ID of the new hook. (Used for removal later) """ return self.hook_manager.add_hook(Hook("irc_raw_%s" % numeric, callback)) def list_commands(self): """ Get a list of all commands the bot knows about. @return: list of command names """ return [hook[8:] for hook in self.hook_manager.hooks.keys() if hook.startswith("command_")] # len("command_") == 8 def unhook_something(self, the_id): """ Unhook any sort of hook. (Command, numeric, or event.) @param the_id: The ID of the hook to remove, returned by a hook adding function. """ self.hook_manager.remove_hook(the_id) def is_admin(self, hostmask=None): """ Check if a hostmask is a bot admin. @param hostmask: The hostmask to check. @return: True if admin, False if not. """ if hostmask is None: hostmask = self.state["last_line"].hostmask return self.perms.check_permission(hostmask, "admin") def check_condition(self, condition, false_message="Sorry, you may not do that.", reply_func=None): """ Check a condition and return it, calling reply_func with false_message if the condition is False. @param condition: The condition to check. @param false_message: The message to be passed to reply_func @param reply_func: The function to call with false_message as argument if condition is False. @return: """ if reply_func is None: reply_func = self.reply if condition: return True reply_func(false_message) return False def check_permission(self, permission="admin", error_reply="Sorry, you do not have permission to do that!", reply_func=None): """ Check a bot permission against the hostmask of the last line received, and return whether it matches. Calls reply_func with error_reply as argument if condition is False @param permission: The permission to check. @param error_reply: The message to be passed to reply_func @param reply_func: The function to call with error_reply as argument if condition is False. @return: """ if reply_func is None: reply_func = self.reply_notice return self.check_condition(self.perms.check_permission(self.state["last_line"].hostmask, permission), error_reply, reply_func) # IRC-related stuff begins here def _msg_like(self, verb, target, message): self.raw("%s %s :%s" % (verb, target, message)) def privmsg(self, target, message): """ Send a PRIVMSG (channel or user message) to a user/channel. @param target: The target to send this message to. (Can be nickname or channel.) @param message: The actual message to send. """ self._msg_like("PRIVMSG", target, message) def act(self, target, action): """ Send a CTCP ACTION (/me) to a user/channel. @param target: The target to send this ACTION to. (Can be nickname or channel.) @param action: The actual action to send. """ self.privmsg(target, "\x01ACTION %s\x01" % action) def notice(self, target, message): """ Send a NOTICE to a user/channel. @param target: The user or channel to send this notice to. @param message: The actual notice text. """ self._msg_like("NOTICE", target, message) def join(self, channel): """ Send a raw channel JOIN message to the server. (Join a channel) @param channel: The channel to join. (Key can be passed in the same argument, separated by a space.) """ self.raw("JOIN %s" % channel) def part(self, channel): """ Send a raw channel PART to the server. (Leave a channel) @param channel: The channel to leave. """ self.raw("PART %s" % channel) # IRC-related stuff that involves state. def reply(self, message): """ Send a PRIVMSG (channel or user message) to the last channel or user we received a message in. @param message: The reply message to send. """ ln = self.state["last_line"] reply_to = ln.hostmask.nick if ln.params[0][0] == "#": reply_to = ln.params[0] self.privmsg(reply_to, message) def reply_act(self, action): """ Send a CTCP ACTION (/me) to the last channel or user we received a message in. @param action: The action to send. """ self.reply("\x01ACTION %s\x01" % action) def reply_notice(self, message): """ Send a NOTICE to the last channel or user we received a message in. @param message: The notice text to send. """ ln = self.state["last_line"] self.notice(ln.hostmask.nick, message) # Web stuff. def http_get(self, url, **kwargs): """ Perform an HTTP GET using requests. @param url: The URL to GET. @param kwargs: Any arguments to pass to requests.get() @return: requests.Response object. """ return self.requests_session.get(url, **kwargs) def http_post(self, url, **kwargs): """ Perform an HTTP POST using requests. @param url: The URL to POST to. @param kwargs: Any arguments to pass to requests.get() @return: requests.Response object. """ return self.requests_session.post(url, **kwargs)