~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to ivle/dispatch/__init__.py

ivle.webapp.base.views#JSONRESTView: Check that methods are available before
    trying to execute them. Also raise a legal MethodNotAllowed with an
    'allowed' attribute indicating permitted methods
ivle.webapp.errors: Alter constructor to take mandatory 'allowed' argument.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# IVLE - Informatics Virtual Learning Environment
 
2
# Copyright (C) 2007-2009 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
# Author: Matt Giuca, Will Grant
 
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.
 
25
Handles authentication (not authorization).
 
26
Then passes the request along to the appropriate ivle app.
 
27
"""
 
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
import routes
 
41
 
 
42
from ivle import util
 
43
import ivle.conf
 
44
import ivle.conf.apps
 
45
from ivle.dispatch.request import Request
 
46
from ivle.dispatch import login
 
47
import apps
 
48
import html
 
49
import plugins.console # XXX: Relies on www/ being in the Python path.
 
50
 
 
51
# XXX List of plugins, which will eventually be read in from conf
 
52
plugins_HACK = [
 
53
    'ivle.webapp.admin.user#Plugin',
 
54
    'ivle.webapp.tutorial#Plugin',
 
55
    'ivle.webapp.admin.subject#Plugin',
 
56
    'ivle.webapp.browser#Plugin',
 
57
    'ivle.webapp.groups#Plugin',
 
58
]
 
59
 
 
60
def get_routes_mapper():
 
61
    """
 
62
    Build a Mapper object for doing URL matching using 'routes', based on the
 
63
    plugins config.
 
64
    """
 
65
    m = routes.Mapper(explicit=True)
 
66
    for pluginstr in plugins_HACK:
 
67
        plugin_path, classname = pluginstr.split('#')
 
68
        # Load the plugin module from somewhere in the Python path
 
69
        # (Note that plugin_path is a fully-qualified Python module name).
 
70
        plugin = getattr(__import__(plugin_path, fromlist=[classname]),
 
71
            classname)
 
72
        # Establish a URL pattern for each element of plugin.urls
 
73
        for url in plugin.urls:
 
74
            routex = url[0]
 
75
            view_class = url[1]
 
76
            kwargs_dict = url[2] if len(url) >= 3 else {}
 
77
            m.connect(routex, view=view_class, **kwargs_dict)
 
78
    return m
 
79
 
 
80
def handler(req):
 
81
    """Handles a request which may be to anywhere in the site except media.
 
82
    Intended to be called by mod_python, as a handler.
 
83
 
 
84
    req: An Apache request object.
 
85
    """
 
86
    # Make the request object into an IVLE request which can be passed to apps
 
87
    apachereq = req
 
88
    try:
 
89
        req = Request(req, html.write_html_head)
 
90
    except Exception:
 
91
        # Pass the apachereq to error reporter, since ivle req isn't created
 
92
        # yet.
 
93
        handle_unknown_exception(apachereq, *sys.exc_info())
 
94
        # Tell Apache not to generate its own errors as well
 
95
        return mod_python.apache.OK
 
96
 
 
97
    # Run the main handler, and catch all exceptions
 
98
    try:
 
99
        return handler_(req, apachereq)
 
100
    except mod_python.apache.SERVER_RETURN:
 
101
        # An apache error. We discourage these, but they might still happen.
 
102
        # Just raise up.
 
103
        raise
 
104
    except Exception:
 
105
        handle_unknown_exception(req, *sys.exc_info())
 
106
        # Tell Apache not to generate its own errors as well
 
107
        return mod_python.apache.OK
 
108
 
 
109
def handler_(req, apachereq):
 
110
    """
 
111
    Nested handler function. May raise exceptions. The top-level handler is
 
112
    just used to catch exceptions.
 
113
    Takes both an IVLE request and an Apache req.
 
