From d2e53a84199b1594cb491c85a24822a113ac07d9 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 17 Sep 2007 18:24:34 -0400 Subject: Upgrading cookie and token handling and brining it inside of CobblerWeb.py --- cobbler/remote.py | 9 +-- cobbler/webui/CobblerWeb.py | 168 ++++++++++++++++++++++++++++++-------------- docs/WUI_TODO | 1 - scripts/cobbler_webui.cgi | 31 +++----- 4 files changed, 128 insertions(+), 81 deletions(-) diff --git a/cobbler/remote.py b/cobbler/remote.py index 85b7b8b..12f13f8 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -67,6 +67,9 @@ class CobblerXMLRPCInterface: def __sorter(self,a,b): return cmp(a["name"],b["name"]) + def ping(self): + return True + def get_settings(self,token=None): """ Return the contents of /var/lib/cobbler/settings, which is a hash. @@ -403,14 +406,12 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): return True return False - def test(self,token): + def token_check(self,token): """ This is a demo function that does not return anything useful. """ self.__validate_token(token) - return "passed" - - + return True def __store_object(self,reference): """ diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index 3d5d446..395c9bc 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -19,6 +19,7 @@ from cobbler.utils import * import logging import sys import Cookie +import time # set up logging @@ -46,70 +47,101 @@ class CobblerWeb(object): it all run either under cgi-bin or CherryPy. Supporting other Python frameworks should be trivial. """ - def __init__(self, server=None, base_url='/', username=None, password=None, token=None, token_cookie_name='cobbler_xmlrpc_token'): + def __init__(self, server=None, base_url='/', username=None, password=None, token=None): self.server = server self.base_url = base_url self.remote = None self.token = token self.username = username self.password = password - self.token_cookie_name = token_cookie_name self.logout = None + self.__cookies = Cookie.SimpleCookie(Cookie.SimpleCookie(os.environ.get("HTTP_COOKIE",""))) - def __xmlrpc_setup(self): + def __xmlrpc_setup(self,is_login=False): """ Sets up the connection to the Cobbler XMLRPC server. Right now, the r/w server is required. In the future, it may be possible to instantiate a r/o webui that doesn't need to login. """ - if self.remote is None: - self.remote = xmlrpclib.Server(self.server, allow_none=True) + + # changed to always create a new connection object + self.remote = xmlrpclib.Server(self.server, allow_none=True) + # if we do not have a token in memory, is it in a cookie? if self.token is None: - self.token = self.remote.login( self.username, self.password ) + self.token = self.__get_cookie_token() + + # if we have don't have a token, login for the first time + if self.token is None: + try: + self.token = self.remote.login( self.username, self.password ) + except Exception, e: + logger.info("login failed for: %s" % self.username) + log_exc() + return False + self.__cookie_login(self.token) # save what we've got self.password = None # don't need it anymore, get rid of it + return True + + # else if we do have a token, try to use it... + else: + # validate that our token is still good + try: + self.remote.token_check(self.token) + except Exception, e: + # FIXME: check exception type to see that it is login related + logger.info("token timeout for: %s" % self.username) + log_exc() + self.token = None + # this should put us back to the login screen + self.__cookie_logout() + return False + return True - return self.remote def __render(self, template, data): """ Call the templating engine (Cheetah), wrapping up the location of files while we're at it. """ - try: - data['base_url'] = self.base_url - - # used by master.tmpl to determine whether or not to show login/logout links - if self.token: - data['logged_in'] = 1 - elif self.username and self.password: - data['logged_in'] = 'configured' - else: - data['logged_in'] = None - - filepath = os.path.join("/usr/share/cobbler/webui_templates/",template) - tmpl = Template( file=filepath, searchList=data ) - return str(tmpl) - except: - log_exc() - return self.error_page("Error while rendering page. See /var/log/cobbler/webui.log") + + data['base_url'] = self.base_url + + # used by master.tmpl to determine whether or not to show login/logout links + if self.token is not None: + data['logged_in'] = 1 + elif self.username and self.password: + data['logged_in'] = 'configured' + else: + data['logged_in'] = None + + filepath = os.path.join("/usr/share/cobbler/webui_templates/",template) + tmpl = Template( file=filepath, searchList=data ) + return str(tmpl) def cookies(self): """ Returns a Cookie.SimpleCookie object with all of CobblerWeb's cookies. Mmmmm cookies! """ - cookies = Cookie.SimpleCookie() + return self.__cookies - if self.logout: - cookies[self.token_cookie_name] = "null" - cookies[self.token_cookie_name]['expires'] = 0 - self.logout = None + def __cookie_logout(self,): + self.__cookies["cobbler_xmlrpc_token"] = "null" + self.__cookies["cobbler_xmlrpc_token"]['expires'] = 0 + return self.cookies - elif self.token: - cookies[self.token_cookie_name] = self.token - - return cookies + def __cookie_login(self,token): + self.__cookies["cobbler_xmlrpc_token"] = token + self.__cookies["cobbler_xmlrpc_token"]['expires'] = time.time() + 29*60 + return self.cookies + + def __get_cookie_token(self): + if self.__cookies.has_key("cobbler_xmlrpc_token"): + value = self.__cookies["cobbler_xmlrpc_token"] + logger.debug("loading token from cookie: %s" % value.value) + return value.value + return None def modes(self): """ @@ -147,13 +179,14 @@ class CobblerWeb(object): self.username = username self.password = password - self.__xmlrpc_setup() + if not self.__xmlrpc_setup(): + return self.login(message="") return self.index() def logout_submit(self): self.token = None - self.logout = 1 + self.__cookie_logout() return self.login() # ------------------------------------------------------------------------ # @@ -165,7 +198,9 @@ class CobblerWeb(object): # including your ability to fix it back. def settings_view(self): - self.__xmlrpc_setup() + if not self.__xmlrpc_setup(): + return self.login(message="") + return self.__render( 'item.tmpl', { 'item_data': self.remote.get_settings(), 'caption': "Cobbler Settings" @@ -176,7 +211,8 @@ class CobblerWeb(object): # ------------------------------------------------------------------------ # def distro_list(self): - self.__xmlrpc_setup() + if not self.__xmlrpc_setup(): + return self.login(message="") distros = self.remote.get_distros() if len(distros) > 0: return self.__render( 'distro_list.tmpl', { @@ -186,7 +222,9 @@ class CobblerWeb(object): return self.__render('empty.tmpl', {}) def distro_edit(self, name=None): - self.__xmlrpc_setup() + + if not self.__xmlrpc_setup(): + return self.login(message="") input_distro = None if name is not None: @@ -197,10 +235,19 @@ class CobblerWeb(object): 'distro': input_distro, } ) - # FIXME: implement handling of delete1, delete2 + renames + # FIXME: deletes and renames def distro_save(self,name=None,new_or_edit=None,kernel=None,initrd=None,kopts=None,ksmeta=None,arch=None,breed=None,**args): - self.__xmlrpc_setup() - + + if not self.__xmlrpc_setup(): + return self.login(message="") + + #if delete1 and delete2: + # try: + # self.remote.distro_remove(name) + # except Exception, e: + # self.error_page("could not delete %s, %s" % str(e)) + # return self.distro_edit(name=name) + # pre-command paramter checking if name is None: return self.error_page("name is required") @@ -214,6 +261,7 @@ class CobblerWeb(object): try: distro = self.remote.get_distro_handle( name, self.token) except: + log_exc() return self.error_page("Failed to lookup distro: %s" % name) else: distro = self.remote.new_distro(self.token) @@ -244,7 +292,10 @@ class CobblerWeb(object): # iterator so the list doesn't get copied around def system_list(self): - self.__xmlrpc_setup() + + if not self.__xmlrpc_setup(): + return self.login(message="") + systems = self.remote.get_systems() if len(systems) > 0: return self.__render( 'system_list.tmpl', { @@ -257,7 +308,8 @@ class CobblerWeb(object): def system_save(self, name=None, profile=None, new_or_edit=None, mac=None, ip=None, hostname=None, kopts=None, ksmeta=None, netboot='n', dhcp_tag=None, **args): - self.__xmlrpc_setup() + if not self.__xmlrpc_setup(): + return self.login(message="") # parameter checking if name is None: @@ -307,7 +359,8 @@ class CobblerWeb(object): def system_edit(self, name=None): - self.__xmlrpc_setup() + if not self.__xmlrpc_setup(): + return self.login(message="") input_system = None if name is not None: @@ -323,7 +376,8 @@ class CobblerWeb(object): # Profiles # ------------------------------------------------------------------------ # def profile_list(self): - self.__xmlrpc_setup() + if not self.__xmlrpc_setup(): + return self.login(message="") profiles = self.remote.get_profiles() if len(profiles) > 0: return self.__render( 'profile_list.tmpl', { @@ -335,7 +389,8 @@ class CobblerWeb(object): # FIXME: implement handling of delete1, delete2 + renames def profile_edit(self, name=None): - self.__xmlrpc_setup() + if not self.__xmlrpc_setup(): + return self.login(message="") input_profile = None if name is not None: @@ -352,7 +407,8 @@ class CobblerWeb(object): ksmeta=None,virtfilesize=None,virtram=None,virttype=None, virtpath=None,repos=None,dhcptag=None,**args): - self.__xmlrpc_setup() + if not self.__xmlrpc_setup(): + return self.login(message="") # pre-command parameter checking if name is None: @@ -402,7 +458,8 @@ class CobblerWeb(object): # ------------------------------------------------------------------------ # def repo_list(self): - self.__xmlrpc_setup() + if not self.__xmlrpc_setup(): + return self.login(message="") repos = self.remote.get_repos() if len(repos) > 0: return self.__render( 'repo_list.tmpl', { @@ -412,7 +469,8 @@ class CobblerWeb(object): return self.__render('empty.tmpl', {}) def repo_edit(self, name=None): - self.__xmlrpc_setup() + if not self.__xmlrpc_setup(): + return self.login(message="") input_repo = None if name is not None: @@ -423,7 +481,8 @@ class CobblerWeb(object): } ) def repo_save(self,name=None,new_or_edit=None,mirror=None,keepupdated=None,localfilename=None,rpmlist=None,createrepoflags=None,**args): - self.__xmlrpc_setup() + if not self.__xmlrpc_setup(): + return self.login(message="") # pre-command parameter checking if name is None: @@ -465,13 +524,15 @@ class CobblerWeb(object): # ------------------------------------------------------------------------ # def ksfile_list(self): - self.__xmlrpc_setup() + if not self.__xmlrpc_setup(): + return self.login(message="") return self.__render( 'ksfile_list.tmpl', { 'ksfiles': self.remote.get_kickstart_templates(self.token) } ) def ksfile_edit(self, name=None): - self.__xmlrpc_setup() + if not self.__xmlrpc_setup(): + return self.login(message="") return self.__render( 'ksfile_edit.tmpl', { 'ksfile': name, 'ksdata': self.remote.read_or_write_kickstart_template(self,name,True,"",self.token) @@ -479,7 +540,8 @@ class CobblerWeb(object): } ) def ksfile_save(self, name=None, data=None): - self.__xmlrpc_setup() + if not self.__xmlrpc_setup(): + return self.login(message="") try: self.remote.read_or_write_kickstart_template(self,name,False,data,self.token) except Exception, e: diff --git a/docs/WUI_TODO b/docs/WUI_TODO index 78cf92a..60b1bcd 100644 --- a/docs/WUI_TODO +++ b/docs/WUI_TODO @@ -1,6 +1,5 @@ Things to do on the Web User Interface -- finish up auth bits - make fields wider that need to be wider (most can probably be ~128? Ksmeta/Kopts min 255) - modify form fields that should be radio boxes, etc (things like arch, breed) - rename support for all distro, profile, system, repo objects diff --git a/scripts/cobbler_webui.cgi b/scripts/cobbler_webui.cgi index 6e93a27..899610c 100755 --- a/scripts/cobbler_webui.cgi +++ b/scripts/cobbler_webui.cgi @@ -40,10 +40,9 @@ def configure(): 'token': None, 'server': None, 'base_url': None, - 'token_cookie_name': None, 'username': None, 'password': None, - 'cgitb_enabled': 0 + 'cgitb_enabled': 1 } #config.username = 'testuser', #config.password = 'llamas2007' @@ -55,9 +54,6 @@ def configure(): if config['base_url'] is None: config['base_url'] = base_url() - if config['token_cookie_name'] is None: - config['token_cookie_name'] = 'cobbler_xmlrpc_token' - return config def main(): @@ -72,10 +68,6 @@ def main(): cgitb.enable() cw_conf.pop('cgitb_enabled') - # look for the token cookie and put it in the config dict if found - if cookies.has_key( cw_conf['token_cookie_name'] ): - cw_conf['token'] = cookies[ cw_conf['token_cookie_name'] ].value - # exchnage single-element arrays in the 'form' dictionary for just that item # so there isn't a ton of 'foo[0]' craziness where 'foo' should suffice # - may be bad for form elements that are sometimes lists and sometimes @@ -88,23 +80,16 @@ def main(): # instantiate a CobblerWeb object cw = CobblerWeb( **cw_conf ) - if not path.startswith('login') and (cw_conf['token'] is None and (cw_conf['username'] is None or cw_conf['password'] is None)): - func = getattr( cw, 'login' ) - content = func( message="Authentication Required." ) + # FIXME: allow for direct URL access and pages will redirect appropriately. + + #if not path.startswith('login') and (cw_conf['token'] is None and (cw_conf['username'] is None or cw_conf['password'] is None)): + # func = getattr( cw, 'login' ) + # content = func( message="Authentication Required." ) # check for a valid path/mode - elif path in cw.modes(): + if path in cw.modes(): func = getattr( cw, path ) - try: - content = func( **form ) - # handle failed authentication gracefully - except Exception, e: - if str(e).find('login failed:') > 0: - func = getattr( cw, 'login' ) - content = func( message="Authentication failed." ) - # everything else is a bug? - else: - raise e + content = func( **form ) # handle invalid paths gracefully else: -- cgit