From fcb4cb6bdbb71b3c49215b9da9c4e1c97c7ab20c Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Mon, 29 Oct 2007 16:40:13 -0400 Subject: Apply AT's patch to switch the WebUI to using Apache digest auth / htaccess / etc --- MANIFEST.in | 1 + cobbler.spec | 1 + cobbler/webui/CobblerWeb.py | 106 +++++++++-------------------------- cobbler/webui/master.py | 131 ++++++++++++++++++++------------------------ config/cobbler.conf | 12 ++++ scripts/webui.cgi | 26 ++++----- setup.py | 2 +- webui_templates/index.tmpl | 5 -- webui_templates/login.tmpl | 28 ---------- webui_templates/master.tmpl | 43 ++++++--------- 10 files changed, 129 insertions(+), 226 deletions(-) delete mode 100644 webui_templates/login.tmpl diff --git a/MANIFEST.in b/MANIFEST.in index c4103ec..85ba1ef 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -10,6 +10,7 @@ include config/modules.conf include config/auth.conf include config/webui-cherrypy.cfg include config/settings +include config/.htaccess recursive-include templates *.template recursive-include kickstarts *.ks include docs/cobbler.1.gz diff --git a/cobbler.spec b/cobbler.spec index 326b586..8d7462e 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -142,6 +142,7 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %{_mandir}/man1/cobbler.1.gz /etc/init.d/cobblerd %config(noreplace) /etc/httpd/conf.d/cobbler.conf +%config(noreplace) /var/www/cgi-bin/cobbler/.htaccess %dir /var/log/cobbler/syslog %defattr(755,root,root) diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index eb82564..69c8e7f 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -19,15 +19,9 @@ import string from cobbler.utils import * import logging import sys -import Cookie -import time LOGGING_ENABLED = True -## FIXME: move to htaccess and eliminate browser cookies? - -COOKIE_TIMEOUT=29*60 - if LOGGING_ENABLED: # set up logging logger = logging.getLogger("cobbler.webui") @@ -40,8 +34,6 @@ if LOGGING_ENABLED: else: logger = None -INVALID_CREDS="Login Required" - def log_exc(): """ Log active traceback to logfile. @@ -68,9 +60,8 @@ class CobblerWeb(object): self.username = username self.password = password self.logout = None - self.__cookies = Cookie.SimpleCookie(Cookie.SimpleCookie(os.environ.get("HTTP_COOKIE",""))) - def __xmlrpc_setup(self,is_login=False): + def __xmlrpc_setup(self): """ 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 @@ -80,10 +71,6 @@ class CobblerWeb(object): # 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.__get_cookie_token() - # else if we do have a token, try to use it... if self.token is not None: # validate that our token is still good @@ -96,13 +83,11 @@ class CobblerWeb(object): 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() else: raise e # if we (still) don't have a token, login for the first time - if self.token is None and is_login: + elif self.password and self.username: try: self.token = self.remote.login( self.username, self.password ) except Exception, e: @@ -110,14 +95,12 @@ class CobblerWeb(object): 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 # login failed return False - def __render(self, template, data): """ Call the templating engine (Cheetah), wrapping up the location @@ -126,16 +109,6 @@ class CobblerWeb(object): data['base_url'] = self.base_url - # used by master.tmpl to determine whether or not to show login/logout links - if data.has_key("hide_links"): - data['logged_in'] = None - elif 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) @@ -232,38 +205,11 @@ class CobblerWeb(object): # ------------------------------------------------------------------------ # def index(self): - return self.__render( 'index.tmpl', { "hide_links" : True } ) + return self.__render( 'index.tmpl', { } ) def menu(self): return self.__render( 'blank.tmpl', { } ) - - # ------------------------------------------------------------------------ # - # Authentication - # ------------------------------------------------------------------------ # - - def login(self, message=None): - return self.__render( 'login.tmpl', {'message': message, "hide_links" : True } ) - - def login_submit(self, username=None, password=None, submit=None): - if username is None: - return self.error_page( "No username supplied." ) - if password is None: - return self.error_page( "No password supplied." ) - - self.username = username - self.password = password - - if not self.__xmlrpc_setup(is_login=True): - return self.login(message="Login Failed.") - - return self.menu() - - def logout_submit(self): - self.token = None - self.__cookie_logout() - return self.login() - # ------------------------------------------------------------------------ # # Settings # ------------------------------------------------------------------------ # @@ -274,7 +220,7 @@ class CobblerWeb(object): def settings_view(self): if not self.__xmlrpc_setup(): - return self.login(message=INVALID_CREDS) + return self.xmlrpc_auth_failure() return self.__render( 'item.tmpl', { 'item_data': self.remote.get_settings(), @@ -287,7 +233,7 @@ class CobblerWeb(object): def distro_list(self): if not self.__xmlrpc_setup(): - return self.login(message=INVALID_CREDS) + return self.xmlrpc_auth_failure() distros = self.remote.get_distros() if len(distros) > 0: return self.__render( 'distro_list.tmpl', { @@ -299,7 +245,7 @@ class CobblerWeb(object): def distro_edit(self, name=None): if not self.__xmlrpc_setup(): - return self.login(message=INVALID_CREDS) + return self.xmlrpc_auth_failure() input_distro = None if name is not None: @@ -316,7 +262,7 @@ class CobblerWeb(object): delete1=None,delete2=None,**args): if not self.__xmlrpc_setup(): - return self.login(message=INVALID_CREDS) + return self.xmlrpc_auth_failure() # handle deletes as a special case if new_or_edit == 'edit' and delete1 and delete2: @@ -383,7 +329,7 @@ class CobblerWeb(object): def system_list(self): if not self.__xmlrpc_setup(): - return self.login(message=INVALID_CREDS) + return self.xmlrpc_auth_failure() systems = self.remote.get_systems() if len(systems) > 0: @@ -400,7 +346,7 @@ class CobblerWeb(object): delete1=None, delete2=None, **args): if not self.__xmlrpc_setup(): - return self.login(message=INVALID_CREDS) + return self.xmlrpc_auth_failure() # parameter checking if name is None and editmode=='edit' and oldname is not None: @@ -508,7 +454,7 @@ class CobblerWeb(object): def system_edit(self, name=None): if not self.__xmlrpc_setup(): - return self.login(message=INVALID_CREDS) + return self.xmlrpc_auth_failure() input_system = None if name is not None: @@ -526,7 +472,7 @@ class CobblerWeb(object): # ------------------------------------------------------------------------ # def profile_list(self): if not self.__xmlrpc_setup(): - return self.login(message=INVALID_CREDS) + return self.xmlrpc_auth_failure() profiles = self.remote.get_profiles() if len(profiles) > 0: return self.__render( 'profile_list.tmpl', { @@ -541,7 +487,7 @@ class CobblerWeb(object): def profile_edit(self, name=None, subprofile=0): if not self.__xmlrpc_setup(): - return self.login(message=INVALID_CREDS) + return self.xmlrpc_auth_failure() input_profile = None if name is not None: @@ -564,7 +510,7 @@ class CobblerWeb(object): parent=None,virtcpus=None,virtbridge=None,subprofile=None,**args): if not self.__xmlrpc_setup(): - return self.login(message=INVALID_CREDS) + return self.xmlrpc_auth_failure() # pre-command parameter checking if name is None and editmode=='edit' and oldname is not None: @@ -655,7 +601,7 @@ class CobblerWeb(object): def repo_list(self): if not self.__xmlrpc_setup(): - return self.login(message=INVALID_CREDS) + return self.xmlrpc_auth_failure() repos = self.remote.get_repos() if len(repos) > 0: return self.__render( 'repo_list.tmpl', { @@ -666,7 +612,7 @@ class CobblerWeb(object): def repo_edit(self, name=None): if not self.__xmlrpc_setup(): - return self.login(message=INVALID_CREDS) + return self.xmlrpc_auth_failure() input_repo = None if name is not None: @@ -681,7 +627,7 @@ class CobblerWeb(object): mirror=None,keep_updated=None,local_filename=None, rpm_list=None,createrepo_flags=None,arch=None,delete1=None,delete2=None,**args): if not self.__xmlrpc_setup(): - return self.login(message=INVALID_CREDS) + return self.xmlrpc_auth_failure() # pre-command parameter checking if name is None and editmode=='edit' and oldname is not None: @@ -748,14 +694,14 @@ class CobblerWeb(object): def ksfile_list(self): if not self.__xmlrpc_setup(): - return self.login(message=INVALID_CREDS) + return self.xmlrpc_auth_failure() return self.__render( 'ksfile_list.tmpl', { 'ksfiles': self.remote.get_kickstart_templates(self.token) } ) def ksfile_edit(self, name=None): if not self.__xmlrpc_setup(): - return self.login(message=INVALID_CREDS) + return self.xmlrpc_auth_failure() return self.__render( 'ksfile_edit.tmpl', { 'name': name, 'ksdata': self.remote.read_or_write_kickstart_template(name,True,"",self.token) @@ -763,7 +709,7 @@ class CobblerWeb(object): def ksfile_save(self, name=None, ksdata=None, **args): if not self.__xmlrpc_setup(): - return self.login(message=INVALID_CREDS) + return self.xmlrpc_auth_failure() try: self.remote.read_or_write_kickstart_template(name,False,ksdata,self.token) except Exception, e: @@ -776,7 +722,7 @@ class CobblerWeb(object): def sync(self): if not self.__xmlrpc_setup(): - return self.login(message=INVALID_CREDS) + return self.xmlrpc_auth_failure() try: rc = self.remote.sync(self.token) @@ -793,7 +739,7 @@ class CobblerWeb(object): def random_mac(self): if not self.__xmlrpc_setup(): - return self.login(message=INVALID_CREDS) + return self.xmlrpc_auth_failure() mac = self.remote.get_random_mac() return mac @@ -809,6 +755,11 @@ class CobblerWeb(object): 'message': message } ) + def xmlrpc_auth_failure(self): + return self.__render( 'error_page.tmpl', { + 'message': "XMLRPC Authentication Error. See Apache logs for details." + } ) + # make CherryPy and related frameworks able to use this module easily # by borrowing the 'exposed' function attritbute standard and using # it for the modes() method @@ -818,11 +769,6 @@ class CobblerWeb(object): index.exposed = True menu.exposed = True - login.exposed = True - login_submit.exposed = True - logout_submit.exposed = True - cookies.exposed = False - distro_edit.exposed = True distro_list.exposed = True distro_save.exposed = True diff --git a/cobbler/webui/master.py b/cobbler/webui/master.py index 17d93b0..ee20ed7 100644 --- a/cobbler/webui/master.py +++ b/cobbler/webui/master.py @@ -33,10 +33,10 @@ VFN=valueForName currentTime=time.time __CHEETAH_version__ = '2.0rc8' __CHEETAH_versionTuple__ = (2, 0, 0, 'candidate', 8) -__CHEETAH_genTime__ = 1192826261.8448961 -__CHEETAH_genTimestamp__ = 'Fri Oct 19 16:37:41 2007' +__CHEETAH_genTime__ = 1193430296.732923 +__CHEETAH_genTimestamp__ = 'Fri Oct 26 13:24:56 2007' __CHEETAH_src__ = 'webui_templates/master.tmpl' -__CHEETAH_srcLastModified__ = 'Fri Oct 12 11:53:14 2007' +__CHEETAH_srcLastModified__ = 'Fri Oct 26 11:41:47 2007' __CHEETAH_docstring__ = 'Autogenerated by CHEETAH: The Python-Powered Template Engine' if __CHEETAH_versionTuple__ < RequiredCheetahVersionTuple: @@ -69,7 +69,7 @@ class master(Template): - ## CHEETAH: generated from #block body at line 60, col 1. + ## CHEETAH: generated from #block body at line 53, col 1. trans = KWS.get("trans") if (not trans and not self._CHEETAH__isBuffering and not callable(self.transaction)): trans = self.transaction # is None unless self.awake() was called @@ -148,75 +148,60 @@ class master(Template): diff --git a/config/cobbler.conf b/config/cobbler.conf index 187e63e..1b0244f 100644 --- a/config/cobbler.conf +++ b/config/cobbler.conf @@ -22,4 +22,16 @@ ProxyPassReverse /cobbler_api http://localhost:25151/ ProxyPass /cobbler_api_rw http://localhost:25152/ ProxyPassReverse /cobbler_api_rw http://localhost:25152/ +# See: http://httpd.apache.org/docs/2.2/mod/mod_auth_digest.html#msie +BrowserMatch "MSIE" AuthDigestEnableQueryStringHack=On + +# set up digest authentication for the webui +# to add users: htdigest /var/www/cgi-bin/cobbler/.htaccess "Cobbler WebUI Authentication" $username + + AuthType Digest + AuthName "Cobbler WebUI Authentication" + AuthDigestProvider file + AuthUserFile /var/www/cgi-bin/cobbler/.htaccess + Require valid-user + diff --git a/scripts/webui.cgi b/scripts/webui.cgi index ed0690f..1a7257d 100755 --- a/scripts/webui.cgi +++ b/scripts/webui.cgi @@ -16,6 +16,7 @@ import cgitb import Cookie import os import sys +import ConfigParser from cobbler.webui.CobblerWeb import CobblerWeb def map_modes(): @@ -44,8 +45,6 @@ def configure(): 'password': None, 'cgitb_enabled': 1 } - #config.username = 'testuser', - #config.password = 'llamas2007' # defaults if config['server'] is None: @@ -54,6 +53,17 @@ def configure(): if config['base_url'] is None: config['base_url'] = base_url() + if ( os.access('/etc/cobbler/auth.conf', os.R_OK) ): + config_parser = ConfigParser.ConfigParser() + auth_conf = open("/etc/cobbler/auth.conf") + config_parser.readfp(auth_conf) + auth_conf.close() + for auth in config_parser.items("xmlrpc_service_users"): + sys.stderr.write( str(auth) ) + if auth[1].lower() != "disabled": + config['username'] = auth[0] + config['password'] = auth[1] + return config def main(): @@ -61,7 +71,6 @@ def main(): cw_conf = configure() path = map_modes() form = cgi.parse() - cookies = Cookie.SimpleCookie(os.environ.get("HTTP_COOKIE","")) # make cgitb enablement configurable if cw_conf['cgitb_enabled'] == 1: @@ -80,12 +89,6 @@ def main(): # instantiate a CobblerWeb object cw = CobblerWeb( **cw_conf ) - # 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 if path in cw.modes(): func = getattr( cw, path ) @@ -96,11 +99,6 @@ def main(): func = getattr( cw, 'error_page' ) content = func( "Invalid Mode: \"%s\"" % path ) - # finally, get any cookies generated by the CobblerWeb object - cookie_header = cw.cookies().output() - if cookie_header: - print cookie_header - # deliver content print "Content-type: text/html" print diff --git a/setup.py b/setup.py index f094d8b..93cb058 100644 --- a/setup.py +++ b/setup.py @@ -64,6 +64,7 @@ if __name__ == "__main__": (cgipath, ['scripts/webui.cgi']), # miscellaneous config files + (cgipath, ['config/.htaccess']), (rotpath, ['config/cobblerd_rotate']), (wwwconf, ['config/cobbler.conf']), (cobpath, ['config/cobbler_hosts']), @@ -148,7 +149,6 @@ if __name__ == "__main__": (wwwtmpl, ['webui_templates/master.tmpl']), (wwwtmpl, ['webui_templates/item.tmpl']), (wwwtmpl, ['webui_templates/index.tmpl']), - (wwwtmpl, ['webui_templates/login.tmpl']), # Web UI kickstart file editing (wwwtmpl, ['webui_templates/ksfile_edit.tmpl']), diff --git a/webui_templates/index.tmpl b/webui_templates/index.tmpl index 8141734..5bbb0d7 100644 --- a/webui_templates/index.tmpl +++ b/webui_templates/index.tmpl @@ -4,10 +4,5 @@ Welcome to Cobbler. -
-
- -Please log in. - #end block body diff --git a/webui_templates/login.tmpl b/webui_templates/login.tmpl deleted file mode 100644 index 891dc4c..0000000 --- a/webui_templates/login.tmpl +++ /dev/null @@ -1,28 +0,0 @@ -#extends cobbler.webui.master - -#block body -
- -#if $message -

$message

-#end if - -
- Log In - - - - -
- - - - -
- - - -
-
-#end block body - diff --git a/webui_templates/master.tmpl b/webui_templates/master.tmpl index 58ecdfa..abd09af 100644 --- a/webui_templates/master.tmpl +++ b/webui_templates/master.tmpl @@ -28,31 +28,24 @@ -- cgit