42
43
from ivle import util
44
46
from ivle.dispatch.request import Request
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):
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',
69
def generate_route_mapper(view_plugins):
52
71
Build a Mapper object for doing URL matching using 'routes', based on the
53
72
given plugin registry.
56
75
for plugin in view_plugins:
57
76
# Establish a URL pattern for each element of plugin.urls
58
77
assert hasattr(plugin, 'urls'), "%r does not have any urls" % plugin
59
for url in getattr(plugin, attr):
78
for url in plugin.urls:
61
80
view_class = url[1]
62
81
kwargs_dict = url[2] if len(url) >= 3 else {}
63
82
m.connect(routex, view=view_class, **kwargs_dict)
66
def handler(apachereq):
67
"""Handles an HTTP request.
85
def get_plugin(pluginstr):
86
plugin_path, classname = pluginstr.split('#')
87
# Load the plugin module from somewhere in the Python path
88
# (Note that plugin_path is a fully-qualified Python module name).
90
getattr(__import__(plugin_path, fromlist=[classname]), classname))
93
"""Handles a request which may be to anywhere in the site except media.
69
94
Intended to be called by mod_python, as a handler.
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)
96
req: An Apache request object.
98
# Make the request object into an IVLE request which can be passed to apps
101
req = Request(req, html.write_html_head)
103
# Pass the apachereq to error reporter, since ivle req isn't created
105
handle_unknown_exception(apachereq, *sys.exc_info())
106
# Tell Apache not to generate its own errors as well
107
return mod_python.apache.OK
109
# Run the main handler, and catch all exceptions
111
return handler_(req, apachereq)
112
except mod_python.apache.SERVER_RETURN:
113
# An apache error. We discourage these, but they might still happen.
117
handle_unknown_exception(req, *sys.exc_info())
118
# Tell Apache not to generate its own errors as well
119
return mod_python.apache.OK
121
def handler_(req, apachereq):
123
Nested handler function. May raise exceptions. The top-level handler is
124
just used to catch exceptions.
125
Takes both an IVLE request and an Apache req.
76
127
# Hack? Try and get the user login early just in case we throw an error
77
128
# (most likely 404) to stop us seeing not logged in even when we are.
78
129
if not req.publicmode:
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],
130
req.user = login.get_user_details(req)
132
### BEGIN New plugins framework ###
133
# XXX This should be done ONCE per Python process, not per request.
135
# XXX No authentication is done here
136
req.plugins = dict([get_plugin(pluginstr) for pluginstr in plugins_HACK])
137
# Index the plugins by base class
138
req.plugin_index = {}
139
for plugin in req.plugins.values():
140
# Getmro returns a tuple of all the super-classes of the plugin
141
for base in inspect.getmro(plugin):
142
if base not in req.plugin_index:
143
req.plugin_index[base] = []
144
req.plugin_index[base].append(plugin)
145
req.reverse_plugins = dict([(v, k) for (k, v) in req.plugins.items()])
146
req.mapper = generate_route_mapper(req.plugin_index[ViewPlugin])
95
148
matchdict = req.mapper.match(req.uri)
96
149
if matchdict is not None:
103
156
# Instantiate the view, which should be a BaseView class
104
157
view = viewcls(req, **kwargs)
106
# Check that the request (mainly the user) is permitted to access
108
if not view.authorize(req):
110
158
# Render the output
112
160
except HTTPError, e:
113
161
# A view explicitly raised an HTTP error. Respect it.
114
162
req.status = e.code
116
# Try to find a custom error view.
117
if hasattr(viewcls, 'get_error_view'):
118
errviewcls = viewcls.get_error_view(e)
120
errviewcls = XHTMLView.get_error_view(e)
123
errview = errviewcls(req, e)
131
except mod_python.apache.SERVER_RETURN:
132
# A mod_python-specific Apache error.
133
# XXX: We need to raise these because req.throw_error() uses them.
134
# Remove this after Google Code issue 117 is fixed.
164
return e.code # Should be req.OK once we have our own errviews.
136
165
except Exception, e:
137
166
# A non-HTTPError appeared. We have an unknown exception. Panic.
138
167
handle_unknown_exception(req, *sys.exc_info())
141
170
req.store.commit()
144
XHTMLErrorView(req, NotFound()).render(req)
172
### END New plugins framework ###
174
# Check req.app to see if it is valid. 404 if not.
175
if req.app is not None and req.app not in ivle.conf.apps.app_url:
176
req.throw_error(Request.HTTP_NOT_FOUND,
177
"There is no application called %s." % repr(req.app))
179
# Special handling for public mode - only allow the public app, call it
181
# NOTE: This will not behave correctly if the public app uses
182
# write_html_head_foot, but "serve" does not.
184
if req.app != ivle.conf.apps.public_app:
185
req.throw_error(Request.HTTP_FORBIDDEN,
186
"This application is not available on the public site.")
187
app = ivle.conf.apps.app_url[ivle.conf.apps.public_app]
188
apps.call_app(app.dir, req)
191
# app is the App object for the chosen app
193
app = ivle.conf.apps.app_url[ivle.conf.apps.default_app]
195
app = ivle.conf.apps.app_url[req.app]
197
# Check if app requires auth. If so, perform authentication and login.
198
# This will either return a User object, None, or perform a redirect
199
# which we will not catch here.
201
req.user = login.login(req)
202
logged_in = req.user is not None
204
req.user = login.get_user_details(req)
208
# Keep the user's session alive by writing to the session object.
209
# req.get_session().save()
210
# Well, it's a fine idea, but it creates considerable grief in the
211
# concurrent update department, so instead, we'll just make the
212
# sessions not time out.
213
req.get_session().unlock()
215
# If user did not specify an app, HTTP redirect to default app and
218
req.throw_redirect(util.make_path(ivle.conf.apps.default_app))
220
# Set the default title to the app's tab name, if any. Otherwise URL
222
if app.name is not None:
227
# Call the specified app with the request object
228
apps.call_app(app.dir, req)
230
# if not logged in, login.login will have written the login box.
231
# Just clean up and exit.
233
# MAKE SURE we write the HTTP (and possibly HTML) header. This
234
# wouldn't happen if nothing else ever got written, so we have to make
236
req.ensure_headers_written()
238
# When done, write out the HTML footer if the app has requested it
239
if req.write_html_head_foot:
240
html.write_html_foot(req)
242
# Note: Apache will not write custom HTML error messages here.
243
# Use req.throw_error to do that.
147
246
def handle_unknown_exception(req, exc_type, exc_value, exc_traceback):
149
248
Given an exception that has just been thrown from IVLE, print its details
190
# A "bad" error message. We shouldn't get here unless IVLE
191
# misbehaves (which is currently very easy, if things aren't set up
193
# Write the traceback.
194
# If this is a non-4xx IVLEError, get the message and httpcode and
195
# make the error message a bit nicer (but still include the
197
# We also need to special-case IVLEJailError, as we can get another
198
# almost-exception out of it.
200
codename, msg = None, None
202
if exc_type is util.IVLEJailError:
203
msg = exc_value.type_str + ": " + exc_value.message
204
tb = 'Exception information extracted from IVLEJailError:\n'
205
tb += urllib.unquote(exc_value.info)
302
logging.debug('Logging Unhandled Exception')
304
# We handle 3 types of error.
305
# IVLEErrors with 4xx response codes (client error).
306
# IVLEErrors with 5xx response codes (handled server error).
307
# Other exceptions (unhandled server error).
308
# IVLEErrors should not have other response codes than 4xx or 5xx
309
# (eg. throw_redirect should have been used for 3xx codes).
310
# Therefore, that is treated as an unhandled error.
312
if (exc_type == util.IVLEError and httpcode >= 400
313
and httpcode <= 499):
314
# IVLEErrors with 4xx response codes are client errors.
315
# Therefore, these have a "nice" response (we even coat it in the IVLE
318
req.write_html_head_foot = True
319
req.write_javascript_settings = False
320
req.write('<div id="ivle_padding">\n')
208
322
codename, msg = req.get_http_codename(httpcode)
209
323
except AttributeError:
212
tb = ''.join(traceback.format_exception(exc_type, exc_value,
215
logging.error('%s\n%s'%(str(msg), tb))
217
# Error messages are only displayed is the user is NOT a student,
218
# or if there has been a problem logging the error message
219
show_errors = (not publicmode) and ((login and req.user.admin) or logfail)
220
req.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
324
codename, msg = None, None
325
# Override the default message with the supplied one,
327
if exc_value.message is not None:
328
msg = exc_value.message
329
if codename is not None:
330
req.write("<h1>Error: %s</h1>\n" % cgi.escape(codename))
332
req.write("<h1>Error</h1>\n")
334
req.write("<p>%s</p>\n" % cgi.escape(msg))
336
req.write("<p>An unknown error occured.</p>\n")
339
logging.info(str(msg))
341
req.write("<p>(HTTP error code %d)</p>\n" % httpcode)
343
req.write("<p>Warning: Could not open Error Log: '%s'</p>\n"
344
%cgi.escape(logfile))
345
req.write('</div>\n')
346
html.write_html_foot(req)
348
# A "bad" error message. We shouldn't get here unless IVLE
349
# misbehaves (which is currently very easy, if things aren't set up
351
# Write the traceback.
352
# If this is a non-4xx IVLEError, get the message and httpcode and
353
# make the error message a bit nicer (but still include the
355
# We also need to special-case IVLEJailError, as we can get another
356
# almost-exception out of it.
358
codename, msg = None, None
360
if exc_type is util.IVLEJailError:
361
msg = exc_value.type_str + ": " + exc_value.message
362
tb = 'Exception information extracted from IVLEJailError:\n'
363
tb += urllib.unquote(exc_value.info)
366
codename, msg = req.get_http_codename(httpcode)
367
except AttributeError:
369
# Override the default message with the supplied one,
371
if hasattr(exc_value, 'message') and exc_value.message is not None:
372
msg = exc_value.message
373
# Prepend the exception type
374
if exc_type != util.IVLEError:
375
msg = exc_type.__name__ + ": " + msg
377
tb = ''.join(traceback.format_exception(exc_type, exc_value,
381
logging.error('%s\n%s'%(str(msg), tb))
382
# Error messages are only displayed is the user is NOT a student,
383
# or if there has been a problem logging the error message
384
show_errors = (not publicmode) and ((login and \
385
str(role) != "student") or logfail)
386
req.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
221
387
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
222
388
<html xmlns="http://www.w3.org/1999/xhtml">
223
389
<head><title>IVLE Internal Server Error</title></head>
225
391
<h1>IVLE Internal Server Error""")
227
if codename is not None and \
228
httpcode != mod_python.apache.HTTP_INTERNAL_SERVER_ERROR:
229
req.write(": %s" % cgi.escape(codename))
393
if (codename is not None
394
and httpcode != mod_python.apache.HTTP_INTERNAL_SERVER_ERROR):
395
req.write(": %s" % cgi.escape(codename))
232
398
<p>An error has occured which is the fault of the IVLE developers or
236
req.write("Please report this issue to the server administrators, "
237
"along with the following information.")
239
req.write("Details have been logged for further examination.")
244
req.write("<p>%s</p>\n" % cgi.escape(msg))
245
if httpcode is not None:
246
req.write("<p>(HTTP error code %d)</p>\n" % httpcode)
247
req.write("<h2>Debugging information</h2>")
249
req.write("<pre>\n%s\n</pre>\n"%cgi.escape(tb))
250
req.write("</body></html>")
399
administration. The developers have been notified.</p>
403
req.write("<p>%s</p>\n" % cgi.escape(msg))
404
if httpcode is not None:
405
req.write("<p>(HTTP error code %d)</p>\n" % httpcode)
407
<p>Please report this to <a href="mailto:%s">%s</a> (the system
408
administrator). Include the following information:</p>
409
""" % (cgi.escape(admin_email), cgi.escape(admin_email)))
411
req.write("<pre>\n%s\n</pre>\n"%cgi.escape(tb))
413
req.write("<p>Warning: Could not open Error Log: '%s'</p>\n"
414
%cgi.escape(logfile))
415
req.write("</body></html>")