1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
1 |
# IVLE - Informatics Virtual Learning Environment
|
1080.1.2
by matt.giuca
New module: ivle.database. Classes and utilities for Storm ORM. |
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 |
||
18 |
# Author: Matt Giuca
|
|
1080.1.2
by matt.giuca
New module: ivle.database. Classes and utilities for Storm ORM. |
19 |
|
20 |
"""
|
|
21 |
IVLE Request Object
|
|
22 |
||
23 |
Builds an IVLE request object from a mod_python request object.
|
|
24 |
See design notes/apps/dispatch.txt for a full specification of this request
|
|
25 |
object.
|
|
26 |
"""
|
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
27 |
|
1099.1.5
by William Grant
ivle.dispatch{,.{login,request}}: Fix mod_python imports to ensure that we can |
28 |
try: |
29 |
import mod_python.Session |
|
30 |
import mod_python.Cookie |
|
31 |
import mod_python.util |
|
1750
by William Grant
Set 'secure' flag on cookies if served over a direct or proxied HTTPS connection. |
32 |
import mod_python.apache |
1754
by William Grant
Move PotentiallySecureFileSession into the try/export import block, so ivle.dispatch.request can be imported from outside Apache. |
33 |
|
34 |
class PotentiallySecureFileSession(mod_python.Session.FileSession): |
|
35 |
"""A mod_python FileSession that sets secure cookie when appropriate.
|
|
36 |
||
37 |
A secure cookie will be set if the request itself is over HTTPS, or if
|
|
38 |
a proxy in front has set X-Forwarded-Proto: https. Otherwise the cookie
|
|
39 |
will be insecure.
|
|
40 |
"""
|
|
41 |
def make_cookie(self): |
|
42 |
cookie = super(PotentiallySecureFileSession, self).make_cookie() |
|
43 |
if (self._req.is_https() or |
|
44 |
self._req.headers_in.get('X-Forwarded-Proto') == 'https'): |
|
45 |
cookie.secure = True |
|
46 |
return cookie |
|
1099.1.5
by William Grant
ivle.dispatch{,.{login,request}}: Fix mod_python imports to ensure that we can |
47 |
except ImportError: |
48 |
# This needs to be importable from outside Apache.
|
|
49 |
pass
|
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
50 |
|
1208
by William Grant
Replace ivle.util.unmake_path with specialisations in Request and CGIRequest. |
51 |
import os.path |
52 |
||
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
53 |
import ivle.util |
1080.1.2
by matt.giuca
New module: ivle.database. Classes and utilities for Storm ORM. |
54 |
import ivle.database |
1099.1.80
by William Grant
Port the forum app to the new framework. With it also comes new cookie |
55 |
from ivle.webapp.base.plugins import CookiePlugin |
1712
by William Grant
Shuffle things around so that req.user and req.store only construct when actually retrieved, and ensure they're not retrieved for media files. Saves 50ms of DB connection time per request. |
56 |
import ivle.webapp.security |
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
57 |
|
1750
by William Grant
Set 'secure' flag on cookies if served over a direct or proxied HTTPS connection. |
58 |
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
59 |
class Request: |
60 |
"""An IVLE request object. This is presented to the IVLE apps as a way of
|
|
61 |
interacting with the web server and the dispatcher.
|
|
62 |
||
63 |
Request object attributes:
|
|
64 |
method (read)
|
|
65 |
String. The request method (eg. 'GET', 'POST', etc)
|
|
66 |
uri (read)
|
|
67 |
String. The path portion of the URI.
|
|
1796.1.3
by William Grant
Use req.unparsed_uri instead of req.uri -- req.uri doesn't contain the query string. |
68 |
unparsed_uri (read)
|
69 |
String. The path portion of the URI, unparsed with query string.
|
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
70 |
app (read)
|
71 |
String. Name of the application specified in the URL, or None.
|
|
72 |
path (read)
|
|
73 |
String. The path specified in the URL *not including* the
|
|
74 |
application or the IVLE location prefix. eg. a URL of
|
|
75 |
"/ivle/files/joe/myfiles" has a path of "joe/myfiles".
|
|
76 |
user (read)
|
|
77 |
User object. Details of the user who is currently logged in, or
|
|
78 |
None.
|
|
1080.1.2
by matt.giuca
New module: ivle.database. Classes and utilities for Storm ORM. |
79 |
store (read)
|
80 |
storm.store.Store instance. Holds a database transaction open,
|
|
81 |
which is available for the entire lifetime of the request.
|
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
82 |
hostname (read)
|
83 |
String. Hostname the server is running on.
|
|
84 |
headers_in (read)
|
|
85 |
Table object representing headers sent by the client.
|
|
86 |
headers_out (read, can be written to)
|
|
87 |
Table object representing headers to be sent to the client.
|
|
88 |
publicmode (read)
|
|
89 |
Bool. True if the request came for the "public host" as
|
|
90 |
configured in conf.py. Note that public mode requests do not
|
|
91 |
have an app (app is set to None).
|
|
92 |
||
93 |
status (write)
|
|
94 |
Int. Response status number. Use one of the status codes defined
|
|
95 |
in class Request.
|
|
96 |
content_type (write)
|
|
97 |
String. The Content-Type (mime type) header value.
|
|
1791.2.3
by David Coles
Dispatch: Allow setting of Content-Length header with req.content_length |
98 |
content_length (write)
|
99 |
Integer. The number of octets to be transfered.
|
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
100 |
location (write)
|
101 |
String. Response "Location" header value. Used with HTTP redirect
|
|
102 |
responses.
|
|
103 |
"""
|
|
104 |
||
105 |
# Special code for an OK response.
|
|
106 |
# Do not use HTTP_OK; for some reason Apache produces an "OK" error
|
|
107 |
# message if you do that.
|
|
108 |
OK = 0 |
|
109 |
||
110 |
# HTTP status codes
|
|
111 |
||
112 |
HTTP_OK = 200 |
|
113 |
HTTP_MOVED_TEMPORARILY = 302 |
|
114 |
HTTP_FORBIDDEN = 403 |
|
115 |
HTTP_NOT_FOUND = 404 |
|
116 |
HTTP_INTERNAL_SERVER_ERROR = 500 |
|
117 |
||
1712
by William Grant
Shuffle things around so that req.user and req.store only construct when actually retrieved, and ensure they're not retrieved for media files. Saves 50ms of DB connection time per request. |
118 |
_store = None |
119 |
||
1199
by William Grant
Populate req.config in a cleaner manner. |
120 |
def __init__(self, req, config): |
121 |
"""Create an IVLE request from a mod_python one.
|
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
122 |
|
1199
by William Grant
Populate req.config in a cleaner manner. |
123 |
@param req: A mod_python request.
|
124 |
@param config: An IVLE configuration.
|
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
125 |
"""
|
126 |
||
127 |
# Methods are mostly wrappers around the Apache request object
|
|
128 |
self.apache_req = req |
|
1199
by William Grant
Populate req.config in a cleaner manner. |
129 |
self.config = config |
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
130 |
self.headers_written = False |
131 |
||
132 |
# Determine if the browser used the public host name to make the
|
|
133 |
# request (in which case we are in "public mode")
|
|
1199
by William Grant
Populate req.config in a cleaner manner. |
134 |
if req.hostname == config['urls']['public_host']: |
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
135 |
self.publicmode = True |
136 |
else: |
|
137 |
self.publicmode = False |
|
138 |
||
139 |
# Inherit values for the input members
|
|
140 |
self.method = req.method |
|
141 |
self.uri = req.uri |
|
1796.1.3
by William Grant
Use req.unparsed_uri instead of req.uri -- req.uri doesn't contain the query string. |
142 |
self.unparsed_uri = req.unparsed_uri |
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
143 |
# Split the given path into the app (top-level dir) and sub-path
|
144 |
# (after first stripping away the root directory)
|
|
1288
by William Grant
Kill Request.unmake_path. |
145 |
(self.app, self.path) = (ivle.util.split_path(req.uri)) |
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
146 |
self.hostname = req.hostname |
147 |
self.headers_in = req.headers_in |
|
148 |
self.headers_out = req.headers_out |
|
149 |
||
150 |
# Default values for the output members
|
|
151 |
self.status = Request.HTTP_OK |
|
152 |
self.content_type = None # Use Apache's default |
|
1791.2.3
by David Coles
Dispatch: Allow setting of Content-Length header with req.content_length |
153 |
self.content_length = None # Don't provide Content-Length |
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
154 |
self.location = None |
155 |
# In some cases we don't want the template JS (such as the username
|
|
156 |
# and public FQDN) in the output HTML. In that case, set this to 0.
|
|
157 |
self.write_javascript_settings = True |
|
158 |
self.got_common_vars = False |
|
159 |
||
1080.1.2
by matt.giuca
New module: ivle.database. Classes and utilities for Storm ORM. |
160 |
def __del__(self): |
1712
by William Grant
Shuffle things around so that req.user and req.store only construct when actually retrieved, and ensure they're not retrieved for media files. Saves 50ms of DB connection time per request. |
161 |
self.cleanup() |
162 |
||
163 |
def cleanup(self): |
|
164 |
"""Cleanup."""
|
|
165 |
if self._store is not None: |
|
166 |
self._store.close() |
|
167 |
self._store = None |
|
168 |
||
169 |
def commit(self): |
|
170 |
"""Cleanup."""
|
|
171 |
if self._store is not None: |
|
172 |
self._store.commit() |
|
1080.1.2
by matt.giuca
New module: ivle.database. Classes and utilities for Storm ORM. |
173 |
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
174 |
def __writeheaders(self): |
175 |
"""Writes out the HTTP and HTML headers before any real data is
|
|
176 |
written."""
|
|
177 |
self.headers_written = True |
|
178 |
||
179 |
# Prepare the HTTP and HTML headers before the first write is made
|
|
180 |
if self.content_type != None: |
|
181 |
self.apache_req.content_type = self.content_type |
|
1791.2.3
by David Coles
Dispatch: Allow setting of Content-Length header with req.content_length |
182 |
if self.content_length: |
183 |
self.apache_req.set_content_length(self.content_length) |
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
184 |
self.apache_req.status = self.status |
185 |
if self.location != None: |
|
186 |
self.apache_req.headers_out['Location'] = self.location |
|
187 |
||
188 |
def ensure_headers_written(self): |
|
189 |
"""Writes out the HTTP and HTML headers if they haven't already been
|
|
190 |
written."""
|
|
191 |
if not self.headers_written: |
|
192 |
self.__writeheaders() |
|
193 |
||
194 |
def write(self, string, flush=1): |
|
195 |
"""Writes string directly to the client, then flushes the buffer,
|
|
196 |
unless flush is 0."""
|
|
197 |
||
198 |
if not self.headers_written: |
|
199 |
self.__writeheaders() |
|
200 |
if isinstance(string, unicode): |
|
201 |
# Encode unicode strings as UTF-8
|
|
202 |
# (Otherwise cannot handle being written to a bytestream)
|
|
203 |
self.apache_req.write(string.encode('utf8'), flush) |
|
204 |
else: |
|
205 |
# 8-bit clean strings just get written directly.
|
|
206 |
# This includes binary strings.
|
|
207 |
self.apache_req.write(string, flush) |
|
208 |
||
1081
by me at id
ivle.conf, ivle.dispatch: Redo some of Nick's changes made between the |
209 |
def logout(self): |
210 |
"""Log out the current user by destroying the session state.
|
|
211 |
Then redirect to the top-level IVLE page."""
|
|
212 |
if hasattr(self, 'session'): |
|
213 |
self.session.invalidate() |
|
214 |
self.session.delete() |
|
215 |
# Invalidates all IVLE cookies
|
|
1099.1.5
by William Grant
ivle.dispatch{,.{login,request}}: Fix mod_python imports to ensure that we can |
216 |
all_cookies = mod_python.Cookie.get_cookies(self) |
1099.1.80
by William Grant
Port the forum app to the new framework. With it also comes new cookie |
217 |
|
218 |
# Create cookies for plugins that might request them.
|
|
1092.1.61
by William Grant
Fix cookie deletion. |
219 |
for plugin in self.config.plugin_index[CookiePlugin]: |
1099.1.80
by William Grant
Port the forum app to the new framework. With it also comes new cookie |
220 |
for cookie in plugin.cookies: |
221 |
self.add_cookie(mod_python.Cookie.Cookie(cookie, '', |
|
222 |
expires=1, path='/')) |
|
1210
by William Grant
Use Request.make_path everywhere. |
223 |
self.throw_redirect(self.make_path('')) |
1081
by me at id
ivle.conf, ivle.dispatch: Redo some of Nick's changes made between the |
224 |
|
225 |
||
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
226 |
def flush(self): |
227 |
"""Flushes the output buffer."""
|
|
228 |
self.apache_req.flush() |
|
229 |
||
230 |
def sendfile(self, filename): |
|
231 |
"""Sends the named file directly to the client."""
|
|
232 |
if not self.headers_written: |
|
233 |
self.__writeheaders() |
|
234 |
self.apache_req.sendfile(filename) |
|
235 |
||
236 |
def read(self, len=None): |
|
237 |
"""Reads at most len bytes directly from the client. (See mod_python
|
|
238 |
Request.read)."""
|
|
239 |
if len is None: |
|
240 |
return self.apache_req.read() |
|
241 |
else: |
|
242 |
return self.apache_req.read(len) |
|
243 |
||
244 |
def throw_redirect(self, location): |
|
245 |
"""Writes out an HTTP redirect to the specified URL. Raises an
|
|
246 |
exception which is caught by the dispatch or web server, so any
|
|
247 |
code following this call will not be executed.
|
|
248 |
||
249 |
httpcode: An HTTP response status code. Pass a constant from the
|
|
250 |
Request class.
|
|
251 |
"""
|
|
1080.1.7
by matt.giuca
The new ivle.database.User class is now used in Request and usrmgt, which |
252 |
# Note: location may be a unicode, but it MUST only have ASCII
|
253 |
# characters (non-ascii characters should be URL-encoded).
|
|
254 |
mod_python.util.redirect(self.apache_req, location.encode("ascii")) |
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
255 |
|
256 |
def add_cookie(self, cookie, value=None, **attributes): |
|
257 |
"""Inserts a cookie into this request object's headers."""
|
|
258 |
if value is None: |
|
1099.1.5
by William Grant
ivle.dispatch{,.{login,request}}: Fix mod_python imports to ensure that we can |
259 |
mod_python.Cookie.add_cookie(self.apache_req, cookie) |
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
260 |
else: |
1099.1.5
by William Grant
ivle.dispatch{,.{login,request}}: Fix mod_python imports to ensure that we can |
261 |
mod_python.Cookie.add_cookie(self.apache_req, cookie, value, **attributes) |
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
262 |
|
1209
by William Grant
Add Request.make_path, to replace ivle.util.make_path. |
263 |
def make_path(self, path): |
264 |
"""Prepend the IVLE URL prefix to the given path.
|
|
265 |
||
266 |
This is used when generating URLs to send to the client.
|
|
267 |
||
268 |
This method is DEPRECATED. We no longer support use of a prefix.
|
|
269 |
"""
|
|
270 |
return os.path.join(self.config['urls']['root'], path) |
|
271 |
||
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
272 |
def get_session(self): |
273 |
"""Returns a mod_python Session object for this request.
|
|
274 |
Note that this is dependent on mod_python and may need to change
|
|
1130
by William Grant
Unlock the session everywhere as soon as we are done with it, and add a warning |
275 |
interface if porting away from mod_python.
|
276 |
||
277 |
IMPORTANT: Call unlock() on the session as soon as you are done with
|
|
278 |
it! If you don't, all other requests will block!
|
|
279 |
"""
|
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
280 |
# Cache the session object and set the timeout to 24 hours.
|
281 |
if not hasattr(self, 'session'): |
|
1750
by William Grant
Set 'secure' flag on cookies if served over a direct or proxied HTTPS connection. |
282 |
self.session = PotentiallySecureFileSession( |
283 |
self.apache_req, timeout = 60 * 60 * 24) |
|
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
284 |
return self.session |
285 |
||
286 |
def get_fieldstorage(self): |
|
287 |
"""Returns a mod_python FieldStorage object for this request.
|
|
288 |
Note that this is dependent on mod_python and may need to change
|
|
289 |
interface if porting away from mod_python."""
|
|
290 |
# Cache the fieldstorage object
|
|
291 |
if not hasattr(self, 'fields'): |
|
1099.1.5
by William Grant
ivle.dispatch{,.{login,request}}: Fix mod_python imports to ensure that we can |
292 |
self.fields = mod_python.util.FieldStorage(self.apache_req) |
1079
by William Grant
Merge setup-refactor branch. This completely breaks existing installations; |
293 |
return self.fields |
294 |
||
295 |
def get_cgi_environ(self): |
|
296 |
"""Returns the CGI environment emulation for this request. (Calls
|
|
297 |
add_common_vars). The environment is returned as a mapping
|
|
298 |
compatible with os.environ."""
|
|
299 |
if not self.got_common_vars: |
|
300 |
self.apache_req.add_common_vars() |
|
301 |
self.got_common_vars = True |
|
302 |
return self.apache_req.subprocess_env |
|
303 |
||
1712
by William Grant
Shuffle things around so that req.user and req.store only construct when actually retrieved, and ensure they're not retrieved for media files. Saves 50ms of DB connection time per request. |
304 |
@property
|
305 |
def store(self): |
|
306 |
# Open a database connection and transaction, keep it around for users
|
|
307 |
# of the Request object to use.
|
|
308 |
if self._store is None: |
|
309 |
self._store = ivle.database.get_store(self.config) |
|
310 |
return self._store |
|
311 |
||
312 |
@property
|
|
313 |
def user(self): |
|
314 |
# Get and cache the request user, or None if it's not valid.
|
|
315 |
# This is a property so that we don't create a store unless
|
|
316 |
# some code actually requests the user.
|
|
317 |
try: |
|
318 |
return self._user |
|
319 |
except AttributeError: |
|
320 |
if self.publicmode: |
|
321 |
self._user = None |
|
322 |
else: |
|
323 |
temp_user = ivle.webapp.security.get_user_details(self) |
|
324 |
if temp_user and temp_user.valid: |
|
325 |
self._user = temp_user |
|
326 |
else: |
|
327 |
self._user = None |
|
328 |
return self._user |
|
329 |