Add mod_python adapter and some UI tuning

This commit is contained in:
Jason Gerard DeRose
2009-10-26 05:16:18 -06:00
parent 23b800a879
commit c4b7b70636
7 changed files with 242 additions and 70 deletions

View File

@@ -11,30 +11,6 @@ PythonImport ipaserver main_interpreter
# This is required so the auto-configuration works with Firefox 2+ # This is required so the auto-configuration works with Firefox 2+
AddType application/java-archive jar 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 # This is where we redirect on failed auth
Alias /ipa/errors "/usr/share/ipa/html" Alias /ipa/errors "/usr/share/ipa/html"
@@ -44,7 +20,8 @@ Alias /ipa/config "/usr/share/ipa/html"
# For CRL publishing # For CRL publishing
Alias /ipa/crl "/var/lib/pki-ca/publish" Alias /ipa/crl "/var/lib/pki-ca/publish"
<Location "/ipa/xml">
<Location "/ipa">
AuthType Kerberos AuthType Kerberos
AuthName "Kerberos Login" AuthName "Kerberos Login"
KrbMethodNegotiate on KrbMethodNegotiate on
@@ -55,19 +32,39 @@ Alias /ipa/crl "/var/lib/pki-ca/publish"
KrbSaveCredentials on KrbSaveCredentials on
Require valid-user Require valid-user
ErrorDocument 401 /ipa/errors/unauthorized.html ErrorDocument 401 /ipa/errors/unauthorized.html
</Location>
<Location "/ipa/xml">
SetHandler python-program SetHandler python-program
PythonInterpreter main_interpreter PythonInterpreter main_interpreter
PythonHandler ipaserver::xmlrpc PythonHandler ipaserver::xmlrpc
PythonDebug Off PythonDebug Off
PythonOption IPADebug Off PythonOption IPADebug Off
PythonOption SCRIPT_NAME /ipa/xml
# this is pointless to use since it would just reload ipaxmlrpc.py
PythonAutoReload Off PythonAutoReload Off
</Location> </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>
<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 # Do no authentication on the directory that contains error messages
<Directory "/usr/share/ipa/html"> <Directory "/usr/share/ipa/html">
AllowOverride None AllowOverride None
@@ -112,7 +109,7 @@ Alias /ipa/crl "/var/lib/pki-ca/publish"
# #
# SetHandler mod_python # SetHandler mod_python
# PythonHandler test_mod_python # PythonHandler test_mod_python
# #
# PythonDebug Off # PythonDebug Off
# #
#</Directory> #</Directory>

View File

@@ -109,6 +109,7 @@ class LDAPCreate(crud.Create):
Flag('raw', Flag('raw',
cli_name='raw', cli_name='raw',
doc='print entries as they are stored in LDAP', 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): def post_callback(self, ldap, entries, truncated, *args, **options):
pass pass

View File

@@ -21,6 +21,7 @@
Package containing server backend. Package containing server backend.
""" """
import traceback
from xmlrpclib import dumps, Fault from xmlrpclib import dumps, Fault
from ipalib import api from ipalib import api
@@ -32,44 +33,191 @@ try:
api.bootstrap(context='server', debug=True, log=None) api.bootstrap(context='server', debug=True, log=None)
api.finalize() api.finalize()
api.log.info('*** PROCESS START ***') api.log.info('*** PROCESS START ***')
import ipawebui
ui = ipawebui.create_wsgi_app(api)
except ImportError: except ImportError:
pass pass
# 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:
Handler(req).run(app)
return apache.OK
def xmlrpc(req): def xmlrpc(req):
""" """
mod_python handler for XML-RPC requests. mod_python handler for XML-RPC requests.
""" """
if req.method != 'POST': return adapter(req, api.Backend.xmlserver)
req.allow_methods(['POST'], 1)
return apache.HTTP_METHOD_NOT_ALLOWED
if apache.mpm_query(apache.AP_MPMQ_IS_THREADED):
response = dumps(
Fault(3, 'Apache must use the forked model'),
methodresponse=True,
)
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)
return apache.OK
def jsonrpc(req): def jsonrpc(req):
""" """
mod_python handler for JSON-RPC requests (place holder). mod_python handler for JSON-RPC requests (place holder).
""" """
return adapter(req, api.Backend.jsonserver)
def webui(req): def webui(req):
""" """
mod_python handler for web-UI requests (place holder). mod_python handler for web-UI requests (place holder).
""" """
return adapter(req, ui)

View File

@@ -26,5 +26,10 @@ from ipalib import api
if 'in_server' in api.env and api.env.in_server is True: if 'in_server' in api.env and api.env.in_server is True:
from ipaserver.rpcserver import xmlserver, jsonserver from ipaserver.rpcserver import xmlserver, jsonserver
from ipalib.backend import Executioner
api.register(xmlserver) api.register(xmlserver)
api.register(jsonserver) api.register(jsonserver)
class session(Executioner):
pass
api.register(session)

View File

@@ -155,6 +155,11 @@ class WSGIExecutioner(Executioner):
raise NotImplementedError('%s.marshal()' % self.fullname) raise NotImplementedError('%s.marshal()' % self.fullname)
class session(Executioner):
pass
class xmlserver(WSGIExecutioner): class xmlserver(WSGIExecutioner):
""" """
Execution backend plugin for XML-RPC server. Execution backend plugin for XML-RPC server.

View File

@@ -20,6 +20,9 @@
IPA web UI. 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 controllers import JSON
from engine import Engine from engine import Engine
from widgets import create_widgets from widgets import create_widgets
@@ -34,20 +37,36 @@ def join_url(base, url):
return base + url return base + url
def create_wsgi_app(api): class WebUI(Application):
baseurl = api.env.mount_ipa def __init__(self, api):
assets = Assets( self.api = api
url=join_url(baseurl, api.env.mount_webui_assets), self.session = api.Backend.session
dir=api.env.webui_assets_dir, baseurl = api.env.mount_ipa
prod=api.env.webui_prod, assets = Assets(
) url=join_url(baseurl, api.env.mount_webui_assets),
app = Application( dir=api.env.webui_assets_dir,
url=join_url(baseurl, api.env.mount_webui), prod=api.env.webui_prod,
assets=assets, )
widgets=create_widgets(), super(WebUI, self).__init__(
prod=api.env.webui_prod, 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 = Engine(api, app)
engine.build() engine.build()

View File

@@ -86,16 +86,12 @@ class Engine(object):
) )
def build(self): 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) self.pages[cmd.name] = self.build_page(cmd)
for page in self.pages.itervalues(): for page in self.pages.itervalues():
page.menu.label = 'Users' page.menu.label = 'Users'
self.add_object_menuitems(page.menu, 'user') 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: # Add in the info pages:
page = self.app.new('PageApp', id='api', title='api') page = self.app.new('PageApp', id='api', title='api')
page.view.add( page.view.add(
@@ -143,6 +139,8 @@ class Engine(object):
table = self.app.new('FieldTable') table = self.app.new('FieldTable')
page.view.add(table) page.view.add(table)
for param in cmd.params(): for param in cmd.params():
if param.exclude and 'webui' in param.exclude:
continue
field = self.param_mapper(param, cmd) field = self.param_mapper(param, cmd)
table.add(field) table.add(field)