43
42
from ivle import util
46
44
from ivle.dispatch.request import Request
47
from ivle.dispatch import login
48
from ivle.webapp.base.plugins import ViewPlugin
49
from ivle.webapp.errors import HTTPError
53
# XXX List of plugins, which will eventually be read in from conf
55
'ivle.webapp.core#Plugin',
56
'ivle.webapp.admin.user#Plugin',
57
'ivle.webapp.tutorial#Plugin',
58
'ivle.webapp.admin.subject#Plugin',
59
'ivle.webapp.filesystem.browser#Plugin',
60
'ivle.webapp.filesystem.diff#Plugin',
61
'ivle.webapp.filesystem.svnlog#Plugin',
62
'ivle.webapp.groups#Plugin',
63
'ivle.webapp.console#Plugin',
64
'ivle.webapp.security#Plugin',
65
'ivle.webapp.media#Plugin',
66
'ivle.webapp.forum#Plugin',
67
'ivle.webapp.help#Plugin',
68
'ivle.webapp.tos#Plugin',
71
def generate_route_mapper(view_plugins):
45
import ivle.webapp.security
46
from ivle.webapp.base.plugins import ViewPlugin, PublicViewPlugin
47
from ivle.webapp.base.xhtml import XHTMLView, XHTMLErrorView
48
from ivle.webapp.errors import HTTPError, Unauthorized, NotFound
50
def generate_route_mapper(view_plugins, attr):
73
52
Build a Mapper object for doing URL matching using 'routes', based on the
74
53
given plugin registry.
77
56
for plugin in view_plugins:
78
57
# Establish a URL pattern for each element of plugin.urls
79
58
assert hasattr(plugin, 'urls'), "%r does not have any urls" % plugin
80
for url in plugin.urls:
59
for url in getattr(plugin, attr):
82
61
view_class = url[1]
83
62
kwargs_dict = url[2] if len(url) >= 3 else {}
84
63
m.connect(routex, view=view_class, **kwargs_dict)
87
def get_plugin(pluginstr):
88
plugin_path, classname = pluginstr.split('#')
89
# Load the plugin module from somewhere in the Python path
90
# (Note that plugin_path is a fully-qualified Python module name).
92
getattr(__import__(plugin_path, fromlist=[classname]), classname))
66
def handler(apachereq):
67
"""Handles an HTTP request.
95
"""Handles a request which may be to anywhere in the site except media.
96
69
Intended to be called by mod_python, as a handler.
98
req: An Apache request object.
100
# Make the request object into an IVLE request which can be passed to apps
103
req = Request(req, html.write_html_head)
105
# Pass the apachereq to error reporter, since ivle req isn't created
107
handle_unknown_exception(apachereq, *sys.exc_info())
108
# Tell Apache not to generate its own errors as well
109
return mod_python.apache.OK
111
# Run the main handler, and catch all exceptions
113
return handler_(req, apachereq)
114
except mod_python.apache.SERVER_RETURN:
115
# An apache error. We discourage these, but they might still happen.
119
handle_unknown_exception(req, *sys.exc_info())
120
# Tell Apache not to generate its own errors as well
121
return mod_python.apache.OK
123
def handler_(req, apachereq):
125
Nested handler function. May raise exceptions. The top-level handler is
126
just used to catch exceptions.
127
Takes both an IVLE request and an Apache req.
71
@param apachereq: An Apache request object.
73
# Make the request object into an IVLE request which can be given to views
74
req = Request(apachereq)
129
76
# Hack? Try and get the user login early just in case we throw an error
130
77
# (most likely 404) to stop us seeing not logged in even when we are.
131
78
if not req.publicmode:
132
req.user = login.get_user_details(req)
134
### BEGIN New plugins framework ###
135
# XXX This should be done ONCE per Python process, not per request.
137
# XXX No authentication is done here
138
req.plugins = dict([get_plugin(pluginstr) for pluginstr in plugins_HACK])
139
# Index the plugins by base class
140
req.plugin_index = {}
141
for plugin in req.plugins.values():
142
# Getmro returns a tuple of all the super-classes of the plugin
143
for base in inspect.getmro(plugin):
144
if base not in req.plugin_index:
145
req.plugin_index[base] = []
146
req.plugin_index[base].append(plugin)
147
req.reverse_plugins = dict([(v, k) for (k, v) in req.plugins.items()])
148
req.mapper = generate_route_mapper(req.plugin_index[ViewPlugin])
79
user = ivle.webapp.security.get_user_details(req)
81
# Don't set the user if it is disabled or hasn't accepted the ToS.
82
if user and user.valid:
85
conf = ivle.config.Config()
89
req.mapper = generate_route_mapper(conf.plugin_index[PublicViewPlugin],
92
req.mapper = generate_route_mapper(conf.plugin_index[ViewPlugin],
150
95
matchdict = req.mapper.match(req.uri)
151
96
if matchdict is not None:
184
136
req.store.commit()
186
### END New plugins framework ###
188
# Check req.app to see if it is valid. 404 if not.
189
if req.app is not None and req.app not in ivle.conf.apps.app_url:
190
req.throw_error(Request.HTTP_NOT_FOUND,
191
"There is no application called %s." % repr(req.app))
193
# Special handling for public mode - only allow the public app, call it
195
# NOTE: This will not behave correctly if the public app uses
196
# write_html_head_foot, but "serve" does not.
198
if req.app != ivle.conf.apps.public_app:
199
req.throw_error(Request.HTTP_FORBIDDEN,
200
"This application is not available on the public site.")
201
app = ivle.conf.apps.app_url[ivle.conf.apps.public_app]
202
apps.call_app(app.dir, req)
139
XHTMLErrorView(req, NotFound()).render(req)
205
# app is the App object for the chosen app
207
app = ivle.conf.apps.app_url[ivle.conf.apps.default_app]
209
app = ivle.conf.apps.app_url[req.app]
211
# Check if app requires auth. If so, perform authentication and login.
212
# This will either return a User object, None, or perform a redirect
213
# which we will not catch here.
215
req.user = login.login(req)
216
logged_in = req.user is not None
218
req.user = login.get_user_details(req)
222
# Keep the user's session alive by writing to the session object.
223
# req.get_session().save()
224
# Well, it's a fine idea, but it creates considerable grief in the
225
# concurrent update department, so instead, we'll just make the
226
# sessions not time out.
227
req.get_session().unlock()
229
# If user did not specify an app, HTTP redirect to default app and
232
req.throw_redirect(util.make_path(ivle.conf.apps.default_app))
234
# Set the default title to the app's tab name, if any. Otherwise URL
236
if app.name is not None:
241
# Call the specified app with the request object
242
apps.call_app(app.dir, req)
244
# if not logged in, login.login will have written the login box.
245
# Just clean up and exit.
247
# MAKE SURE we write the HTTP (and possibly HTML) header. This
248
# wouldn't happen if nothing else ever got written, so we have to make
250
req.ensure_headers_written()
252
# When done, write out the HTML footer if the app has requested it
253
if req.write_html_head_foot:
254
html.write_html_foot(req)
256
# Note: Apache will not write custom HTML error messages here.
257
# Use req.throw_error to do that.
260
142
def handle_unknown_exception(req, exc_type, exc_value, exc_traceback):
262
144
Given an exception that has just been thrown from IVLE, print its details
316
logging.debug('Logging Unhandled Exception')
318
# We handle 3 types of error.
319
# IVLEErrors with 4xx response codes (client error).
320
# IVLEErrors with 5xx response codes (handled server error).
321
# Other exceptions (unhandled server error).
322
# IVLEErrors should not have other response codes than 4xx or 5xx
323
# (eg. throw_redirect should have been used for 3xx codes).
324
# Therefore, that is treated as an unhandled error.
326
if (exc_type == util.IVLEError and httpcode >= 400
327
and httpcode <= 499):
328
# IVLEErrors with 4xx response codes are client errors.
329
# Therefore, these have a "nice" response (we even coat it in the IVLE
332
req.write_html_head_foot = True
333
req.write_javascript_settings = False
334
req.write('<div id="ivle_padding">\n')
185
# A "bad" error message. We shouldn't get here unless IVLE
186
# misbehaves (which is currently very easy, if things aren't set up
188
# Write the traceback.
189
# If this is a non-4xx IVLEError, get the message and httpcode and
190
# make the error message a bit nicer (but still include the
192
# We also need to special-case IVLEJailError, as we can get another
193
# almost-exception out of it.
195
codename, msg = None, None
197
if exc_type is util.IVLEJailError:
198
msg = exc_value.type_str + ": " + exc_value.message
199
tb = 'Exception information extracted from IVLEJailError:\n'
200
tb += urllib.unquote(exc_value.info)
336
203
codename, msg = req.get_http_codename(httpcode)
337
204
except AttributeError:
338
codename, msg = None, None
339
# Override the default message with the supplied one,
341
if exc_value.message is not None:
342
msg = exc_value.message
343
if codename is not None:
344
req.write("<h1>Error: %s</h1>\n" % cgi.escape(codename))
346
req.write("<h1>Error</h1>\n")
348
req.write("<p>%s</p>\n" % cgi.escape(msg))
350
req.write("<p>An unknown error occured.</p>\n")
353
logging.info(str(msg))
355
req.write("<p>(HTTP error code %d)</p>\n" % httpcode)
357
req.write("<p>Warning: Could not open Error Log: '%s'</p>\n"
358
%cgi.escape(logfile))
359
req.write('</div>\n')
360
html.write_html_foot(req)
362
# A "bad" error message. We shouldn't get here unless IVLE
363
# misbehaves (which is currently very easy, if things aren't set up
365
# Write the traceback.
366
# If this is a non-4xx IVLEError, get the message and httpcode and
367
# make the error message a bit nicer (but still include the
369
# We also need to special-case IVLEJailError, as we can get another
370
# almost-exception out of it.
372
codename, msg = None, None
374
if exc_type is util.IVLEJailError:
375
msg = exc_value.type_str + ": " + exc_value.message
376
tb = 'Exception information extracted from IVLEJailError:\n'
377
tb += urllib.unquote(exc_value.info)
380
codename, msg = req.get_http_codename(httpcode)
381
except AttributeError:
383
# Override the default message with the supplied one,
385
if hasattr(exc_value, 'message') and exc_value.message is not None:
386
msg = exc_value.message
387
# Prepend the exception type
388
if exc_type != util.IVLEError:
389
msg = exc_type.__name__ + ": " + msg
391
tb = ''.join(traceback.format_exception(exc_type, exc_value,
395
logging.error('%s\n%s'%(str(msg), tb))
396
# Error messages are only displayed is the user is NOT a student,
397
# or if there has been a problem logging the error message
398
show_errors = (not publicmode) and ((login and \
399
str(role) != "student") or logfail)
400
req.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
207
tb = ''.join(traceback.format_exception(exc_type, exc_value,
210
logging.error('%s\n%s'%(str(msg), tb))
212
# Error messages are only displayed is the user is NOT a student,
213
# or if there has been a problem logging the error message
214
show_errors = (not publicmode) and ((login and req.user.admin) or logfail)
215
req.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
401
216
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
402
217
<html xmlns="http://www.w3.org/1999/xhtml">
403
218
<head><title>IVLE Internal Server Error</title></head>
405
220
<h1>IVLE Internal Server Error""")
407
if (codename is not None
408
and httpcode != mod_python.apache.HTTP_INTERNAL_SERVER_ERROR):
409
req.write(": %s" % cgi.escape(codename))
222
if codename is not None and \
223
httpcode != mod_python.apache.HTTP_INTERNAL_SERVER_ERROR:
224
req.write(": %s" % cgi.escape(codename))
412
227
<p>An error has occured which is the fault of the IVLE developers or
413
administration. The developers have been notified.</p>
417
req.write("<p>%s</p>\n" % cgi.escape(msg))
418
if httpcode is not None:
419
req.write("<p>(HTTP error code %d)</p>\n" % httpcode)
421
<p>Please report this to <a href="mailto:%s">%s</a> (the system
422
administrator). Include the following information:</p>
423
""" % (cgi.escape(admin_email), cgi.escape(admin_email)))
425
req.write("<pre>\n%s\n</pre>\n"%cgi.escape(tb))
427
req.write("<p>Warning: Could not open Error Log: '%s'</p>\n"
428
%cgi.escape(logfile))
429
req.write("</body></html>")
231
req.write("Please report this issue to the server administrators, "
232
"along with the following information.")
234
req.write("Details have been logged for further examination.")
239
req.write("<p>%s</p>\n" % cgi.escape(msg))
240
if httpcode is not None:
241
req.write("<p>(HTTP error code %d)</p>\n" % httpcode)
242
req.write("<h2>Debugging information</h2>")
244
req.write("<pre>\n%s\n</pre>\n"%cgi.escape(tb))
245
req.write("</body></html>")