import CryptoBox import WebInterfaceDataset import re import Plugins from CryptoBoxExceptions import * import cherrypy class WebInterfacePlugins: def __init__(self, log, plugins, handler_func): for plugin in plugins.getPlugins(): if not plugin: continue plname = plugin.getName() log.info("Plugin '%s' loaded" % plname) ## this should be the "easiest" way to expose all plugins as URLs setattr(self, plname, handler_func(plugin)) setattr(getattr(self, plname), "exposed", True) # TODO: check, if this really works - for now the "stream_response" feature seems to be broken #setattr(getattr(self, plname), "stream_respones", True) class WebInterfaceSites: ''' url2func = {'index':'show_status','doc':'show_doc','logs':'show_log'} ''' def __init__(self): import logging self.cbox = CryptoBox.CryptoBoxProps() self.log = logging.getLogger("CryptoBox") self.prefs = self.cbox.prefs self.__resetDataset() def __resetDataset(self): """this method has to be called at the beginning of every "site" action important: only at the beginning of an action (to not loose information) important: for _every_ "site" action (cherrypy is stateful) also take care for the plugins, as they also contain datasets """ self.pluginList = Plugins.PluginManager(self.cbox, self.prefs["Locations"]["PluginDir"]) self.plugins = WebInterfacePlugins(self.log, self.pluginList, self.return_plugin_action) ## publish the url "/system" as an alias for "/plugins" self.plugins.index = self.system self.dataset = WebInterfaceDataset.WebInterfaceDataset(self.cbox, self.prefs, self.pluginList.getPlugins()) def __check_config(self): #TODO: from now on a cryptobox is always configured return True def __check_init_running(self): #TODO: implement this check (is mkfs still running?) return False ## this is a function decorator to check authentication ## it has to be defined before any page definition requiring authentification def __requestAuth(self=None): def check_credentials(site): def _inner_wrapper(self, *args, **kargs): import base64 ## define a "non-allowed" function user, password = None, None try: resp = cherrypy.request.headers["Authorization"][6:] # ignore "Basic " (user, password) = base64.b64decode(resp).split(":",1) except KeyError: ## no "authorization" header was sent pass except TypeError: ## invalid base64 string pass except AttributeError: ## no cherrypy response header defined pass authDict = self.cbox.prefs.userDB["admins"] if user in authDict.keys(): if self.cbox.prefs.userDB.getDigest(password) == authDict[user]: ## ok: return the choosen page self.cbox.log.info("access granted for: %s" % user) return site(self, *args, **kargs) else: self.cbox.log.info("wrong password supplied for: %s" % user) else: self.cbox.log.info("unknown user: %s" % str(user)) ## wrong credentials: return "access denied" cherrypy.response.headers["WWW-Authenticate"] = '''Basic realm="CryptoBox"''' cherrypy.response.status = 401 return self.__render("access_denied") return _inner_wrapper return check_credentials ###################################################################### ## put real sites down here and don't forget to expose them at the end @cherrypy.expose def status(self, weblang=""): '''shows the current status of the box ''' self.__resetDataset() self.__setWebLang(weblang) if not self.__check_config(): self.dataset["Data.Warning"] = "NotInitialized" return self.__render("form_init") elif self.__check_init_running(): self.dataset["Data.Warning"] = "InitNotFinished" self.dataset["Data.Redirect.Action"] = "form_config" self.dataset["Data.Redirect.Delay"] = "30" return self.__render("empty") else: self.dataset["Data.Redirect.Delay"] = "60" return self.__render("show_status") @cherrypy.expose def doc(self,page="",weblang=""): '''prints the offline wikipage ''' import re self.__resetDataset() self.__setWebLang(weblang) ## check for invalid characters if page and not re.search(u'\W', page): self.dataset["Data.Doc.Page"] = page else: ## display this page as default help page self.dataset["Data.Doc.Page"] ="CryptoBoxUser" return self.__render("show_doc") @cherrypy.expose def system(self, weblang=""): self.__resetDataset() self.__setWebLang(weblang) return self.__render("form_system") @cherrypy.expose def index(self, weblang=""): self.__resetDataset() self.__setWebLang(weblang) return self.__render("show_status") @cherrypy.expose def show_volume(self, device="", weblang=""): self.__resetDataset() self.__setWebLang(weblang) if self.__setDevice(device): return self.__render("show_volume") else: if self.cbox.getContainerList(): return self.__render("show_volumes") else: return self.__render("show_status") @cherrypy.expose def show_volumes(self, weblang=""): self.__resetDataset() self.__setWebLang(weblang) return self.__render("show_volumes") def return_plugin_action(self, plugin): def handler(self, **args): self.__resetDataset() try: self.__setWebLang(args["weblang"]) del args["weblang"] except KeyError: pass if "icon" in args.keys(): return plugin.getIcon() ## check the device argument of volume plugins if "volume" in plugin.pluginCapabilities: try: ## initialize the dataset of the selected device if necessary if self.__setDevice(args["device"]): plugin.device = args["device"] self.dataset.setCurrentDiskState(plugin.device) else: return self.__render("show_status") except KeyError: return self.__render("show_status") else: ## the parameter 'device' exists - we have to remove it del args["device"] ## call the plugin handler nextTemplate = plugin.doAction(**args) ## for 'volume' plugins: reread the dataset of the current disk ## additionally: set the default template for plugins if "volume" in plugin.pluginCapabilities: self.dataset.setCurrentDiskState(plugin.device) if not nextTemplate: nextTemplate = "show_volume" else: self.dataset.setPluginData() if not nextTemplate: nextTemplate = "form_system" ## save the currently active plugin name self.dataset["Data.ActivePlugin"] = plugin.getName() return self.__render(nextTemplate, plugin) ## apply authentication? if plugin.isAuthRequired(): return lambda **args: self.__requestAuth()(handler)(self, **args) else: return lambda **args: handler(self, **args) ## test authentication @cherrypy.expose @__requestAuth def test(self, weblang=""): self.__resetDataset() self.__setWebLang(weblang) return "test passed" @cherrypy.expose def test_stream(self): """just for testing purposes - to check if the "stream_response" feature actually works - for now (September 02006) it does not seem to be ok""" import time yield "neu

