diff options
-rw-r--r-- | install/conf/ipa.conf | 53 | ||||
-rw-r--r-- | ipalib/plugins/baseldap.py | 2 | ||||
-rw-r--r-- | ipaserver/__init__.py | 182 | ||||
-rw-r--r-- | ipaserver/plugins/xmlserver.py | 5 | ||||
-rw-r--r-- | ipaserver/rpcserver.py | 5 | ||||
-rw-r--r-- | ipawebui/__init__.py | 45 | ||||
-rw-r--r-- | ipawebui/engine.py | 8 |
7 files changed, 236 insertions, 64 deletions
diff --git a/install/conf/ipa.conf b/install/conf/ipa.conf index 5ca13d37b..032be20b2 100644 --- a/install/conf/ipa.conf +++ b/install/conf/ipa.conf @@ -11,30 +11,6 @@ PythonImport ipaserver main_interpreter # This is required so the auto-configuration works with Firefox 2+ AddType application/java-archive jar -<ProxyMatch ^.*/ipa/ui.*$$> - AuthType Kerberos - AuthName "Kerberos Login" - KrbMethodNegotiate on - KrbMethodK5Passwd off - KrbServiceName HTTP - KrbAuthRealms $REALM - Krb5KeyTab /etc/httpd/conf/ipa.keytab - KrbSaveCredentials on - Require valid-user - ErrorDocument 401 /ipa/errors/unauthorized.html - RewriteEngine on - Order deny,allow - Allow from all - - RequestHeader set X-Forwarded-Keytab %{KRB5CCNAME}e - - # RequestHeader unset Authorization -</ProxyMatch> - -# The URI's with a trailing ! are those that aren't handled by the proxy -ProxyPass /ipa/ui http://localhost:8080/ipa/ui -ProxyPassReverse /ipa/ui http://localhost:8080/ipa/ui - # This is where we redirect on failed auth Alias /ipa/errors "/usr/share/ipa/html" @@ -44,7 +20,8 @@ Alias /ipa/config "/usr/share/ipa/html" # For CRL publishing Alias /ipa/crl "/var/lib/pki-ca/publish" -<Location "/ipa/xml"> + +<Location "/ipa"> AuthType Kerberos AuthName "Kerberos Login" KrbMethodNegotiate on @@ -55,19 +32,39 @@ Alias /ipa/crl "/var/lib/pki-ca/publish" KrbSaveCredentials on Require valid-user ErrorDocument 401 /ipa/errors/unauthorized.html +</Location> +<Location "/ipa/xml"> SetHandler python-program PythonInterpreter main_interpreter PythonHandler ipaserver::xmlrpc - PythonDebug Off + PythonOption IPADebug Off + PythonOption SCRIPT_NAME /ipa/xml + PythonAutoReload Off +</Location> +<Location "/ipa/json"> + SetHandler python-program + PythonInterpreter main_interpreter + PythonHandler ipaserver::jsonrpc + PythonDebug Off PythonOption IPADebug Off + PythonOption SCRIPT_NAME /ipa/json + PythonAutoReload Off +</Location> - # this is pointless to use since it would just reload ipaxmlrpc.py +<Location "/ipa/ui"> + SetHandler python-program + PythonInterpreter main_interpreter + PythonHandler ipaserver::webui + PythonDebug Off + PythonOption IPADebug Off + PythonOption SCRIPT_NAME /ipa/ui PythonAutoReload Off </Location> + # Do no authentication on the directory that contains error messages <Directory "/usr/share/ipa/html"> AllowOverride None @@ -112,7 +109,7 @@ Alias /ipa/crl "/var/lib/pki-ca/publish" # # SetHandler mod_python # PythonHandler test_mod_python -# +# # PythonDebug Off # #</Directory> diff --git a/ipalib/plugins/baseldap.py b/ipalib/plugins/baseldap.py index fe0edaeaf..3798d8388 100644 --- a/ipalib/plugins/baseldap.py +++ b/ipalib/plugins/baseldap.py @@ -109,6 +109,7 @@ class LDAPCreate(crud.Create): Flag('raw', cli_name='raw', doc='print entries as they are stored in LDAP', + exclude='webui', ), ) @@ -613,4 +614,3 @@ class LDAPSearch(crud.Search): def post_callback(self, ldap, entries, truncated, *args, **options): pass - diff --git a/ipaserver/__init__.py b/ipaserver/__init__.py index 7fdba6246..678120b87 100644 --- a/ipaserver/__init__.py +++ b/ipaserver/__init__.py @@ -21,6 +21,7 @@ Package containing server backend. """ +import traceback from xmlrpclib import dumps, Fault from ipalib import api @@ -32,44 +33,191 @@ try: api.bootstrap(context='server', debug=True, log=None) api.finalize() api.log.info('*** PROCESS START ***') + import ipawebui + ui = ipawebui.create_wsgi_app(api) except ImportError: pass -def xmlrpc(req): - """ - mod_python handler for XML-RPC requests. - """ - if req.method != 'POST': - req.allow_methods(['POST'], 1) - return apache.HTTP_METHOD_NOT_ALLOWED +# This module is from paste: +# http://pythonpaste.org/ +# Which in turn was based on Robert Brewer's: +# http://projects.amor.org/misc/svn/modpython_gateway.py + +class InputWrapper(object): + + def __init__(self, req): + self.req = req + + def close(self): + pass + + def read(self, size=-1): + return self.req.read(size) + + def readline(self, size=-1): + return self.req.readline(size) + + def readlines(self, hint=-1): + return self.req.readlines(hint) + + def __iter__(self): + line = self.readline() + while line: + yield line + # Notice this won't prefetch the next line; it only + # gets called if the generator is resumed. + line = self.readline() + + +class ErrorWrapper(object): + + def __init__(self, req): + self.req = req + + def flush(self): + pass + + def write(self, msg): + self.req.log_error(msg) + + def writelines(self, seq): + self.write(''.join(seq)) + + +bad_value = ("You must provide a PythonOption '%s', either 'on' or 'off', " + "when running a version of mod_python < 3.1") + + +class Handler(object): + + def __init__(self, req): + self.started = False + + options = req.get_options() + + # Threading and forking + try: + q = apache.mpm_query + threaded = q(apache.AP_MPMQ_IS_THREADED) + forked = q(apache.AP_MPMQ_IS_FORKED) + except AttributeError: + threaded = options.get('multithread', '').lower() + if threaded == 'on': + threaded = True + elif threaded == 'off': + threaded = False + else: + raise ValueError(bad_value % "multithread") + + forked = options.get('multiprocess', '').lower() + if forked == 'on': + forked = True + elif forked == 'off': + forked = False + else: + raise ValueError(bad_value % "multiprocess") + + env = self.environ = dict(apache.build_cgi_env(req)) + + if 'SCRIPT_NAME' in options: + # Override SCRIPT_NAME and PATH_INFO if requested. + env['SCRIPT_NAME'] = options['SCRIPT_NAME'] + env['PATH_INFO'] = req.uri[len(options['SCRIPT_NAME']):] + else: + env['SCRIPT_NAME'] = '' + env['PATH_INFO'] = req.uri + + env['wsgi.input'] = InputWrapper(req) + env['wsgi.errors'] = ErrorWrapper(req) + env['wsgi.version'] = (1, 0) + env['wsgi.run_once'] = False + if env.get("HTTPS") in ('yes', 'on', '1'): + env['wsgi.url_scheme'] = 'https' + else: + env['wsgi.url_scheme'] = 'http' + env['wsgi.multithread'] = threaded + env['wsgi.multiprocess'] = forked + + self.request = req + + def run(self, application): + try: + result = application(self.environ, self.start_response) + for data in result: + self.write(data) + if not self.started: + self.request.set_content_length(0) + if hasattr(result, 'close'): + result.close() + except: + traceback.print_exc(None, self.environ['wsgi.errors']) + if not self.started: + self.request.status = 500 + self.request.content_type = 'text/plain' + data = "A server error occurred. Please contact the administrator." + self.request.set_content_length(len(data)) + self.request.write(data) + + def start_response(self, status, headers, exc_info=None): + if exc_info: + try: + if self.started: + raise exc_info[0], exc_info[1], exc_info[2] + finally: + exc_info = None + + self.request.status = int(status[:3]) + + for key, val in headers: + if key.lower() == 'content-length': + self.request.set_content_length(int(val)) + elif key.lower() == 'content-type': + self.request.content_type = val + else: + self.request.headers_out.add(key, val) + + return self.write + + def write(self, data): + if not self.started: + self.started = True + self.request.write(data) + +# END module from paste + + +def adapter(req, app): if apache.mpm_query(apache.AP_MPMQ_IS_THREADED): response = dumps( Fault(3, 'Apache must use the forked model'), methodresponse=True, ) + req.content_type = 'text/xml' + req.set_content_length(len(response)) + req.write(response) else: - req.add_common_vars() - response = api.Backend.xmlserver.marshaled_dispatch( - req.read(), - req.subprocess_env.get('KRB5CCNAME'), - req.connection.remote_ip - ) - - req.content_type = 'text/xml' - req.set_content_length(len(response)) - req.write(response) + Handler(req).run(app) return apache.OK +def xmlrpc(req): + """ + mod_python handler for XML-RPC requests. + """ + return adapter(req, api.Backend.xmlserver) + + def jsonrpc(req): """ mod_python handler for JSON-RPC requests (place holder). """ + return adapter(req, api.Backend.jsonserver) def webui(req): """ mod_python handler for web-UI requests (place holder). """ + return adapter(req, ui) diff --git a/ipaserver/plugins/xmlserver.py b/ipaserver/plugins/xmlserver.py index 10c94d452..cbbf14893 100644 --- a/ipaserver/plugins/xmlserver.py +++ b/ipaserver/plugins/xmlserver.py @@ -26,5 +26,10 @@ from ipalib import api if 'in_server' in api.env and api.env.in_server is True: from ipaserver.rpcserver import xmlserver, jsonserver + from ipalib.backend import Executioner api.register(xmlserver) api.register(jsonserver) + + class session(Executioner): + pass + api.register(session) diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py index ab8b41720..24213dd67 100644 --- a/ipaserver/rpcserver.py +++ b/ipaserver/rpcserver.py @@ -155,6 +155,11 @@ class WSGIExecutioner(Executioner): raise NotImplementedError('%s.marshal()' % self.fullname) + +class session(Executioner): + pass + + class xmlserver(WSGIExecutioner): """ Execution backend plugin for XML-RPC server. diff --git a/ipawebui/__init__.py b/ipawebui/__init__.py index ba14438fd..9f86da344 100644 --- a/ipawebui/__init__.py +++ b/ipawebui/__init__.py @@ -20,6 +20,9 @@ IPA web UI. """ +from ipalib.backend import Executioner +from ipalib.request import destroy_context +from ipaserver.rpcserver import extract_query from controllers import JSON from engine import Engine from widgets import create_widgets @@ -34,20 +37,36 @@ def join_url(base, url): return base + url -def create_wsgi_app(api): - baseurl = api.env.mount_ipa - assets = Assets( - url=join_url(baseurl, api.env.mount_webui_assets), - dir=api.env.webui_assets_dir, - prod=api.env.webui_prod, - ) - app = Application( - url=join_url(baseurl, api.env.mount_webui), - assets=assets, - widgets=create_widgets(), - prod=api.env.webui_prod, - ) +class WebUI(Application): + def __init__(self, api): + self.api = api + self.session = api.Backend.session + baseurl = api.env.mount_ipa + assets = Assets( + url=join_url(baseurl, api.env.mount_webui_assets), + dir=api.env.webui_assets_dir, + prod=api.env.webui_prod, + ) + super(WebUI, self).__init__( + url=join_url(baseurl, api.env.mount_webui), + assets=assets, + widgets=create_widgets(), + prod=api.env.webui_prod, + ) + + def __call__(self, environ, start_response): + self.session.create_context(ccache=environ.get('KRB5CCNAME')) + try: + query = extract_query(environ) + print query + response = super(WebUI, self).__call__(environ, start_response) + finally: + destroy_context() + return response + +def create_wsgi_app(api): + app = WebUI(api) engine = Engine(api, app) engine.build() diff --git a/ipawebui/engine.py b/ipawebui/engine.py index a455c0e70..ea0905d49 100644 --- a/ipawebui/engine.py +++ b/ipawebui/engine.py @@ -86,16 +86,12 @@ class Engine(object): ) def build(self): - for cmd in self.api.Command(): + for cmd in self.api.Object.user.methods(): self.pages[cmd.name] = self.build_page(cmd) for page in self.pages.itervalues(): page.menu.label = 'Users' self.add_object_menuitems(page.menu, 'user') - menu = page.new('Menu', label='Groups') - page.menuset.add(menu) - self.add_object_menuitems(menu, 'group') - # Add in the info pages: page = self.app.new('PageApp', id='api', title='api') page.view.add( @@ -143,6 +139,8 @@ class Engine(object): table = self.app.new('FieldTable') page.view.add(table) for param in cmd.params(): + if param.exclude and 'webui' in param.exclude: + continue field = self.param_mapper(param, cmd) table.add(field) |