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

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