93
by mattgiuca
New directory hierarchy. |
1 |
# IVLE
|
2 |
# Copyright (C) 2007-2008 The University of Melbourne
|
|
3 |
#
|
|
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.
|
|
8 |
#
|
|
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.
|
|
13 |
#
|
|
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
|
|
17 |
||
18 |
# Module: dispatch
|
|
19 |
# Author: Matt Giuca
|
|
20 |
# Date: 11/12/2007
|
|
21 |
||
22 |
# This is a mod_python handler program. The correct way to call it is to have
|
|
23 |
# Apache send all requests to be handled by the module 'dispatch'.
|
|
24 |
||
25 |
# Top-level handler. Handles all requests to all pages in IVLE.
|
|
26 |
# Handles authentication (not authorization).
|
|
27 |
# Then passes the request along to the appropriate ivle app.
|
|
28 |
||
29 |
import mod_python |
|
975
by dcoles
Cookies: We now invalidate (expire and blank) all cookies at logout. Also fixed |
30 |
from mod_python import apache, Cookie |
93
by mattgiuca
New directory hierarchy. |
31 |
|
540
by mattgiuca
Refactored error handling and reporting. Much friendlier error messages, for |
32 |
import sys |
93
by mattgiuca
New directory hierarchy. |
33 |
import os |
34 |
import os.path |
|
853
by wagrant
dispatch: Extract exception information from IVLEJailErrors if caught. |
35 |
import urllib |
36 |
||
93
by mattgiuca
New directory hierarchy. |
37 |
import conf |
38 |
import conf.apps |
|
893
by dcoles
Dispatch: Now attempts to log unhandled exceptions to a log directory specified |
39 |
import conf.conf |
93
by mattgiuca
New directory hierarchy. |
40 |
import apps |
41 |
||
42 |
from request import Request |
|
43 |
import html |
|
574
by mattgiuca
dispatch: Error reporting. |
44 |
import cgi |
124
by mattgiuca
dispatch/request: Added new fields: method and username. |
45 |
import login |
493
by dcoles
session.php: More interfaceing between IVLE and phpBB. Adds groups, emails and |
46 |
from common import (util, forumutil) |
540
by mattgiuca
Refactored error handling and reporting. Much friendlier error messages, for |
47 |
import traceback |
882
by dcoles
Console: To fix bug [ 2018542 ] 'Console shown regardless of login status' |
48 |
import plugins.console |
893
by dcoles
Dispatch: Now attempts to log unhandled exceptions to a log directory specified |
49 |
import logging |
50 |
import socket |
|
975
by dcoles
Cookies: We now invalidate (expire and blank) all cookies at logout. Also fixed |
51 |
import time |
93
by mattgiuca
New directory hierarchy. |
52 |
|
53 |
def handler(req): |
|
54 |
"""Handles a request which may be to anywhere in the site except media.
|
|
55 |
Intended to be called by mod_python, as a handler.
|
|
56 |
||
57 |
req: An Apache request object.
|
|
58 |
"""
|
|
59 |
# Make the request object into an IVLE request which can be passed to apps
|
|
60 |
apachereq = req |
|
540
by mattgiuca
Refactored error handling and reporting. Much friendlier error messages, for |
61 |
try: |
62 |
req = Request(req, html.write_html_head) |
|
63 |
except Exception: |
|
64 |
# Pass the apachereq to error reporter, since ivle req isn't created
|
|
65 |
# yet.
|
|
66 |
handle_unknown_exception(apachereq, *sys.exc_info()) |
|
67 |
# Tell Apache not to generate its own errors as well
|
|
68 |
return apache.OK |
|
69 |
||
70 |
# Run the main handler, and catch all exceptions
|
|
71 |
try: |
|
72 |
return handler_(req, apachereq) |
|
73 |
except mod_python.apache.SERVER_RETURN: |
|
74 |
# An apache error. We discourage these, but they might still happen.
|
|
75 |
# Just raise up.
|
|
76 |
raise
|
|
77 |
except Exception: |
|
78 |
handle_unknown_exception(req, *sys.exc_info()) |
|
79 |
# Tell Apache not to generate its own errors as well
|
|
80 |
return apache.OK |
|
81 |
||
82 |
def handler_(req, apachereq): |
|
83 |
"""
|
|
84 |
Nested handler function. May raise exceptions. The top-level handler is
|
|
85 |
just used to catch exceptions.
|
|
86 |
Takes both an IVLE request and an Apache req.
|
|
87 |
"""
|
|
896
by dcoles
Dispatch: Try and get userid from session as early as possible. This is to |
88 |
# Hack? Try and get the user login early just in case we throw an error
|
89 |
# (most likely 404) to stop us seeing not logged in even when we are.
|
|
932
by wagrant
dispatch (public): Don't attempt to get user details if we are on the |
90 |
if not req.publicmode: |
91 |
req.user = login.get_user_details(req) |
|
896
by dcoles
Dispatch: Try and get userid from session as early as possible. This is to |
92 |
|
93
by mattgiuca
New directory hierarchy. |
93 |
# Check req.app to see if it is valid. 404 if not.
|
94 |
if req.app is not None and req.app not in conf.apps.app_url: |
|
124
by mattgiuca
dispatch/request: Added new fields: method and username. |
95 |
# Maybe it is a special app!
|
1076
by chadnickbok
Created a new app, logout, which when given a GET |
96 |
#if req.app == 'logout':
|
97 |
# logout(req)
|
|
98 |
#else:
|
|
99 |
req.throw_error(Request.HTTP_NOT_FOUND, |
|
100 |
"There is no application called %s." % repr(req.app)) |
|
93
by mattgiuca
New directory hierarchy. |
101 |
|
932
by wagrant
dispatch (public): Don't attempt to get user details if we are on the |
102 |
# Special handling for public mode - only allow the public app, call it
|
103 |
# and get out.
|
|
259
by mattgiuca
setup.py: Added a new config variable "public_host", which lets the admin set |
104 |
# NOTE: This will not behave correctly if the public app uses
|
105 |
# write_html_head_foot, but "serve" does not.
|
|
106 |
if req.publicmode: |
|
932
by wagrant
dispatch (public): Don't attempt to get user details if we are on the |
107 |
if req.app != conf.apps.public_app: |
108 |
req.throw_error(Request.HTTP_FORBIDDEN, |
|
109 |
"This application is not available on the public site.") |
|
259
by mattgiuca
setup.py: Added a new config variable "public_host", which lets the admin set |
110 |
app = conf.apps.app_url[conf.apps.public_app] |
111 |
apps.call_app(app.dir, req) |
|
112 |
return req.OK |
|
113 |
||
93
by mattgiuca
New directory hierarchy. |
114 |
# app is the App object for the chosen app
|
115 |
if req.app is None: |
|
195
by mattgiuca
Configuration: Moved "default_app" setting from conf/conf.py to conf/apps.py. |
116 |
app = conf.apps.app_url[conf.apps.default_app] |
93
by mattgiuca
New directory hierarchy. |
117 |
else: |
118 |
app = conf.apps.app_url[req.app] |
|
119 |
||
120 |
# Check if app requires auth. If so, perform authentication and login.
|
|
504
by mattgiuca
Warning: Broken build, but rather unavoidable or this commit will spiral out |
121 |
# This will either return a User object, None, or perform a redirect
|
122 |
# which we will not catch here.
|
|
93
by mattgiuca
New directory hierarchy. |
123 |
if app.requireauth: |
504
by mattgiuca
Warning: Broken build, but rather unavoidable or this commit will spiral out |
124 |
req.user = login.login(req) |
125 |
logged_in = req.user is not None |
|
93
by mattgiuca
New directory hierarchy. |
126 |
else: |
504
by mattgiuca
Warning: Broken build, but rather unavoidable or this commit will spiral out |
127 |
req.user = login.get_user_details(req) |
124
by mattgiuca
dispatch/request: Added new fields: method and username. |
128 |
logged_in = True |
129 |
||
130 |
if logged_in: |
|
338
by mattgiuca
dispatch: Saves the session every time a request is made. This keeps the users |
131 |
# Keep the user's session alive by writing to the session object.
|
444
by drtomc
dispatch: Change the session timeout to 24 hours (there doesn't seem to be |
132 |
# req.get_session().save()
|
133 |
# Well, it's a fine idea, but it creates considerable grief in the
|
|
134 |
# concurrent update department, so instead, we'll just make the
|
|
135 |
# sessions not time out.
|
|
1019
by wagrant
dispatch: Unlock the session just before we launch the app. If we don't |
136 |
req.get_session().unlock() |
137 |
||
124
by mattgiuca
dispatch/request: Added new fields: method and username. |
138 |
# If user did not specify an app, HTTP redirect to default app and
|
139 |
# exit.
|
|
140 |
if req.app is None: |
|
195
by mattgiuca
Configuration: Moved "default_app" setting from conf/conf.py to conf/apps.py. |
141 |
req.throw_redirect(util.make_path(conf.apps.default_app)) |
124
by mattgiuca
dispatch/request: Added new fields: method and username. |
142 |
|
143 |
# Set the default title to the app's tab name, if any. Otherwise URL
|
|
144 |
# name.
|
|
145 |
if app.name is not None: |
|
146 |
req.title = app.name |
|
147 |
else: |
|
148 |
req.title = req.app |
|
149 |
||
150 |
# Call the specified app with the request object
|
|
151 |
apps.call_app(app.dir, req) |
|
540
by mattgiuca
Refactored error handling and reporting. Much friendlier error messages, for |
152 |
|
124
by mattgiuca
dispatch/request: Added new fields: method and username. |
153 |
# if not logged in, login.login will have written the login box.
|
154 |
# Just clean up and exit.
|
|
93
by mattgiuca
New directory hierarchy. |
155 |
|
156 |
# MAKE SURE we write the HTTP (and possibly HTML) header. This
|
|
157 |
# wouldn't happen if nothing else ever got written, so we have to make
|
|
158 |
# sure.
|
|
159 |
req.ensure_headers_written() |
|
160 |
||
161 |
# When done, write out the HTML footer if the app has requested it
|
|
162 |
if req.write_html_head_foot: |
|
882
by dcoles
Console: To fix bug [ 2018542 ] 'Console shown regardless of login status' |
163 |
# Show the console if required
|
164 |
if logged_in and app.useconsole: |
|
165 |
plugins.console.present(req, windowpane=True) |
|
93
by mattgiuca
New directory hierarchy. |
166 |
html.write_html_foot(req) |
167 |
||
124
by mattgiuca
dispatch/request: Added new fields: method and username. |
168 |
# Note: Apache will not write custom HTML error messages here.
|
169 |
# Use req.throw_error to do that.
|
|
170 |
return req.OK |
|
93
by mattgiuca
New directory hierarchy. |
171 |
|
540
by mattgiuca
Refactored error handling and reporting. Much friendlier error messages, for |
172 |
def handle_unknown_exception(req, exc_type, exc_value, exc_traceback): |
173 |
"""
|
|
174 |
Given an exception that has just been thrown from IVLE, print its details
|
|
175 |
to the request.
|
|
176 |
This is a full handler. It assumes nothing has been written, and writes a
|
|
177 |
complete HTML page.
|
|
178 |
req: May be EITHER an IVLE req or an Apache req.
|
|
179 |
IVLE reqs may have the HTML head/foot written (on a 400 error), but
|
|
180 |
the handler code may pass an apache req if an exception occurs before
|
|
181 |
the IVLE request is created.
|
|
182 |
"""
|
|
183 |
req.content_type = "text/html" |
|
893
by dcoles
Dispatch: Now attempts to log unhandled exceptions to a log directory specified |
184 |
logfile = os.path.join(conf.conf.log_path, 'ivle_error.log') |
185 |
logfail = False |
|
558
by mattgiuca
dispatch: Fixed error on some setups from trying to read admin email. |
186 |
# For some reason, some versions of mod_python have "_server" instead of
|
187 |
# "main_server". So we check for both.
|
|
188 |
try: |
|
189 |
admin_email = apache.main_server.server_admin |
|
190 |
except AttributeError: |
|
191 |
try: |
|
192 |
admin_email = apache._server.server_admin |
|
193 |
except AttributeError: |
|
194 |
admin_email = "" |
|
540
by mattgiuca
Refactored error handling and reporting. Much friendlier error messages, for |
195 |
try: |
196 |
httpcode = exc_value.httpcode |
|
197 |
req.status = httpcode |
|
198 |
except AttributeError: |
|
199 |
httpcode = None |
|
200 |
req.status = apache.HTTP_INTERNAL_SERVER_ERROR |
|
893
by dcoles
Dispatch: Now attempts to log unhandled exceptions to a log directory specified |
201 |
try: |
202 |
login = req.user.login |
|
203 |
except AttributeError: |
|
204 |
login = None |
|
205 |
||
206 |
# Log File
|
|
207 |
try: |
|
1056
by wagrant
www.dispatch: Clear out any existing log handlers when logging an error. |
208 |
for h in logging.getLogger().handlers: |
209 |
logging.getLogger().removeHandler(h) |
|
893
by dcoles
Dispatch: Now attempts to log unhandled exceptions to a log directory specified |
210 |
logging.basicConfig(level=logging.INFO, |
211 |
format='%(asctime)s %(levelname)s: ' + |
|
212 |
'(HTTP: ' + str(req.status) + |
|
213 |
', Ref: ' + str(login) + '@' + |
|
214 |
str(socket.gethostname()) + str(req.uri) + |
|
215 |
') %(message)s', |
|
216 |
filename=logfile, |
|
217 |
filemode='a') |
|
218 |
except IOError: |
|
219 |
logfail = True |
|
220 |
logging.debug('Logging Unhandled Exception') |
|
221 |
||
540
by mattgiuca
Refactored error handling and reporting. Much friendlier error messages, for |
222 |
# We handle 3 types of error.
|
223 |
# IVLEErrors with 4xx response codes (client error).
|
|
224 |
# IVLEErrors with 5xx response codes (handled server error).
|
|
225 |
# Other exceptions (unhandled server error).
|
|
226 |
# IVLEErrors should not have other response codes than 4xx or 5xx
|
|
227 |
# (eg. throw_redirect should have been used for 3xx codes).
|
|
228 |
# Therefore, that is treated as an unhandled error.
|
|
229 |
||
230 |
if (exc_type == util.IVLEError and httpcode >= 400 |
|
231 |
and httpcode <= 499): |
|
232 |
# IVLEErrors with 4xx response codes are client errors.
|
|
233 |
# Therefore, these have a "nice" response (we even coat it in the IVLE
|
|
234 |
# HTML wrappers).
|
|
893
by dcoles
Dispatch: Now attempts to log unhandled exceptions to a log directory specified |
235 |
|
540
by mattgiuca
Refactored error handling and reporting. Much friendlier error messages, for |
236 |
req.write_html_head_foot = True |
1057
by wagrant
www.dispatch.{request,html}: Allow apps to turn off the JS that is |
237 |
req.write_javascript_settings = False |
540
by mattgiuca
Refactored error handling and reporting. Much friendlier error messages, for |
238 |
req.write('<div id="ivle_padding">\n') |
239 |
try: |
|
240 |
codename, msg = req.get_http_codename(httpcode) |
|
241 |
except AttributeError: |
|
242 |
codename, msg = None, None |
|
243 |
# Override the default message with the supplied one,
|
|
244 |
# if available.
|
|
245 |
if exc_value.message is not None: |
|
246 |
msg = exc_value.message |
|
247 |
if codename is not None: |
|
574
by mattgiuca
dispatch: Error reporting. |
248 |
req.write("<h1>Error: %s</h1>\n" % cgi.escape(codename)) |
540
by mattgiuca
Refactored error handling and reporting. Much friendlier error messages, for |
249 |
else: |
250 |
req.write("<h1>Error</h1>\n") |
|
251 |
if msg is not None: |
|
574
by mattgiuca
dispatch: Error reporting. |
252 |
req.write("<p>%s</p>\n" % cgi.escape(msg)) |
540
by mattgiuca
Refactored error handling and reporting. Much friendlier error messages, for |
253 |
else: |
254 |
req.write("<p>An unknown error occured.</p>\n") |
|
893
by dcoles
Dispatch: Now attempts to log unhandled exceptions to a log directory specified |
255 |
|
256 |
# Logging
|
|
257 |
logging.info(str(msg)) |
|
258 |
||
540
by mattgiuca
Refactored error handling and reporting. Much friendlier error messages, for |
259 |
req.write("<p>(HTTP error code %d)</p>\n" % httpcode) |
893
by dcoles
Dispatch: Now attempts to log unhandled exceptions to a log directory specified |
260 |
if logfail: |
261 |
req.write("<p>Warning: Could not open Error Log: '%s'</p>\n" |
|
262 |
%cgi.escape(logfile)) |
|
540
by mattgiuca
Refactored error handling and reporting. Much friendlier error messages, for |
263 |
req.write('</div>\n') |
1055
by wagrant
www.dispatch: Don't sent incomplete XHTML for either type of error. |
264 |
html.write_html_foot(req) |
540
by mattgiuca
Refactored error handling and reporting. Much friendlier error messages, for |
265 |
else: |
266 |
# A "bad" error message. We shouldn't get here unless IVLE
|
|
267 |
# misbehaves (which is currently very easy, if things aren't set up
|
|
268 |
# correctly).
|
|
269 |
# Write the traceback.
|
|
270 |
# If this is a non-4xx IVLEError, get the message and httpcode and
|
|
271 |
# make the error message a bit nicer (but still include the
|
|
272 |
# traceback).
|
|
853
by wagrant
dispatch: Extract exception information from IVLEJailErrors if caught. |
273 |
# We also need to special-case IVLEJailError, as we can get another
|
274 |
# almost-exception out of it.
|
|
275 |
||
276 |
codename, msg = None, None |
|
277 |
||
278 |
if exc_type is util.IVLEJailError: |
|
279 |
msg = exc_value.type_str + ": " + exc_value.message |
|
280 |
tb = 'Exception information extracted from IVLEJailError:\n' |
|
281 |
tb += urllib.unquote(exc_value.info) |
|
282 |
else: |
|
283 |
try: |
|
284 |
codename, msg = req.get_http_codename(httpcode) |
|
285 |
except AttributeError: |
|
286 |
pass
|
|
287 |
# Override the default message with the supplied one,
|
|
288 |
# if available.
|
|
289 |
if hasattr(exc_value, 'message') and exc_value.message is not None: |
|
290 |
msg = exc_value.message |
|
291 |
# Prepend the exception type
|
|
292 |
if exc_type != util.IVLEError: |
|
293 |
msg = exc_type.__name__ + ": " + msg |
|
294 |
||
295 |
tb = ''.join(traceback.format_exception(exc_type, exc_value, |
|
296 |
exc_traceback)) |
|
852
by wagrant
dispatch: Use a less roundabout way of getting the exception traceback. |
297 |
|
893
by dcoles
Dispatch: Now attempts to log unhandled exceptions to a log directory specified |
298 |
# Logging
|
299 |
logging.error('%s\n%s'%(str(msg), tb)) |
|
300 |
||
1055
by wagrant
www.dispatch: Don't sent incomplete XHTML for either type of error. |
301 |
req.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" |
302 |
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
|
303 |
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
540
by mattgiuca
Refactored error handling and reporting. Much friendlier error messages, for |
304 |
<head><title>IVLE Internal Server Error</title></head>
|
305 |
<body>
|
|
306 |
<h1>IVLE Internal Server Error""") |
|
307 |
if (codename is not None |
|
308 |
and httpcode != apache.HTTP_INTERNAL_SERVER_ERROR): |
|
574
by mattgiuca
dispatch: Error reporting. |
309 |
req.write(": %s" % cgi.escape(codename)) |
540
by mattgiuca
Refactored error handling and reporting. Much friendlier error messages, for |
310 |
req.write("""</h1> |
311 |
<p>An error has occured which is the fault of the IVLE developers or
|
|
312 |
administration.</p>
|
|
313 |
""") |
|
314 |
if msg is not None: |
|
574
by mattgiuca
dispatch: Error reporting. |
315 |
req.write("<p>%s</p>\n" % cgi.escape(msg)) |
540
by mattgiuca
Refactored error handling and reporting. Much friendlier error messages, for |
316 |
if httpcode is not None: |
317 |
req.write("<p>(HTTP error code %d)</p>\n" % httpcode) |
|
318 |
req.write(""" |
|
319 |
<p>Please report this to <a href="mailto:%s">%s</a> (the system |
|
320 |
administrator). Include the following information:</p>
|
|
574
by mattgiuca
dispatch: Error reporting. |
321 |
""" % (cgi.escape(admin_email), cgi.escape(admin_email))) |
540
by mattgiuca
Refactored error handling and reporting. Much friendlier error messages, for |
322 |
|
893
by dcoles
Dispatch: Now attempts to log unhandled exceptions to a log directory specified |
323 |
req.write("<pre>\n%s\n</pre>\n"%cgi.escape(tb)) |
324 |
if logfail: |
|
325 |
req.write("<p>Warning: Could not open Error Log: '%s'</p>\n" |
|
326 |
%cgi.escape(logfile)) |
|
1055
by wagrant
www.dispatch: Don't sent incomplete XHTML for either type of error. |
327 |
req.write("</body></html>") |