114
    """
 
115
    # Hack? Try and get the user login early just in case we throw an error
 
116
    # (most likely 404) to stop us seeing not logged in even when we are.
 
117
    if not req.publicmode:
 
118
        req.user = login.get_user_details(req)
 
119
 
 
120
    ### BEGIN New plugins framework ###
 
121
    # XXX This should be done ONCE per Python process, not per request.
 
122
    # (Wait till WSGI)
 
123
    # XXX No authentication is done here
 
124
    req.mapper = get_routes_mapper()
 
125
    matchdict = req.mapper.match(req.uri)
 
126
    if matchdict is not None:
 
127
        viewcls = matchdict['view']
 
128
        # Get the remaining arguments, less 'view', 'action' and 'controller'
 
129
        # (The latter two seem to be built-in, and we don't want them).
 
130
        kwargs = matchdict.copy()
 
131
        del kwargs['view']
 
132
        # Instantiate the view, which should be a BaseView class
 
133
        view = viewcls(req, **kwargs)
 
134
        # Render the output
 
135
        view.render(req)
 
136
        req.store.commit()
 
137
        return req.OK
 
138
    ### END New plugins framework ###
 
139
 
 
140
    # Check req.app to see if it is valid. 404 if not.
 
141
    if req.app is not None and req.app not in ivle.conf.apps.app_url:
 
142
        req.throw_error(Request.HTTP_NOT_FOUND,
 
143
            "There is no application called %s." % repr(req.app))
 
144
 
 
145
    # Special handling for public mode - only allow the public app, call it
 
146
    # and get out.
 
147
    # NOTE: This will not behave correctly if the public app uses
 
148
    # write_html_head_foot, but "serve" does not.
 
149
    if req.publicmode:
 
150
        if req.app != ivle.conf.apps.public_app:
 
151
            req.throw_error(Request.HTTP_FORBIDDEN,
 
152
                "This application is not available on the public site.")
 
153
        app = ivle.conf.apps.app_url[ivle.conf.apps.public_app]
 
154
        apps.call_app(app.dir, req)
 
155
        return req.OK
 
156
 
 
157
    # app is the App object for the chosen app
 
158
    if req.app is None:
 
159
        app = ivle.conf.apps.app_url[ivle.conf.apps.default_app]
 
160
    else:
 
161
        app = ivle.conf.apps.app_url[req.app]
 
162
 
 
163
    # Check if app requires auth. If so, perform authentication and login.
 
164
    # This will either return a User object, None, or perform a redirect
 
165
    # which we will not catch here.
 
166
    if app.requireauth:
 
167
        req.user = login.login(req)
 
168
        logged_in = req.user is not None
 
169
    else:
 
170
        req.user = login.get_user_details(req)
 
171
        logged_in = True
 
172
 
 
173
    if logged_in:
 
174
        # Keep the user's session alive by writing to the session object.
 
175
        # req.get_session().save()
 
176
        # Well, it's a fine idea, but it creates considerable grief in the
 
177
        # concurrent update department, so instead, we'll just make the
 
178
        # sessions not time out.
 
179
        req.get_session().unlock()
 
180
 
 
181
        # If user did not specify an app, HTTP redirect to default app and
 
182
        # exit.
 
183
        if req.app is None:
 
184
            req.throw_redirect(util.make_path(ivle.conf.apps.default_app))
 
185
 
 
186
        # Set the default title to the app's tab name, if any. Otherwise URL
 
187
        # name.
 
188
        if app.name is not None:
 
189
            req.title = app.name
 
190
        else:
 
191
            req.title = req.app
 
192
 
 
193
        # Call the specified app with the request object
 
194
        apps.call_app(app.dir, req)
 
195
 
 
196
    # if not logged in, login.login will have written the login box.
 
197
    # Just clean up and exit.
 
198
 
 
199
    # MAKE SURE we write the HTTP (and possibly HTML) header. This
 
200
    # wouldn't happen if nothing else ever got written, so we have to make
 
201
    # sure.
 
202
    req.ensure_headers_written()
 
203
 
 
204
    # When done, write out the HTML footer if the app has requested it
 
205
    if req.write_html_head_foot:
 
206
        # Show the console if required
 
207
        if logged_in and app.useconsole:
 
208
            plugins.console.present(req, windowpane=True)
 
209
        html.write_html_foot(req)
 
210
 
 
211
    # Note: Apache will not write custom HTML error messages here.
 
212
    # Use req.throw_error to do that.
 
213
    return req.OK
 
214
 
 
215
def handle_unknown_exception(req, exc_type, exc_value, exc_traceback):
 
216
    """
 
