summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--install/conf/ipa.conf81
-rw-r--r--ipalib/constants.py2
-rw-r--r--ipaserver/__init__.py4
-rw-r--r--ipaserver/plugins/xmlserver.py10
-rw-r--r--ipaserver/rpcserver.py149
-rw-r--r--ipawebui/__init__.py11
-rwxr-xr-xlite-server.py6
-rw-r--r--tests/test_ipaserver/test_rpcserver.py96
8 files changed, 276 insertions, 83 deletions
diff --git a/install/conf/ipa.conf b/install/conf/ipa.conf
index b9562936f..f5987fbea 100644
--- a/install/conf/ipa.conf
+++ b/install/conf/ipa.conf
@@ -11,14 +11,6 @@ PythonImport ipaserver main_interpreter
# This is required so the auto-configuration works with Firefox 2+
AddType application/java-archive jar
-# This is where we redirect on failed auth
-Alias /ipa/errors "/usr/share/ipa/html"
-
-# For the MIT Windows config files
-Alias /ipa/config "/usr/share/ipa/html"
-
-# For CRL publishing
-Alias /ipa/crl "/var/lib/pki-ca/publish"
<Location "/ipa">
@@ -32,34 +24,42 @@ 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
+ PythonHandler ipaserver::handler
PythonDebug Off
- PythonOption SCRIPT_NAME /ipa/xml
+ PythonOption SCRIPT_NAME /ipa
PythonAutoReload Off
-</Location>
-<Location "/ipa/json">
- SetHandler python-program
- PythonInterpreter main_interpreter
- PythonHandler ipaserver::jsonrpc
- PythonDebug Off
- PythonOption SCRIPT_NAME /ipa/json
- PythonAutoReload Off
</Location>
-<Location "/ipa/ui">
- SetHandler python-program
- PythonInterpreter main_interpreter
- PythonHandler ipaserver::webui
- PythonDebug Off
- PythonOption SCRIPT_NAME /ipa/ui
- PythonAutoReload Off
-</Location>
+#<Location "/ipa/xml">
+# SetHandler python-program
+# PythonInterpreter main_interpreter
+# PythonHandler ipaserver::xmlrpc
+# PythonDebug Off
+# PythonOption SCRIPT_NAME /ipa/xml
+# PythonAutoReload Off
+#</Location>
+
+#<Location "/ipa/json">
+# SetHandler python-program
+# PythonInterpreter main_interpreter
+# PythonHandler ipaserver::jsonrpc
+# PythonDebug Off
+# PythonOption SCRIPT_NAME /ipa/json
+# PythonAutoReload Off
+#</Location>
+
+#<Location "/ipa/ui">
+# SetHandler python-program
+# PythonInterpreter main_interpreter
+# PythonHandler ipaserver::webui
+# PythonDebug Off
+# PythonOption SCRIPT_NAME /ipa/ui
+# PythonAutoReload Off
+#</Location>
Alias /ipa-assets/ "/var/cache/ipa/assets/"
<Directory "/var/cache/ipa/assets">
@@ -72,14 +72,39 @@ Alias /ipa-assets/ "/var/cache/ipa/assets/"
</Directory>
+<Location "/ipa/errors">
+ SetHandler None
+</Location>
+
+<Location "/ipa/config">
+ SetHandler None
+</Location>
+
+<Location "/ipa/crl">
+ SetHandler None
+</Location>
+
+
+# This is where we redirect on failed auth
+Alias /ipa/errors "/usr/share/ipa/html"
+
+# For the MIT Windows config files
+Alias /ipa/config "/usr/share/ipa/html"
+
# Do no authentication on the directory that contains error messages
<Directory "/usr/share/ipa/html">
+ SetHandler None
AllowOverride None
Satisfy Any
Allow from all
</Directory>
+
+# For CRL publishing
+Alias /ipa/crl "/var/lib/pki-ca/publish"
+
<Directory "/var/lib/pki-ca/publish">
+ SetHandler None
AllowOverride None
Options Indexes FollowSymLinks
Satisfy Any
diff --git a/ipalib/constants.py b/ipalib/constants.py
index 79ddbca8f..a94207696 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -108,7 +108,7 @@ DEFAULT_CONFIG = (
('mount_ipa', '/ipa/'),
('mount_xmlserver', 'xml'),
('mount_jsonserver', 'json'),
- ('mount_webui', 'ui/'),
+ ('mount_webui', 'ui'),
('mount_webui_assets', '/ipa-assets/'),
# WebUI stuff:
diff --git a/ipaserver/__init__.py b/ipaserver/__init__.py
index 1b6225536..874ac3e24 100644
--- a/ipaserver/__init__.py
+++ b/ipaserver/__init__.py
@@ -222,3 +222,7 @@ def webui(req):
mod_python handler for web-UI requests (place holder).
"""
return adapter(req, ui)
+
+
+def handler(req):
+ return adapter(req, api.Backend.session)
diff --git a/ipaserver/plugins/xmlserver.py b/ipaserver/plugins/xmlserver.py
index cbbf14893..290bef6a2 100644
--- a/ipaserver/plugins/xmlserver.py
+++ b/ipaserver/plugins/xmlserver.py
@@ -19,17 +19,13 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
-XML-RPC client plugin.
+Loads WSGI server plugins.
"""
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
+ from ipaserver.rpcserver import session, xmlserver, jsonserver
+ api.register(session)
api.register(xmlserver)
api.register(jsonserver)
-
- class session(Executioner):
- pass
- api.register(session)
diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py
index 4a5040e2f..ad402cdf8 100644
--- a/ipaserver/rpcserver.py
+++ b/ipaserver/rpcserver.py
@@ -24,6 +24,7 @@ Also see the `ipalib.rpc` module.
"""
from cgi import parse_qs
+from xml.sax.saxutils import escape
from xmlrpclib import Fault
from ipalib.backend import Executioner
from ipalib.errors import PublicError, InternalError, CommandError, JSONError
@@ -31,6 +32,33 @@ from ipalib.request import context, Connection, destroy_context
from ipalib.rpc import xml_dumps, xml_loads
from ipalib.util import make_repr
from ipalib.compat import json
+from wsgiref.util import shift_path_info
+
+
+_not_found_template = """<html>
+<head>
+<title>404 Not Found</title>
+</head>
+<body>
+<h1>Not Found</h1>
+<p>
+The requested URL <strong>%(url)s</strong> was not found on this server.
+</p>
+</body>
+</html>"""
+
+
+def not_found(environ, start_response):
+ """
+ Return a 404 Not Found error.
+ """
+ status = '404 Not Found'
+ response_headers = [('Content-Type', 'text/html')]
+ start_response(status, response_headers)
+ output = _not_found_template % dict(
+ url=escape(environ['SCRIPT_NAME'] + environ['PATH_INFO'])
+ )
+ return [output]
def read_input(environ):
@@ -85,17 +113,81 @@ def extract_query(environ):
return query
+class session(Executioner):
+ """
+ WSGI routing middleware and entry point into IPA server.
+
+ The `session` plugin is the entry point into the IPA server. It will create
+ an LDAP connection (from a session cookie or the KRB5CCNAME header) and then
+ dispatch the request to the appropriate application. In WSGI parlance,
+ `session` is *middleware*.
+ """
+
+ def __init__(self):
+ super(session, self).__init__()
+ self.__apps = {}
+
+ def __iter__(self):
+ for key in sorted(self.__apps):
+ yield key
+
+ def __getitem__(self, key):
+ return self.__apps[key]
+
+ def __contains__(self, key):
+ return key in self.__apps
+
+ def __call__(self, environ, start_response):
+ try:
+ self.create_context(ccache=environ.get('KRB5CCNAME'))
+ return self.route(environ, start_response)
+ finally:
+ destroy_context()
+
+ def finalize(self):
+ self.url = self.env['mount_ipa']
+ super(session, self).finalize()
+
+ def route(self, environ, start_response):
+ key = shift_path_info(environ)
+ if key in self.__apps:
+ app = self.__apps[key]
+ return app(environ, start_response)
+ return not_found(environ, start_response)
+
+ def mount(self, app, key):
+ """
+ Mount the WSGI application *app* at *key*.
+ """
+# if self.__islocked__():
+# raise StandardError('%s.mount(): locked, cannot mount %r at %r' % (
+# self.name, app, key)
+# )
+ if key in self.__apps:
+ raise StandardError('%s.mount(): cannot replace %r with %r at %r' % (
+ self.name, self.__apps[key], app, key)
+ )
+ self.info('Mounting %r at %r', app, key)
+ self.__apps[key] = app
+
+
+
+
+
class WSGIExecutioner(Executioner):
"""
Base class for execution backends with a WSGI application interface.
"""
+ key = ''
+
+ def set_api(self, api):
+ super(WSGIExecutioner, self).set_api(api)
+ if 'session' in self.api.Backend:
+ self.api.Backend.session.mount(self, self.key)
+
def finalize(self):
- url = self.env['mount_' + self.name]
- if url.startswith('/'):
- self.url = url
- else:
- self.url = self.env.mount_ipa + url
+ self.url = self.env.mount_ipa + self.key
super(WSGIExecutioner, self).finalize()
def wsgi_execute(self, environ):
@@ -103,28 +195,24 @@ class WSGIExecutioner(Executioner):
error = None
_id = None
try:
- try:
- self.create_context(ccache=environ.get('KRB5CCNAME'))
- if (
- environ.get('CONTENT_TYPE', '').startswith(self.content_type)
- and environ['REQUEST_METHOD'] == 'POST'
- ):
- data = read_input(environ)
- (name, args, options, _id) = self.unmarshal(data)
- else:
- (name, args, options, _id) = self.simple_unmarshal(environ)
- if name not in self.Command:
- raise CommandError(name=name)
- result = self.Command[name](*args, **options)
- except PublicError, e:
- error = e
- except StandardError, e:
- self.exception(
- 'non-public: %s: %s', e.__class__.__name__, str(e)
- )
- error = InternalError()
- finally:
- destroy_context()
+ if (
+ environ.get('CONTENT_TYPE', '').startswith(self.content_type)
+ and environ['REQUEST_METHOD'] == 'POST'
+ ):
+ data = read_input(environ)
+ (name, args, options, _id) = self.unmarshal(data)
+ else:
+ (name, args, options, _id) = self.simple_unmarshal(environ)
+ if name not in self.Command:
+ raise CommandError(name=name)
+ result = self.Command[name](*args, **options)
+ except PublicError, e:
+ error = e
+ except StandardError, e:
+ self.exception(
+ 'non-public: %s: %s', e.__class__.__name__, str(e)
+ )
+ error = InternalError()
return self.marshal(result, error, _id)
def simple_unmarshal(self, environ):
@@ -155,11 +243,6 @@ class WSGIExecutioner(Executioner):
raise NotImplementedError('%s.marshal()' % self.fullname)
-
-class session(Executioner):
- pass
-
-
class xmlserver(WSGIExecutioner):
"""
Execution backend plugin for XML-RPC server.
@@ -168,6 +251,7 @@ class xmlserver(WSGIExecutioner):
"""
content_type = 'text/xml'
+ key = 'xml'
def finalize(self):
self.__system = {
@@ -226,6 +310,7 @@ class jsonserver(WSGIExecutioner):
"""
content_type = 'application/json'
+ key = 'json'
def marshal(self, result, error, _id=None):
if error:
diff --git a/ipawebui/__init__.py b/ipawebui/__init__.py
index 037fc7647..0e892d8a2 100644
--- a/ipawebui/__init__.py
+++ b/ipawebui/__init__.py
@@ -47,7 +47,6 @@ def join_url(base, url):
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),
@@ -60,16 +59,8 @@ class WebUI(Application):
widgets=create_widgets(),
prod=api.env.webui_prod,
)
+ self.api.Backend.session.mount(self, api.env.mount_webui)
- 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):
diff --git a/lite-server.py b/lite-server.py
index 65fb555a8..ba7cfe3d3 100755
--- a/lite-server.py
+++ b/lite-server.py
@@ -86,13 +86,11 @@ if __name__ == '__main__':
urlmap = URLMap()
apps = [
- ('XML RPC', api.Backend.xmlserver),
- ('JSON RPC', api.Backend.jsonserver),
+ ('IPA', KRBCheater(api.Backend.session)),
('Assets', AssetsApp(ui.assets)),
- ('Web UI', ui),
]
for (name, app) in apps:
- urlmap[app.url] = KRBCheater(app)
+ urlmap[app.url] = app
api.log.info('Mounting %s at %s', name, app.url)
if path.isfile(api.env.lite_pem):
diff --git a/tests/test_ipaserver/test_rpcserver.py b/tests/test_ipaserver/test_rpcserver.py
index 12d37ca30..294d349d3 100644
--- a/tests/test_ipaserver/test_rpcserver.py
+++ b/tests/test_ipaserver/test_rpcserver.py
@@ -21,13 +21,56 @@
Test the `ipaserver.rpc` module.
"""
-from tests.util import create_test_api, raises, PluginTester
+from tests.util import create_test_api, assert_equal, raises, PluginTester
from tests.data import unicode_str
from ipalib import errors, Command
from ipaserver import rpcserver
from ipalib.compat import json
+class StartResponse(object):
+ def __init__(self):
+ self.reset()
+
+ def reset(self):
+ self.status = None
+ self.headers = None
+
+ def __call__(self, status, headers):
+ assert self.status is None
+ assert self.headers is None
+ assert isinstance(status, str)
+ assert isinstance(headers, list)
+ self.status = status
+ self.headers = headers
+
+
+def test_not_found():
+ f = rpcserver.not_found
+ t = rpcserver._not_found_template
+ s = StartResponse()
+
+ # Test with an innocent URL:
+ d = dict(SCRIPT_NAME='/ipa', PATH_INFO='/foo/stuff')
+ assert_equal(
+ f(d, s),
+ [t % dict(url='/ipa/foo/stuff')]
+ )
+ assert s.status == '404 Not Found'
+ assert s.headers == [('Content-Type', 'text/html')]
+
+ # Test when URL contains any of '<>&'
+ s.reset()
+ d = dict(SCRIPT_NAME='&nbsp;', PATH_INFO='<script>do_bad_stuff();</script>')
+ assert_equal(
+ f(d, s),
+ [t % dict(url='&amp;nbsp;&lt;script&gt;do_bad_stuff();&lt;/script&gt;')]
+ )
+ assert s.status == '404 Not Found'
+ assert s.headers == [('Content-Type', 'text/html')]
+
+
+
def test_params_2_args_options():
"""
Test the `ipaserver.rpcserver.params_2_args_options` function.
@@ -42,6 +85,57 @@ def test_params_2_args_options():
assert f((options,) + args) == ((options,) + args, dict())
+class test_session(object):
+ klass = rpcserver.session
+
+ def test_route(self):
+ def app1(environ, start_response):
+ return (
+ 'from 1',
+ [environ[k] for k in ('SCRIPT_NAME', 'PATH_INFO')]
+ )
+
+ def app2(environ, start_response):
+ return (
+ 'from 2',
+ [environ[k] for k in ('SCRIPT_NAME', 'PATH_INFO')]
+ )
+
+ inst = self.klass()
+ inst.mount(app1, 'foo')
+ inst.mount(app2, 'bar')
+
+ d = dict(SCRIPT_NAME='/ipa', PATH_INFO='/foo/stuff')
+ assert inst.route(d, None) == ('from 1', ['/ipa/foo', '/stuff'])
+
+ d = dict(SCRIPT_NAME='/ipa', PATH_INFO='/bar')
+ assert inst.route(d, None) == ('from 2', ['/ipa/bar', ''])
+
+ def test_mount(self):
+ def app1(environ, start_response):
+ pass
+
+ def app2(environ, start_response):
+ pass
+
+ # Test that mount works:
+ inst = self.klass()
+ inst.mount(app1, 'foo')
+ assert inst['foo'] is app1
+ assert list(inst) == ['foo']
+
+ # Test that StandardError is raise if trying override a mount:
+ e = raises(StandardError, inst.mount, app2, 'foo')
+ assert str(e) == '%s.mount(): cannot replace %r with %r at %r' % (
+ 'session', app1, app2, 'foo'
+ )
+
+ # Test mounting a second app:
+ inst.mount(app2, 'bar')
+ assert inst['bar'] is app2
+ assert list(inst) == ['bar', 'foo']
+
+
class test_xmlserver(PluginTester):
"""
Test the `ipaserver.rpcserver.xmlserver` plugin.