42
42
from ivle import util
44
45
from ivle.dispatch.request import Request
45
import ivle.webapp.security
46
from ivle.webapp.base.plugins import ViewPlugin, PublicViewPlugin
47
from ivle.webapp.errors import HTTPError, Unauthorized
49
def generate_route_mapper(view_plugins, attr):
46
from ivle.dispatch import login
49
import plugins.console # XXX: Relies on www/ being in the Python path.
51
# XXX List of plugins, which will eventually be read in from conf
53
'ivle.webapp.core#Plugin',
54
'ivle.webapp.admin.user#Plugin',
55
'ivle.webapp.tutorial#Plugin',
56
'ivle.webapp.admin.subject#Plugin',
57
'ivle.webapp.filesystem.browser#Plugin',
58
'ivle.webapp.filesystem.diff#Plugin',
59
'ivle.webapp.filesystem.svnlog#Plugin',
60
'ivle.webapp.groups#Plugin',
61
'ivle.webapp.console#Plugin',
62
'ivle.webapp.security#Plugin',
63
'ivle.webapp.media#Plugin',
66
def generate_route_mapper(plugins):
51
68
Build a Mapper object for doing URL matching using 'routes', based on the
52
69
given plugin registry.
54
71
m = routes.Mapper(explicit=True)
55
for plugin in view_plugins:
56
73
# Establish a URL pattern for each element of plugin.urls
57
assert hasattr(plugin, 'urls'), "%r does not have any urls" % plugin
58
for url in getattr(plugin, attr):
74
if not hasattr(plugins[name], 'urls'):
76
for url in plugins[name].urls:
60
78
view_class = url[1]
61
79
kwargs_dict = url[2] if len(url) >= 3 else {}
62
80
m.connect(routex, view=view_class, **kwargs_dict)
65
def handler(apachereq):
66
"""Handles an HTTP request.
83
def get_plugin(pluginstr):
84
plugin_path, classname = pluginstr.split('#')
85
# Load the plugin module from somewhere in the Python path
86
# (Note that plugin_path is a fully-qualified Python module name).
88
getattr(__import__(plugin_path, fromlist=[classname]), classname))
91
"""Handles a request which may be to anywhere in the site except media.
68
92
Intended to be called by mod_python, as a handler.
70
@param apachereq: An Apache request object.
72
# Make the request object into an IVLE request which can be given to views
73
req = Request(apachereq)
94
req: An Apache request object.
96
# Make the request object into an IVLE request which can be passed to apps
99
req = Request(req, html.write_html_head)
101
# Pass the apachereq to error reporter, since ivle req isn't created
103
handle_unknown_exception(apachereq, *sys.exc_info())
104
# Tell Apache not to generate its own errors as well
105
return mod_python.apache.OK
107
# Run the main handler, and catch all exceptions
109
return handler_(req, apachereq)
110
except mod_python.apache.SERVER_RETURN:
111
# An apache error. We discourage these, but they might still happen.
115
handle_unknown_exception(req, *sys.exc_info())
116
# Tell Apache not to generate its own errors as well
117
return mod_python.apache.OK
119
def handler_(req, apachereq):
121
Nested handler function. May raise exceptions. The top-level handler is
122
just used to catch exceptions.
123
Takes both an IVLE request and an Apache req.
75
125
# Hack? Try and get the user login early just in case we throw an error
76
126
# (most likely 404) to stop us seeing not logged in even when we are.
77
127
if not req.publicmode:
78
user = ivle.webapp.security.get_user_details(req)
80
# Don't set the user if it is disabled or hasn't accepted the ToS.
81
if user and user.valid:
84
conf = ivle.config.Config()
88
req.mapper = generate_route_mapper(conf.plugin_index[PublicViewPlugin],
91
req.mapper = generate_route_mapper(conf.plugin_index[ViewPlugin],
128
req.user = login.get_user_details(req)
130
### BEGIN New plugins framework ###
131
# XXX This should be done ONCE per Python process, not per request.
133
# XXX No authentication is done here
134
req.plugins = dict([get_plugin(pluginstr) for pluginstr in plugins_HACK])
135
req.reverse_plugins = dict([(v, k) for (k, v) in req.plugins.items()])
136
req.mapper = generate_route_mapper(req.plugins)
94
138
matchdict = req.mapper.match(req.uri)
95
139
if matchdict is not None:
98
142
# (The latter two seem to be built-in, and we don't want them).
99
143
kwargs = matchdict.copy()
100
144
del kwargs['view']
102
# Instantiate the view, which should be a BaseView class
103
view = viewcls(req, **kwargs)
105
# Check that the request (mainly the user) is permitted to access
107
if not view.authorize(req):
112
# A view explicitly raised an HTTP error. Respect it.
115
# Try to find a custom error view.
116
if hasattr(viewcls, 'get_error_view'):
117
errviewcls = viewcls.get_error_view(e)
122
errview = errviewcls(req, e)
131
# A non-HTTPError appeared. We have an unknown exception. Panic.
132
handle_unknown_exception(req, *sys.exc_info())
145
# Instantiate the view, which should be a BaseView class
146
view = viewcls(req, **kwargs)
151
### END New plugins framework ###
153
# Check req.app to see if it is valid. 404 if not.
154
if req.app is not None and req.app not in ivle.conf.apps.app_url:
155
req.throw_error(Request.HTTP_NOT_FOUND,
156
"There is no application called %s." % repr(req.app))
158
# Special handling for public mode - only allow the public app, call it
160
# NOTE: This will not behave correctly if the public app uses
161
# write_html_head_foot, but "serve" does not.
163
if req.app != ivle.conf.apps.public_app:
164
req.throw_error(Request.HTTP_FORBIDDEN,
165
"This application is not available on the public site.")
166
app = ivle.conf.apps.app_url[ivle.conf.apps.public_app]
167
apps.call_app(app.dir, req)
170
# app is the App object for the chosen app
172
app = ivle.conf.apps.app_url[ivle.conf.apps.default_app]
174
app = ivle.conf.apps.app_url[req.app]
176
# Check if app requires auth. If so, perform authentication and login.
177
# This will either return a User object, None, or perform a redirect
178
# which we will not catch here.
180
req.user = login.login(req)
181
logged_in = req.user is not None
183
req.user = login.get_user_details(req)
187
# Keep the user's session alive by writing to the session object.
188
# req.get_session().save()
189
# Well, it's a fine idea, but it creates considerable grief in the
190
# concurrent update department, so instead, we'll just make the
191
# sessions not time out.
192
req.get_session().unlock()
194
# If user did not specify an app, HTTP redirect to default app and
197
req.throw_redirect(util.make_path(ivle.conf.apps.default_app))
199
# Set the default title to the app's tab name, if any. Otherwise URL
201
if app.name is not None:
138
return req.HTTP_NOT_FOUND # TODO: Prettify.
206
# Call the specified app with the request object
207
apps.call_app(app.dir, req)
209
# if not logged in, login.login will have written the login box.
210
# Just clean up and exit.
212
# MAKE SURE we write the HTTP (and possibly HTML) header. This
213
# wouldn't happen if nothing else ever got written, so we have to make
215
req.ensure_headers_written()
217
# When done, write out the HTML footer if the app has requested it
218
if req.write_html_head_foot:
219
# Show the console if required
220
if logged_in and app.useconsole:
221
plugins.console.present(req, windowpane=True)
222
html.write_html_foot(req)
224
# Note: Apache will not write custom HTML error messages here.
225
# Use req.throw_error to do that.
140
228
def handle_unknown_exception(req, exc_type, exc_value, exc_traceback):
187
# A "bad" error message. We shouldn't get here unless IVLE
188
# misbehaves (which is currently very easy, if things aren't set up
190
# Write the traceback.
191
# If this is a non-4xx IVLEError, get the message and httpcode and
192
# make the error message a bit nicer (but still include the
194
# We also need to special-case IVLEJailError, as we can get another
195
# almost-exception out of it.
197
codename, msg = None, None
199
if exc_type is util.IVLEJailError:
200
msg = exc_value.type_str + ": " + exc_value.message
201
tb = 'Exception information extracted from IVLEJailError:\n'
202
tb += urllib.unquote(exc_value.info)
284
logging.debug('Logging Unhandled Exception')
286
# We handle 3 types of error.
287
# IVLEErrors with 4xx response codes (client error).
288
# IVLEErrors with 5xx response codes (handled server error).
289
# Other exceptions (unhandled server error).
290
# IVLEErrors should not have other response codes than 4xx or 5xx
291
# (eg. throw_redirect should have been used for 3xx codes).
292
# Therefore, that is treated as an unhandled error.
294
if (exc_type == util.IVLEError and httpcode >= 400
295
and httpcode <= 499):
296
# IVLEErrors with 4xx response codes are client errors.
297
# Therefore, these have a "nice" response (we even coat it in the IVLE
300
req.write_html_head_foot = True
301
req.write_javascript_settings = False
302
req.write('<div id="ivle_padding">\n')
205
304
codename, msg = req.get_http_codename(httpcode)
206
305
except AttributeError:
209
tb = ''.join(traceback.format_exception(exc_type, exc_value,
212
logging.error('%s\n%s'%(str(msg), tb))
214
# Error messages are only displayed is the user is NOT a student,
215
# or if there has been a problem logging the error message
216
show_errors = (not publicmode) and ((login and \
217
str(role) != "student") or logfail)
218
req.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
306
codename, msg = None, None
307
# Override the default message with the supplied one,
309
if exc_value.message is not None:
310
msg = exc_value.message
311
if codename is not None:
312
req.write("<h1>Error: %s</h1>\n" % cgi.escape(codename))
314
req.write("<h1>Error</h1>\n")
316
req.write("<p>%s</p>\n" % cgi.escape(msg))
318
req.write("<p>An unknown error occured.</p>\n")
321
logging.info(str(msg))
323
req.write("<p>(HTTP error code %d)</p>\n" % httpcode)
325
req.write("<p>Warning: Could not open Error Log: '%s'</p>\n"
326
%cgi.escape(logfile))
327
req.write('</div>\n')
328
html.write_html_foot(req)
330
# A "bad" error message. We shouldn't get here unless IVLE
331
# misbehaves (which is currently very easy, if things aren't set up
333
# Write the traceback.
334
# If this is a non-4xx IVLEError, get the message and httpcode and
335
# make the error message a bit nicer (but still include the
337
# We also need to special-case IVLEJailError, as we can get another
338
# almost-exception out of it.
340
codename, msg = None, None
342
if exc_type is util.IVLEJailError:
343
msg = exc_value.type_str + ": " + exc_value.message
344
tb = 'Exception information extracted from IVLEJailError:\n'
345
tb += urllib.unquote(exc_value.info)
348
codename, msg = req.get_http_codename(httpcode)
349
except AttributeError:
351
# Override the default message with the supplied one,
353
if hasattr(exc_value, 'message') and exc_value.message is not None:
354
msg = exc_value.message
355
# Prepend the exception type
356
if exc_type != util.IVLEError:
357
msg = exc_type.__name__ + ": " + msg
359
tb = ''.join(traceback.format_exception(exc_type, exc_value,
363
logging.error('%s\n%s'%(str(msg), tb))
364
# Error messages are only displayed is the user is NOT a student,
365
# or if there has been a problem logging the error message
366
show_errors = (not publicmode) and ((login and \
367
str(role) != "student") or logfail)
368
req.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
219
369
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
220
370
<html xmlns="http://www.w3.org/1999/xhtml">
221
371
<head><title>IVLE Internal Server Error</title></head>
223
373
<h1>IVLE Internal Server Error""")
225
if codename is not None and \
226
httpcode != mod_python.apache.HTTP_INTERNAL_SERVER_ERROR:
227
req.write(": %s" % cgi.escape(codename))
375
if (codename is not None
376
and httpcode != mod_python.apache.HTTP_INTERNAL_SERVER_ERROR):
377
req.write(": %s" % cgi.escape(codename))
230
380
<p>An error has occured which is the fault of the IVLE developers or
234
req.write("Please report this issue to the server administrators, "
235
"along with the following information.")
237
req.write("Details have been logged for further examination.")
242
req.write("<p>%s</p>\n" % cgi.escape(msg))
243
if httpcode is not None:
244
req.write("<p>(HTTP error code %d)</p>\n" % httpcode)
245
req.write("<h2>Debugging information</h2>")
247
req.write("<pre>\n%s\n</pre>\n"%cgi.escape(tb))
248
req.write("</body></html>")
381
administration. The developers have been notified.</p>
385
req.write("<p>%s</p>\n" % cgi.escape(msg))
386
if httpcode is not None:
387
req.write("<p>(HTTP error code %d)</p>\n" % httpcode)
389
<p>Please report this to <a href="mailto:%s">%s</a> (the system
390
administrator). Include the following information:</p>
391
""" % (cgi.escape(admin_email), cgi.escape(admin_email)))
393
req.write("<pre>\n%s\n</pre>\n"%cgi.escape(tb))
395
req.write("<p>Warning: Could not open Error Log: '%s'</p>\n"
396
%cgi.escape(logfile))
397
req.write("</body></html>")