217
    Given an exception that has just been thrown from IVLE, print its details
 
218
    to the request.
 
219
    This is a full handler. It assumes nothing has been written, and writes a
 
220
    complete HTML page.
 
221
    req: May be EITHER an IVLE req or an Apache req.
 
222
    IVLE reqs may have the HTML head/foot written (on a 400 error), but
 
223
    the handler code may pass an apache req if an exception occurs before
 
224
    the IVLE request is created.
 
225
    """
 
226
    req.content_type = "text/html"
 
227
    logfile = os.path.join(ivle.conf.log_path, 'ivle_error.log')
 
228
    logfail = False
 
229
    # For some reason, some versions of mod_python have "_server" instead of
 
230
    # "main_server". So we check for both.
 
231
    try:
 
232
        admin_email = mod_python.apache.main_server.server_admin
 
233
    except AttributeError:
 
234
        try:
 
235
            admin_email = mod_python.apache._server.server_admin
 
236
        except AttributeError:
 
237
            admin_email = ""
 
238
    try:
 
239
        httpcode = exc_value.httpcode
 
240
        req.status = httpcode
 
241
    except AttributeError:
 
242
        httpcode = None
 
243
        req.status = mod_python.apache.HTTP_INTERNAL_SERVER_ERROR
 
244
    try:
 
245
        publicmode = req.publicmode
 
246
    except AttributeError:
 
247
        publicmode = True
 
248
    try:
 
249
        login = req.user.login
 
250
    except AttributeError:
 
251
        login = None
 
252
    try:
 
253
        role = req.user.role
 
254
    except AttributeError:
 
255
        role = None
 
256
 
 
257
    # Log File
 
258
    try:
 
259
        for h in logging.getLogger().handlers:
 
260
            logging.getLogger().removeHandler(h)
 
261
        logging.basicConfig(level=logging.INFO,
 
262
            format='%(asctime)s %(levelname)s: ' +
 
263
                '(HTTP: ' + str(req.status) +
 
264
                ', Ref: ' + str(login) + '@' +
 
265
                str(socket.gethostname()) + str(req.uri) +
 
266
                ') %(message)s',
 
267
            filename=logfile,
 
268
            filemode='a')
 
269
    except IOError:
 
270
        logfail = True
 
271
    logging.debug('Logging Unhandled Exception')
 
272
 
 
273
    # We handle 3 types of error.
 
274
    # IVLEErrors with 4xx response codes (client error).
 
275
    # IVLEErrors with 5xx response codes (handled server error).
 
276
    # Other exceptions (unhandled server error).
 
277
    # IVLEErrors should not have other response codes than 4xx or 5xx
 
278
    # (eg. throw_redirect should have been used for 3xx codes).
 
279
    # Therefore, that is treated as an unhandled error.
 
280
 
 
281
    if (exc_type == util.IVLEError and httpcode >= 400
 
282
        and httpcode <= 499):
 
283
        # IVLEErrors with 4xx response codes are client errors.
 
284
        # Therefore, these have a "nice" response (we even coat it in the IVLE
 
285
        # HTML wrappers).
 
286
        
 
287
        req.write_html_head_foot = True
 
288
        req.write_javascript_settings = False
 
289
        req.write('<div id="ivle_padding">\n')
 
290
        try:
 
291
            codename, msg = req.get_http_codename(httpcode)
 
292
        except AttributeError:
 
293
            codename, msg = None, None
 
294
        # Override the default message with the supplied one,
 
295
        # if available.
 
296
        if exc_value.message is not None:
 
297
            msg = exc_value.message
 
298
        if codename is not None:
 
299
            req.write("<h1>Error: %s</h1>\n" % cgi.escape(codename))
 
300
        else:
 
301
            req.write("<h1>Error</h1>\n")
 
302
        if msg is not None:
 
303
            req.write("<p>%s</p>\n" % cgi.escape(msg))
 
304
        else:
 
305
            req.write("<p>An unknown error occured.</p>\n")
 
306
        
 
307
        # Logging
 
308
        logging.info(str(msg))
 
309
        
 
310
        req.write("<p>(HTTP error code %d)</p>\n" % httpcode)
 
311
        if logfail:
 
312
            req.write("<p>Warning: Could not open Error Log: '%s'</p>\n"
 
313
                %cgi.escape(logfile))
 
314
        req.write('</div>\n')
 
315
        html.write_html_foot(req)
 
316
    else:
 
317
        # A "bad" error message. We shouldn't get here unless IVLE
 
318
        # misbehaves (which is currently very easy, if things aren't set up
 
319
        # correctly).
 
320
        # Write the traceback.
 
321
        # If this is a non-4xx IVLEError, get the message and httpcode and
 
322
        # make the error message a bit nicer (but still include the
 
323
        # traceback).
 
324
        # We also need to special-case IVLEJailError, as we can get another
 
325
        # almost-exception out of it.
 
326
 
 
327
        codename, msg = None, None
 
328
 
 
329
        if exc_type is util.IVLEJailError:
 
330
            msg = exc_value.type_str + ": " + exc_value.message
 
331
            tb = 'Exception information extracted from IVLEJailError:\n'
 
332
            tb += urllib.unquote(exc_value.info)
 
333
        else:
 
334
            try:
 
335
                codename, msg = req.get_http_codename(httpcode)
 
336
            except AttributeError:
 
337
                pass
 
338
            # Override the default message with the supplied one,
 
339
            # if available.
 
340
            if hasattr(exc_value, 'message') and exc_value.message is not None:
 
341
                msg = exc_value.message
 
342
                # Prepend the exception type
 
343
                if exc_type != util.IVLEError:
 
344
                    msg = exc_type.__name__ + ": " + msg
 
345
 
 
346
            tb = ''.join(traceback.format_exception(exc_type, exc_value,
 
347
                                                    exc_traceback))
 
348
 
 
349
        # Logging
 
350
        logging.error('%s\n%s'%(str(msg), tb))
 
351
        # Error messages are only displayed is the user is NOT a student,
 
352
        # or if there has been a problem logging the error message
 
353
        show_errors = (not publicmode) and ((login and \
 
354
                            str(role) != "student") or logfail)
 
355
        req.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"                 
 
356
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">                                      
 
357
<html xmlns="http://www.w3.org/1999/xhtml">
 
358
<head><title>IVLE Internal Server Error</title></head>
 
359
<body>
 
360
<h1>IVLE Internal Server Error""")
 
361
        if (show_errors):
 
362
            if (codename is not None
 
363
                        and httpcode != mod_python.apache.HTTP_INTERNAL_SERVER_ERROR):
 
364
                req.write(": %s" % cgi.escape(codename))
 
365
        
 
366
        req.write("""</h1>
 
367
<p>An error has occured which is the fault of the IVLE developers or
 
368
administration. The developers have been notified.</p>
 
369
""")
 
370
        if (show_errors):
 
371
            if msg is not None:
 
372
                req.write("<p>%s</p>\n" % cgi.escape(msg))
 
373
            if httpcode is not None:
 
374
                req.write("<p>(HTTP error code %d)</p>\n" % httpcode)
 
375
            req.write("""
 
376
    <p>Please report this to <a href="mailto:%s">%s</a> (the system
 
377
    administrator). Include the following information:</p>
 
378
    """ % (cgi.escape(admin_email), cgi.escape(admin_email)))
 
379
 
 
380
            req.write("<pre>\n%s\n</pre>\n"%cgi.escape(tb))
 
381
            if logfail:
 
382
                req.write("<p>Warning: Could not open Error Log: '%s'</p>\n"
 
383
                    %cgi.escape(logfile))
 
384
        req.write("</body></html>")