" ##################### input checker ########################## def __setWebLang(self, value): guess = value availLangs = self.cbox.getAvailableLanguages() ## no language specified: check browser language if not guess: guess = self.__getPreferredBrowserLanguage(availLangs) ## no preferred language or invalid language? if not guess \ or not guess in availLangs \ or re.search(u'\W', guess): ## warn only for invalid languages if not guess is None: self.cbox.log.info("invalid language choosen: %s" % guess) guess = self.prefs["WebSettings"]["Language"] ## maybe the language is still not valid if not guess in availLangs: self.log.warn("the configured language is invalid: %s" % guess) guess = "en" ## maybe there is no english dataset??? if not guess in availLangs: self.log.warn("couldn't find the english dataset") guess = availLangs[0] self.dataset["Settings.Language"] = guess ## we only have to save it, if it was specified correctly and explicitly if value == guess: self.dataset["Settings.LinkAttrs.weblang"] = guess def __getPreferredBrowserLanguage(self, availLangs): """guess the preferred language of the user (as sent by the browser) take the first language, that is part of 'availLangs' """ import cherrypy try: pref_lang_header = cherrypy.request.headers["Accept-Language"] except KeyError: ## no language header was specified return None ## this could be a typical 'Accept-Language' header: ## de-de,de;q=0.8,en-us;q=0.5,en;q=0.3 regex = re.compile(u"\w+(-\w+)?(;q=[\d\.]+)?$") pref_langs = [e.split(";",1)[0] for e in pref_lang_header.split(",") if regex.match(e)] ## is one of these preferred languages available? for lang in pref_langs: if lang in availLangs: return lang ## we try to be nice: also look for "de" if "de-de" was specified ... for lang in pref_langs: ## use only the first part of the language short_lang = lang.split("-",1)[0] if short_lang in availLangs: return short_lang ## we give up return None def __setDevice(self, device): if device and re.match(u'[\w /\-]+$', device) and self.cbox.getContainer(device): self.log.debug("select device: %s" % device) self.dataset.setCurrentDiskState(device) return True else: self.log.warn("invalid device: %s" % device) self.dataset["Data.Warning"] = "InvalidDevice" return False def __checkVolumeName(self, name): if name and re.match(u'[\w \-]+$', name): return True else: return False def __getLanguageValue(self, value): hdf = self.__getLanguageData(self.dataset["Settings.Language"]) return hdf.getValue(value, "") def __getLanguageData(self, web_lang="en"): import neo_cgi, neo_util, os default_lang = "en" conf_lang = self.prefs["WebSettings"]["Language"] hdf = neo_util.HDF() langDir = os.path.abspath(self.prefs["Locations"]["LangDir"]) langFiles = [] ## first: read default language (en) if (default_lang != conf_lang) and (default_lang != web_lang): langFiles.append(os.path.join(langDir, default_lang + ".hdf")) ## second: read language as defined in the config file if (conf_lang != web_lang): langFiles.append(os.path.join(langDir, conf_lang + ".hdf")) ## third: read language as configured via web interface langFiles.append(os.path.join(langDir, web_lang + ".hdf")) for langFile in langFiles: if os.access(langFile, os.R_OK): hdf.readFile(langFile) else: log.warn("Couldn't read language file: %s" % langFile) return hdf def __render(self, renderInfo, plugin=None): '''renders from clearsilver templates and returns the resulting html ''' import os, types try: import neo_cgi, neo_util, neo_cs except ImportError: errorMsg = "Could not import clearsilver module. Try 'apt-get install python-clearsilver'." self.log.error(errorMsg) sys.stderr.write(errorMsg) raise ImportError, errorMsg ## is renderInfo a string (filename of the template) or a dictionary? if type(renderInfo) == types.DictType: template = renderInfo["template"] if renderInfo.has_key("generator"): generator = renderInfo["generator"] else: generator = False else: (template, generator) = (renderInfo, None) ## load the language data hdf = neo_util.HDF() hdf.copy("Lang", self.__getLanguageData(self.dataset["Settings.Language"])) ## first: assume, that the template file is in the global template directory self.dataset["Settings.TemplateFile"] = os.path.abspath(os.path.join(self.prefs["Locations"]["TemplateDir"], template + ".cs")) if plugin: ## check, if the plugin provides the template file -> overriding plugin_cs_file = plugin.getTemplateFileName(template) if plugin_cs_file: self.dataset["Settings.TemplateFile"] = plugin_cs_file ## add the current state of the plugins to the hdf dataset self.dataset["Data.Status.Plugins.%s" % plugin.getName()] = plugin.getStatus() ## load the language data pl_lang = plugin.getLanguageData(self.dataset["Settings.Language"]) if pl_lang: hdf.copy("Lang.Plugins.%s" % plugin.getName(), pl_lang) ## load the dataset of the plugin plugin.loadDataSet(hdf) self.log.info("rendering site: " + template) cs_path = os.path.abspath(os.path.join(self.prefs["Locations"]["TemplateDir"], "main.cs")) if not os.access(cs_path, os.R_OK): log.error("Couldn't read clearsilver file: %s" % cs_path) yield "Couldn't read clearsilver file: %s" % cs_path return ## update the container hdf-dataset (necessary if a plugin changed the state of a container) self.dataset.setContainersState() self.log.debug(self.dataset) for key in self.dataset.keys(): hdf.setValue(key,str(self.dataset[key])) cs = neo_cs.CS(hdf) cs.parseFile(cs_path) ## is there a generator containing additional information? if generator is None: ## all content in one flush yield cs.render() else: content_generate = generator() dummy_line = """""" ## now we do it linewise - checking for the content marker for line in cs.render().splitlines(): if line.find(dummy_line) != -1: yield line.replace(dummy_line, content_generate.next()) else: yield line + "\n"