summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJason Gerard DeRose <jderose@redhat.com>2009-10-26 05:16:18 -0600
committerJason Gerard DeRose <jderose@redhat.com>2009-10-27 21:38:13 -0600
commitc4b7b70636fb34e48fde0e740e690fe9c5cc13cc (patch)
treed49e9211387d070bfe785365dda465b0a18fdf6d
parent23b800a879afcbb49a2ce095cbe2e309a26976a7 (diff)
downloadfreeipa-c4b7b70636fb34e48fde0e740e690fe9c5cc13cc.tar.gz
freeipa-c4b7b70636fb34e48fde0e740e690fe9c5cc13cc.tar.xz
freeipa-c4b7b70636fb34e48fde0e740e690fe9c5cc13cc.zip
Add mod_python adapter and some UI tuning
-rw-r--r--install/conf/ipa.conf53
-rw-r--r--ipalib/plugins/baseldap.py2
-rw-r--r--ipaserver/__init__.py182
-rw-r--r--ipaserver/plugins/xmlserver.py5
-rw-r--r--ipaserver/rpcserver.py5
-rw-r--r--ipawebui/__init__.py45
-rw-r--r--ipawebui/engine.py8
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)