1
# IVLE - Informatics Virtual Learning Environment
2
# Copyright (C) 2007-2009 The University of Melbourne
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
# Author: Matt Giuca, Will Grant
21
This is a mod_python handler program. The correct way to call it is to have
22
Apache send all requests to be handled by the module 'dispatch'.
24
Top-level handler. Handles all requests to all pages in IVLE.
25
Handles authentication (not authorization).
26
Then passes the request along to the appropriate ivle app.
40
from mod_python import apache, Cookie
49
from request import Request
50
import plugins.console # XXX: Relies on www/ being in the Python path.
52
# XXX List of plugins, which will eventually be read in from conf
53
# Elements are (module, classname) pairs.
55
('ivle.webapp.admin.user', 'Plugin'),
58
def get_routes_mapper():
60
Build a Mapper object for doing URL matching using 'routes', based on the
64
for plugin_path, classname in plugins_HACK:
65
# Load the plugin module from somewhere in the Python path
66
# (Note that plugin_path is a fully-qualified Python module name).
67
plugin = getattr(__import__(plugin_path, fromlist=[classname]),
69
# Establish a URL pattern for each element of plugin.urls
70
for url in plugin.urls:
73
kwargs_dict = url[2] if len(url) >= 3 else {}
74
m.connect(routex, view=view_class, **kwargs_dict)
78
"""Handles a request which may be to anywhere in the site except media.
79
Intended to be called by mod_python, as a handler.
81
req: An Apache request object.
83
# Make the request object into an IVLE request which can be passed to apps
86
req = Request(req, html.write_html_head)
88
# Pass the apachereq to error reporter, since ivle req isn't created
90
handle_unknown_exception(apachereq, *sys.exc_info())
91
# Tell Apache not to generate its own errors as well
94
# Run the main handler, and catch all exceptions
96
return handler_(req, apachereq)
97
except mod_python.apache.SERVER_RETURN:
98
# An apache error. We discourage these, but they might still happen.
102
handle_unknown_exception(req, *sys.exc_info())
103
# Tell Apache not to generate its own errors as well
106
def handler_(req, apachereq):
108
Nested handler function. May raise exceptions. The top-level handler is
109
just used to catch exceptions.
110
Takes both an IVLE request and an Apache req.
112
# Hack? Try and get the user login early just in case we throw an error
113
# (most likely 404) to stop us seeing not logged in even when we are.
114
if not req.publicmode:
115
req.user = login.get_user_details(req)
117
### BEGIN New plugins framework ###
118
# XXX This should be done ONCE per Python process, not per request.
120
# XXX No authentication is done here
121
mapper = get_routes_mapper()
122
matchdict = mapper.match(req.uri)
123
if matchdict is not None:
124
viewcls = matchdict['view']
125
# Get the remaining arguments, less 'view', 'action' and 'controller'
126
# (The latter two seem to be built-in, and we don't want them).
127
kwargs = matchdict.copy()
130
del kwargs['controller']
131
# Instantiate the view, which should be a BaseView class
132
view = viewcls(req, **kwargs)
137
### END New plugins framework ###
139
# Check req.app to see if it is valid. 404 if not.
140
if req.app is not None and req.app not in ivle.conf.apps.app_url:
141
req.throw_error(Request.HTTP_NOT_FOUND,
142
"There is no application called %s." % repr(req.app))
144
# Special handling for public mode - only allow the public app, call it
146
# NOTE: This will not behave correctly if the public app uses
147
# write_html_head_foot, but "serve" does not.
149
if req.app != ivle.conf.apps.public_app:
150
req.throw_error(Request.HTTP_FORBIDDEN,
151
"This application is not available on the public site.")
152
app = ivle.conf.apps.app_url[ivle.conf.apps.public_app]
153
apps.call_app(app.dir, req)
156
# app is the App object for the chosen app
158
app = ivle.conf.apps.app_url[ivle.conf.apps.default_app]
160
app = ivle.conf.apps.app_url[req.app]
162
# Check if app requires auth. If so, perform authentication and login.
163
# This will either return a User object, None, or perform a redirect
164
# which we will not catch here.
166
req.user = login.login(req)
167
logged_in = req.user is not None
169
req.user = login.get_user_details(req)
173
# Keep the user's session alive by writing to the session object.
174
# req.get_session().save()
175
# Well, it's a fine idea, but it creates considerable grief in the
176
# concurrent update department, so instead, we'll just make the
177
# sessions not time out.
178
req.get_session().unlock()
180
# If user did not specify an app, HTTP redirect to default app and
183
req.throw_redirect(util.make_path(ivle.conf.apps.default_app))
185
# Set the default title to the app's tab name, if any. Otherwise URL
187
if app.name is not None:
192
# Call the specified app with the request object
193
apps.call_app(app.dir, req)
195
# if not logged in, login.login will have written the login box.
196
# Just clean up and exit.
198
# MAKE SURE we write the HTTP (and possibly HTML) header. This
199
# wouldn't happen if nothing else ever got written, so we have to make
201
req.ensure_headers_written()
203
# When done, write out the HTML footer if the app has requested it
204
if req.write_html_head_foot:
205
# Show the console if required
206
if logged_in and app.useconsole:
207
plugins.console.present(req, windowpane=True)
208
html.write_html_foot(req)
210
# Note: Apache will not write custom HTML error messages here.
211
# Use req.throw_error to do that.
214
def handle_unknown_exception(req, exc_type, exc_value, exc_traceback):
216
Given an exception that has just been thrown from IVLE, print its details
218
This is a full handler. It assumes nothing has been written, and writes a
220
req: May be EITHER an IVLE req or an Apache req.
221
IVLE reqs may have the HTML head/foot written (on a 400 error), but
222
the handler code may pass an apache req if an exception occurs before
223
the IVLE request is created.
225
req.content_type = "text/html"
226
logfile = os.path.join(ivle.conf.log_path, 'ivle_error.log')
228
# For some reason, some versions of mod_python have "_server" instead of
229
# "main_server". So we check for both.
231
admin_email = apache.main_server.server_admin
232
except AttributeError:
234
admin_email = apache._server.server_admin
235
except AttributeError:
238
httpcode = exc_value.httpcode
239
req.status = httpcode
240
except AttributeError:
242
req.status = apache.HTTP_INTERNAL_SERVER_ERROR
244
publicmode = req.publicmode
245
except AttributeError:
248
login = req.user.login
249
except AttributeError:
253
except AttributeError:
258
for h in logging.getLogger().handlers:
259
logging.getLogger().removeHandler(h)
260
logging.basicConfig(level=logging.INFO,
261
format='%(asctime)s %(levelname)s: ' +
262
'(HTTP: ' + str(req.status) +
263
', Ref: ' + str(login) + '@' +
264
str(socket.gethostname()) + str(req.uri) +
270
logging.debug('Logging Unhandled Exception')
272
# We handle 3 types of error.
273
# IVLEErrors with 4xx response codes (client error).
274
# IVLEErrors with 5xx response codes (handled server error).
275
# Other exceptions (unhandled server error).
276
# IVLEErrors should not have other response codes than 4xx or 5xx
277
# (eg. throw_redirect should have been used for 3xx codes).
278
# Therefore, that is treated as an unhandled error.
280
if (exc_type == util.IVLEError and httpcode >= 400
281
and httpcode <= 499):
282
# IVLEErrors with 4xx response codes are client errors.
283
# Therefore, these have a "nice" response (we even coat it in the IVLE
286
req.write_html_head_foot = True
287
req.write_javascript_settings = False
288
req.write('<div id="ivle_padding">\n')
290
codename, msg = req.get_http_codename(httpcode)
291
except AttributeError:
292
codename, msg = None, None
293
# Override the default message with the supplied one,
295
if exc_value.message is not None:
296
msg = exc_value.message
297
if codename is not None:
298
req.write("<h1>Error: %s</h1>\n" % cgi.escape(codename))
300
req.write("<h1>Error</h1>\n")
302
req.write("<p>%s</p>\n" % cgi.escape(msg))
304
req.write("<p>An unknown error occured.</p>\n")
307
logging.info(str(msg))
309
req.write("<p>(HTTP error code %d)</p>\n" % httpcode)
311
req.write("<p>Warning: Could not open Error Log: '%s'</p>\n"
312
%cgi.escape(logfile))
313
req.write('</div>\n')
314
html.write_html_foot(req)
316
# A "bad" error message. We shouldn't get here unless IVLE
317
# misbehaves (which is currently very easy, if things aren't set up
319
# Write the traceback.
320
# If this is a non-4xx IVLEError, get the message and httpcode and
321
# make the error message a bit nicer (but still include the
323
# We also need to special-case IVLEJailError, as we can get another
324
# almost-exception out of it.
326
codename, msg = None, None
328
if exc_type is util.IVLEJailError:
329
msg = exc_value.type_str + ": " + exc_value.message
330
tb = 'Exception information extracted from IVLEJailError:\n'
331
tb += urllib.unquote(exc_value.info)
334
codename, msg = req.get_http_codename(httpcode)
335
except AttributeError:
337
# Override the default message with the supplied one,
339
if hasattr(exc_value, 'message') and exc_value.message is not None:
340
msg = exc_value.message
341
# Prepend the exception type
342
if exc_type != util.IVLEError:
343
msg = exc_type.__name__ + ": " + msg
345
tb = ''.join(traceback.format_exception(exc_type, exc_value,
349
logging.error('%s\n%s'%(str(msg), tb))
350
# Error messages are only displayed is the user is NOT a student,
351
# or if there has been a problem logging the error message
352
show_errors = (not publicmode) and ((login and \
353
str(role) != "student") or logfail)
354
req.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
355
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
356
<html xmlns="http://www.w3.org/1999/xhtml">
357
<head><title>IVLE Internal Server Error</title></head>
359
<h1>IVLE Internal Server Error""")
361
if (codename is not None
362
and httpcode != apache.HTTP_INTERNAL_SERVER_ERROR):
363
req.write(": %s" % cgi.escape(codename))
366
<p>An error has occured which is the fault of the IVLE developers or
367
administration. The developers have been notified.</p>
371
req.write("<p>%s</p>\n" % cgi.escape(msg))
372
if httpcode is not None:
373
req.write("<p>(HTTP error code %d)</p>\n" % httpcode)
375
<p>Please report this to <a href="mailto:%s">%s</a> (the system
376
administrator). Include the following information:</p>
377
""" % (cgi.escape(admin_email), cgi.escape(admin_email)))
379
req.write("<pre>\n%s\n</pre>\n"%cgi.escape(tb))
381
req.write("<p>Warning: Could not open Error Log: '%s'</p>\n"
382
%cgi.escape(logfile))
383
req.write("</body></html>")