1099
by matt.giuca
dispatch/__init__.py: Updated header comments to new policy, updated copyright. |
1 |
# IVLE - Informatics Virtual Learning Environment
|
2 |
# Copyright (C) 2007-2009 The University of Melbourne
|
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
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 |
||
1099.1.1
by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick). |
18 |
# Author: Matt Giuca, Will Grant
|
1099
by matt.giuca
dispatch/__init__.py: Updated header comments to new policy, updated copyright. |
19 |
|
20 |
"""
|
|
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'.
|
|
23 |
||
24 |
Top-level handler. Handles all requests to all pages in IVLE.
|
|
1099.1.166
by William Grant
Remove the odd handler() -> handler_() chaining in ivle.dispatch. |
25 |
Handles authentication and delegates to views for authorization,
|
26 |
then passes the request along to the appropriate view.
|
|
1099
by matt.giuca
dispatch/__init__.py: Updated header comments to new policy, updated copyright. |
27 |
"""
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
28 |
|
29 |
import sys |
|
30 |
import os |
|
31 |
import os.path |
|
32 |
import urllib |
|
33 |
import cgi |
|
34 |
import traceback |
|
35 |
import logging |
|
36 |
import socket |
|
37 |
import time |
|
38 |
||
39 |
import mod_python |
|
40 |
||
41 |
from ivle import util |
|
1199
by William Grant
Populate req.config in a cleaner manner. |
42 |
import ivle.config |
1099.1.5
by William Grant
ivle.dispatch{,.{login,request}}: Fix mod_python imports to ensure that we can |
43 |
from ivle.dispatch.request import Request |
1099.1.161
by William Grant
Move ivle.dispatch.login.get_user_details() to ivle.webapp.security. |
44 |
import ivle.webapp.security |
1099.1.146
by William Grant
Add support for public mode views to the new framework. |
45 |
from ivle.webapp.base.plugins import ViewPlugin, PublicViewPlugin |
1099.1.202
by William Grant
XHTMLErrorView is now the default error view, so we get nice 404s. |
46 |
from ivle.webapp.base.xhtml import XHTMLView, XHTMLErrorView |
47 |
from ivle.webapp.errors import HTTPError, Unauthorized, NotFound |
|
1294.3.2
by William Grant
Router->Publisher |
48 |
from ivle.webapp.publisher import Publisher, PublishingError |
1294.2.18
by William Grant
Replace string-route-based dispatch with object-traversal-route one. |
49 |
from ivle.webapp import ApplicationRoot |
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
50 |
|
1199
by William Grant
Populate req.config in a cleaner manner. |
51 |
config = ivle.config.Config() |
52 |
||
1294.2.129
by William Grant
Refuse to traverse through an object to which the user has no permissions. This stops information leakage in breadcrumbs. |
53 |
class ObjectPermissionCheckingPublisher(Publisher): |
54 |
"""A specialised publisher that checks object permissions.
|
|
55 |
||
56 |
This publisher verifies that the user holds any permission at all
|
|
57 |
on the model objects through which the resolution path passes. If
|
|
58 |
no permission is held, resolution is aborted with an Unauthorized
|
|
59 |
exception.
|
|
60 |
||
61 |
IMPORTANT: This does NOT check view permissions. It only checks
|
|
62 |
the objects in between the root and the view, exclusive!
|
|
63 |
"""
|
|
64 |
||
65 |
def traversed_to_object(self, obj): |
|
66 |
"""Check that the user has any permission at all over the object."""
|
|
67 |
if (hasattr(obj, 'get_permissions') and |
|
1544
by Matt Giuca
Added an argument 'config' to every single get_permissions method throughout the program. All calls to get_permissions pass a config. This is to allow per-site policy configurations on permissions. |
68 |
len(obj.get_permissions(self.root.user, config)) == 0): |
1294.2.130
by William Grant
If the user is an admin, indicate the object in an Unauthorized. |
69 |
# Indicate the forbidden object if this is an admin.
|
70 |
if self.root.user and self.root.user.admin: |
|
71 |
raise Unauthorized('Unauthorized: %s' % obj) |
|
72 |
else: |
|
73 |
raise Unauthorized() |
|
1294.2.129
by William Grant
Refuse to traverse through an object to which the user has no permissions. This stops information leakage in breadcrumbs. |
74 |
|
75 |
||
1294.2.133
by William Grant
Add support for public views. |
76 |
def generate_publisher(view_plugins, root, publicmode=False): |
1099.1.1
by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick). |
77 |
"""
|
78 |
Build a Mapper object for doing URL matching using 'routes', based on the
|
|
1099.1.59
by William Grant
Provide a media file framework in ivle.webapp.media. |
79 |
given plugin registry.
|
1099.1.1
by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick). |
80 |
"""
|
1294.2.129
by William Grant
Refuse to traverse through an object to which the user has no permissions. This stops information leakage in breadcrumbs. |
81 |
r = ObjectPermissionCheckingPublisher(root=root) |
1294.2.18
by William Grant
Replace string-route-based dispatch with object-traversal-route one. |
82 |
|
83 |
r.add_set_switch('api', 'api') |
|
84 |
||
1294.2.133
by William Grant
Add support for public views. |
85 |
if publicmode: |
86 |
view_attr = 'public_views' |
|
1294.2.134
by William Grant
Use a different set of routes for public mode, too. |
87 |
forward_route_attr = 'public_forward_routes' |
88 |
reverse_route_attr = 'public_reverse_routes' |
|
1294.2.133
by William Grant
Add support for public views. |
89 |
else: |
90 |
view_attr = 'views' |
|
1294.2.134
by William Grant
Use a different set of routes for public mode, too. |
91 |
forward_route_attr = 'forward_routes' |
92 |
reverse_route_attr = 'reverse_routes' |
|
93 |
||
1294.2.133
by William Grant
Add support for public views. |
94 |
|
1099.1.72
by Nick Chadwick
Dispatch now generates an index for each plugin type, allowing plugins to |
95 |
for plugin in view_plugins: |
1294.2.134
by William Grant
Use a different set of routes for public mode, too. |
96 |
if hasattr(plugin, forward_route_attr): |
97 |
for fr in getattr(plugin, forward_route_attr): |
|
1294.2.68
by William Grant
Add annotated route function support to ivle.dispatch. |
98 |
# An annotated function can also be passed in directly.
|
99 |
if hasattr(fr, '_forward_route_meta'): |
|
100 |
r.add_forward_func(fr) |
|
101 |
else: |
|
102 |
r.add_forward(*fr) |
|
103 |
||
1294.2.134
by William Grant
Use a different set of routes for public mode, too. |
104 |
if hasattr(plugin, reverse_route_attr): |
1294.2.135
by William Grant
Fix route typo. |
105 |
for rr in getattr(plugin, reverse_route_attr): |
1294.2.68
by William Grant
Add annotated route function support to ivle.dispatch. |
106 |
# An annotated function can also be passed in directly.
|
107 |
if hasattr(rr, '_reverse_route_src'): |
|
108 |
r.add_reverse_func(rr) |
|
109 |
else: |
|
110 |
r.add_reverse(*rr) |
|
1294.2.18
by William Grant
Replace string-route-based dispatch with object-traversal-route one. |
111 |
|
1294.2.133
by William Grant
Add support for public views. |
112 |
if hasattr(plugin, view_attr): |
113 |
for v in getattr(plugin, view_attr): |
|
1294.2.18
by William Grant
Replace string-route-based dispatch with object-traversal-route one. |
114 |
r.add_view(*v) |
115 |
||
116 |
return r |
|
1099.1.1
by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick). |
117 |
|
1099.1.166
by William Grant
Remove the odd handler() -> handler_() chaining in ivle.dispatch. |
118 |
def handler(apachereq): |
119 |
"""Handles an HTTP request.
|
|
120 |
||
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
121 |
Intended to be called by mod_python, as a handler.
|
122 |
||
1099.1.166
by William Grant
Remove the odd handler() -> handler_() chaining in ivle.dispatch. |
123 |
@param apachereq: An Apache request object.
|
124 |
"""
|
|
125 |
# Make the request object into an IVLE request which can be given to views
|
|
1199
by William Grant
Populate req.config in a cleaner manner. |
126 |
req = Request(apachereq, config) |
1099.1.166
by William Grant
Remove the odd handler() -> handler_() chaining in ivle.dispatch. |
127 |
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
128 |
# Hack? Try and get the user login early just in case we throw an error
|
129 |
# (most likely 404) to stop us seeing not logged in even when we are.
|
|
130 |
if not req.publicmode: |
|
1099.1.161
by William Grant
Move ivle.dispatch.login.get_user_details() to ivle.webapp.security. |
131 |
user = ivle.webapp.security.get_user_details(req) |
1099.1.121
by William Grant
Don't set req.user unless the login in the session specifies a valid user. |
132 |
|
133 |
# Don't set the user if it is disabled or hasn't accepted the ToS.
|
|
1099.1.123
by William Grant
Don't crash when not authenticated, and display an error on password absence. |
134 |
if user and user.valid: |
1099.1.121
by William Grant
Don't set req.user unless the login in the session specifies a valid user. |
135 |
req.user = user |
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
136 |
|
1294.2.129
by William Grant
Refuse to traverse through an object to which the user has no permissions. This stops information leakage in breadcrumbs. |
137 |
req.publisher = generate_publisher( |
138 |
config.plugin_index[ViewPlugin], |
|
1294.2.133
by William Grant
Add support for public views. |
139 |
ApplicationRoot(req.config, req.store, req.user), |
140 |
publicmode=req.publicmode) |
|
1294.2.18
by William Grant
Replace string-route-based dispatch with object-traversal-route one. |
141 |
|
142 |
try: |
|
1294.3.3
by William Grant
req.router -> req.publisher |
143 |
obj, viewcls, subpath = req.publisher.resolve(req.uri.decode('utf-8')) |
1099.1.84
by William Grant
Handle HTTPErrors nicely in the new framework. Currently there is no facility |
144 |
try: |
1294.2.59
by William Grant
Implement optional view subpath support. |
145 |
# We 404 if we have a subpath but the view forbids it.
|
146 |
if not viewcls.subpath_allowed and subpath: |
|
147 |
raise NotFound() |
|
148 |
||
1099.1.86
by William Grant
Also capture the view object creation in the new framework's exception handler. |
149 |
# Instantiate the view, which should be a BaseView class
|
1294.2.59
by William Grant
Implement optional view subpath support. |
150 |
view = viewcls(req, obj, subpath) |
1099.1.110
by William Grant
Implement an authorization system in the new framework. This breaks the REST |
151 |
|
152 |
# Check that the request (mainly the user) is permitted to access
|
|
153 |
# the view.
|
|
154 |
if not view.authorize(req): |
|
1294.2.130
by William Grant
If the user is an admin, indicate the object in an Unauthorized. |
155 |
# Indicate the forbidden object if this is an admin.
|
156 |
if req.user and req.user.admin: |
|
157 |
raise Unauthorized('Unauthorized: %s' % view) |
|
158 |
else: |
|
159 |
raise Unauthorized() |
|
1099.1.86
by William Grant
Also capture the view object creation in the new framework's exception handler. |
160 |
# Render the output
|
1099.1.84
by William Grant
Handle HTTPErrors nicely in the new framework. Currently there is no facility |
161 |
view.render(req) |
162 |
except HTTPError, e: |
|
163 |
# A view explicitly raised an HTTP error. Respect it.
|
|
164 |
req.status = e.code |
|
1099.1.91
by William Grant
Add support for custom error views. Plugins can now declare that errors |
165 |
|
166 |
# Try to find a custom error view.
|
|
1099.1.95
by William Grant
Error views are now retrieved from a class method of the view, not the plugin |
167 |
if hasattr(viewcls, 'get_error_view'): |
168 |
errviewcls = viewcls.get_error_view(e) |
|
169 |
else: |
|
1099.1.202
by William Grant
XHTMLErrorView is now the default error view, so we get nice 404s. |
170 |
errviewcls = XHTMLView.get_error_view(e) |
1099.1.95
by William Grant
Error views are now retrieved from a class method of the view, not the plugin |
171 |
|
1099.1.91
by William Grant
Add support for custom error views. Plugins can now declare that errors |
172 |
if errviewcls: |
1294.2.108
by William Grant
Display breadcrumbs on error pages too. |
173 |
errview = errviewcls(req, e, obj) |
1099.1.91
by William Grant
Add support for custom error views. Plugins can now declare that errors |
174 |
errview.render(req) |
175 |
return req.OK |
|
1099.1.135
by William Grant
In the new framework handler, either write the exception message or use |
176 |
elif e.message: |
1099.1.91
by William Grant
Add support for custom error views. Plugins can now declare that errors |
177 |
req.write(e.message) |
1099.1.135
by William Grant
In the new framework handler, either write the exception message or use |
178 |
return req.OK |
179 |
else: |
|
1099.1.91
by William Grant
Add support for custom error views. Plugins can now declare that errors |
180 |
return e.code |
1155
by William Grant
Don't handle mod_python exceptions (eg. redirects) as unknown exceptions. |
181 |
except mod_python.apache.SERVER_RETURN: |
182 |
# A mod_python-specific Apache error.
|
|
183 |
# XXX: We need to raise these because req.throw_error() uses them.
|
|
184 |
# Remove this after Google Code issue 117 is fixed.
|
|
185 |
raise
|
|
1099.1.84
by William Grant
Handle HTTPErrors nicely in the new framework. Currently there is no facility |
186 |
except Exception, e: |
187 |
# A non-HTTPError appeared. We have an unknown exception. Panic.
|
|
188 |
handle_unknown_exception(req, *sys.exc_info()) |
|
1099.1.88
by William Grant
Make sure that we don't use the old framework if the new framework crashes. |
189 |
return req.OK |
1099.1.84
by William Grant
Handle HTTPErrors nicely in the new framework. Currently there is no facility |
190 |
else: |
191 |
req.store.commit() |
|
192 |
return req.OK |
|
1294.2.129
by William Grant
Refuse to traverse through an object to which the user has no permissions. This stops information leakage in breadcrumbs. |
193 |
except Unauthorized, e: |
194 |
# Resolution failed due to a permission check. Display a pretty
|
|
195 |
# error, or maybe a login page.
|
|
196 |
XHTMLView.get_error_view(e)(req, e, req.publisher.root).render(req) |
|
197 |
return req.OK |
|
1294.3.2
by William Grant
Router->Publisher |
198 |
except PublishingError, e: |
1294.2.107
by William Grant
Actually return a 404 if routing fails. |
199 |
req.status = 404 |
200 |
||
1294.2.122
by William Grant
Don't crash in admin check if not logged in. |
201 |
if req.user and req.user.admin: |
1294.2.48
by William Grant
Display useful NotFound data to admins. |
202 |
XHTMLErrorView(req, NotFound('Not found: ' + |
1294.2.108
by William Grant
Display breadcrumbs on error pages too. |
203 |
str(e.args)), e[0]).render(req) |
1294.2.48
by William Grant
Display useful NotFound data to admins. |
204 |
else: |
1294.2.108
by William Grant
Display breadcrumbs on error pages too. |
205 |
XHTMLErrorView(req, NotFound(), e[0]).render(req) |
1294.2.48
by William Grant
Display useful NotFound data to admins. |
206 |
|
1099.1.202
by William Grant
XHTMLErrorView is now the default error view, so we get nice 404s. |
207 |
return req.OK |
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
208 |
|
209 |
def handle_unknown_exception(req, exc_type, exc_value, exc_traceback): |
|
210 |
"""
|
|
211 |
Given an exception that has just been thrown from IVLE, print its details
|
|
212 |
to the request.
|
|
213 |
This is a full handler. It assumes nothing has been written, and writes a
|
|
214 |
complete HTML page.
|
|
215 |
req: May be EITHER an IVLE req or an Apache req.
|
|
1099.1.157
by William Grant
Drop the 4xx-series exception handling ability from |
216 |
The handler code may pass an apache req if an exception occurs before
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
217 |
the IVLE request is created.
|
218 |
"""
|
|
219 |
req.content_type = "text/html" |
|
1199
by William Grant
Populate req.config in a cleaner manner. |
220 |
logfile = os.path.join(config['paths']['logs'], 'ivle_error.log') |
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
221 |
logfail = False |
1292
by William Grant
Revive a removed part of the top-level crash handler. |
222 |
|
223 |
# XXX: This remains here for ivle.interpret's IVLEErrors. Once we rewrite
|
|
224 |
# fileservice, req.status should always be 500 (ISE) here.
|
|
225 |
try: |
|
226 |
httpcode = exc_value.httpcode |
|
227 |
req.status = httpcode |
|
228 |
except AttributeError: |
|
229 |
httpcode = None |
|
230 |
req.status = mod_python.apache.HTTP_INTERNAL_SERVER_ERROR |
|
1286
by William Grant
Simplify (and deprettify) the crash handler. It's now only used to handle crashes, so needn't be pretty. |
231 |
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
232 |
try: |
1087
by chadnickbok
Fixes Issue #3 |
233 |
publicmode = req.publicmode |
234 |
except AttributeError: |
|
1088
by chadnickbok
Fixed an issue with my previous patch to this file. Now, if |
235 |
publicmode = True |
1087
by chadnickbok
Fixes Issue #3 |
236 |
try: |
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
237 |
login = req.user.login |
238 |
except AttributeError: |
|
239 |
login = None |
|
240 |
||
241 |
# Log File
|
|
242 |
try: |
|
243 |
for h in logging.getLogger().handlers: |
|
244 |
logging.getLogger().removeHandler(h) |
|
245 |
logging.basicConfig(level=logging.INFO, |
|
246 |
format='%(asctime)s %(levelname)s: ' + |
|
247 |
'(HTTP: ' + str(req.status) + |
|
248 |
', Ref: ' + str(login) + '@' + |
|
249 |
str(socket.gethostname()) + str(req.uri) + |
|
250 |
') %(message)s', |
|
251 |
filename=logfile, |
|
252 |
filemode='a') |
|
253 |
except IOError: |
|
254 |
logfail = True |
|
255 |
||
1099.1.157
by William Grant
Drop the 4xx-series exception handling ability from |
256 |
# A "bad" error message. We shouldn't get here unless IVLE
|
257 |
# misbehaves (which is currently very easy, if things aren't set up
|
|
258 |
# correctly).
|
|
259 |
# Write the traceback.
|
|
1286
by William Grant
Simplify (and deprettify) the crash handler. It's now only used to handle crashes, so needn't be pretty. |
260 |
|
261 |
# We need to special-case IVLEJailError, as we can get another
|
|
1099.1.157
by William Grant
Drop the 4xx-series exception handling ability from |
262 |
# almost-exception out of it.
|
263 |
if exc_type is util.IVLEJailError: |
|
264 |
tb = 'Exception information extracted from IVLEJailError:\n' |
|
265 |
tb += urllib.unquote(exc_value.info) |
|
266 |
else: |
|
267 |
tb = ''.join(traceback.format_exception(exc_type, exc_value, |
|
268 |
exc_traceback)) |
|
269 |
||
1286
by William Grant
Simplify (and deprettify) the crash handler. It's now only used to handle crashes, so needn't be pretty. |
270 |
logging.error('\n' + tb) |
1099.1.167
by William Grant
Simplify and prettify the crash handler. |
271 |
|
1099.1.157
by William Grant
Drop the 4xx-series exception handling ability from |
272 |
# Error messages are only displayed is the user is NOT a student,
|
273 |
# or if there has been a problem logging the error message
|
|
1101
by William Grant
Privileges (apart from admin) are now offering-local, not global. |
274 |
show_errors = (not publicmode) and ((login and req.user.admin) or logfail) |
1099.1.157
by William Grant
Drop the 4xx-series exception handling ability from |
275 |
req.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" |
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
276 |
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
277 |
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
278 |
<head><title>IVLE Internal Server Error</title></head>
|
|
279 |
<body>
|
|
1286
by William Grant
Simplify (and deprettify) the crash handler. It's now only used to handle crashes, so needn't be pretty. |
280 |
<h1>IVLE Internal Server Error</h1>
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
281 |
<p>An error has occured which is the fault of the IVLE developers or
|
1099.1.175
by William Grant
Alter the crash handler to display a different message depending on whether |
282 |
administrators. """) |
283 |
||
284 |
if logfail: |
|
285 |
req.write("Please report this issue to the server administrators, " |
|
286 |
"along with the following information.") |
|
287 |
else: |
|
288 |
req.write("Details have been logged for further examination.") |
|
289 |
req.write("</p>") |
|
290 |
||
1099.1.167
by William Grant
Simplify and prettify the crash handler. |
291 |
if show_errors: |
292 |
req.write("<h2>Debugging information</h2>") |
|
1099.1.157
by William Grant
Drop the 4xx-series exception handling ability from |
293 |
req.write("<pre>\n%s\n</pre>\n"%cgi.escape(tb)) |
294 |
req.write("</body></html>") |