mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Add mod_python adapter and some UI tuning
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user