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

« back to all changes in this revision

Viewing changes to ivle/webapp/filesystem/serve/__init__.py

  • Committer: William Grant
  • Date: 2009-02-23 23:47:02 UTC
  • mfrom: (1099.1.211 new-dispatch)
  • Revision ID: grantw@unimelb.edu.au-20090223234702-db4b1llly46ignwo
Merge from lp:~ivle-dev/ivle/new-dispatch.

Pretty much everything changes. Reread the setup docs. Backup your databases.
Every file is now in a different installed location, the configuration system
is rewritten, the dispatch system is rewritten, URLs are different, the
database is different, worksheets and exercises are no longer on the
filesystem, we use a templating engine, jail service protocols are rewritten,
we don't repeat ourselves, we have authorization rewritten, phpBB is gone,
and probably lots of other things that I cannot remember.

This is certainly the biggest commit I have ever made, and hopefully
the largest I ever will.

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
# our safe execution environment.
26
26
 
27
27
import os
28
 
import mimetypes
29
 
 
30
 
from ivle import (util, studpath, interpret)
 
28
 
 
29
import cjson
 
30
 
 
31
from ivle import (studpath, interpret)
31
32
import ivle.conf
32
33
from ivle.database import User
33
 
 
34
 
serveservice_path = os.path.join(ivle.conf.share_path, 'services/serveservice')
35
 
interpretservice_path = os.path.join(ivle.conf.share_path,
36
 
                                     'services/interpretservice')
37
 
 
38
 
# Serve all files as application/octet-stream so the browser presents them as
39
 
# a download.
40
 
default_mimetype = "application/octet-stream"
41
 
zip_mimetype = "application/zip"
42
 
 
43
 
def handle(req):
44
 
    """Handler for the Server application which serves pages."""
45
 
    req.write_html_head_foot = False
46
 
 
47
 
    # Get the username of the student whose work we are browsing, and the path
48
 
    # on the local machine where the file is stored.
49
 
    (login, path) = studpath.url_to_local(req.path)
50
 
 
51
 
    owner = User.get_by_login(req.store, login)
52
 
    if not owner:
53
 
        # There is no user.
54
 
        req.throw_error(req.HTTP_NOT_FOUND,
55
 
            "The path specified is invalid.")
56
 
 
57
 
    serve_file(req, owner, path)
58
 
 
59
 
def authorize(req):
60
 
    """Given a request, checks whether req.username is allowed to
61
 
    access req.path. Returns None on authorization success. Raises
62
 
    HTTP_FORBIDDEN on failure.
63
 
    """
64
 
    if req.publicmode:
65
 
        # Public mode authorization: any user can access any other user's
66
 
        # files, BUT the accessed file needs to have its "ivle:published" flag
67
 
        # turned on in the SVN status.
68
 
        studpath.authorize_public(req)
69
 
    else:
 
34
from ivle.webapp.base.views import BaseView
 
35
from ivle.webapp.base.xhtml import XHTMLErrorView
 
36
from ivle.webapp.base.plugins import ViewPlugin, PublicViewPlugin
 
37
from ivle.webapp.errors import NotFound, Unauthorized, Forbidden
 
38
 
 
39
class ServeView(BaseView):
 
40
    def __init__(self, req, path):
 
41
        self.path = path
 
42
 
 
43
    def authorize(self, req):
 
44
        return req.user is not None
 
45
 
 
46
    def render(self, req):
 
47
        """Handler for the Server application which serves pages."""
 
48
        # Get the username of the student whose work we are browsing, and the
 
49
        # path on the local machine where the file is stored.
 
50
        (login, jail, path) = studpath.url_to_jailpaths(self.path)
 
51
 
 
52
        owner = User.get_by_login(req.store, login)
 
53
        if not owner:
 
54
            # There is no user.
 
55
            raise NotFound()
 
56
 
 
57
        self.serve(req, owner, jail, path)
 
58
 
 
59
    def serve(self, req, owner, jail, path):
 
60
        self.serve_file(req, owner, jail, path)
 
61
 
 
62
    def path_authorize(self, req):
 
63
        """Given a request, checks whether req.username is allowed to
 
64
        access req.path. Returns True on authz success, False on failure.
 
65
 
 
66
        This can't be done in the usual authorize(), because we rely on the
 
67
        jail being mounted.
 
68
        """
70
69
        # Private mode authorization: standard (only logged in user can access
71
70
        # their own files, and can access all of them).
72
 
        studpath.authorize(req)
73
 
 
74
 
def serve_file(req, owner, filename, download=False):
75
 
    """Serves a file, using one of three possibilities: interpreting the file,
76
 
    serving it directly, or denying it and returning a 403 Forbidden error.
77
 
    No return value. Writes to req (possibly throwing a server error exception
78
 
    using req.throw_error).
79
 
 
80
 
    req: An IVLE request object.
81
 
    owner: The user who owns the file being served.
82
 
    filename: Filename in the local file system.
83
 
    download:  Should the file be viewed in browser or downloaded
84
 
    """
85
 
 
86
 
    # We need a no-op trampoline run to ensure that the jail is mounted.
87
 
    # Otherwise we won't be able to authorise for public mode!
88
 
    noop_object = interpret.interpreter_objects["noop"]
89
 
    user_jail_dir = os.path.join(ivle.conf.jail_base, owner.login)
90
 
    interpret.interpret_file(req, owner, user_jail_dir, '', noop_object)
91
 
 
92
 
    # Authorize access. If failure, this throws a HTTP_FORBIDDEN error.
93
 
    authorize(req)
94
 
 
95
 
    # Jump into the jail
96
 
    interp_object = interpret.interpreter_objects["cgi-python"]
97
 
    if download:
98
 
        req.headers_out["Content-Disposition"] = "attachment"
99
 
        interpret.interpret_file(req, owner, user_jail_dir,
100
 
            serveservice_path, interp_object, gentle=False)
101
 
    else:
102
 
        interpret.interpret_file(req, owner, user_jail_dir,
103
 
            interpretservice_path, interp_object, gentle=True)
104
 
 
105
 
def serve_file_direct(req, filename, type):
106
 
    """Serves a file by directly writing it out to the response.
107
 
 
108
 
    req: An IVLE request object.
109
 
    filename: Filename in the local file system.
110
 
    type: String. Mime type to serve the file with.
111
 
    """
112
 
    if not os.access(filename, os.R_OK):
113
 
        req.throw_error(req.HTTP_NOT_FOUND,
114
 
            "The specified file does not exist.")
115
 
    req.content_type = type
116
 
    req.sendfile(filename)
 
71
        return studpath.authorize(req, req.user)
 
72
 
 
73
    def serve_file(self, req, owner, jail, path, download=False, files=None):
 
74
        """Serves a file, using one of three possibilities: interpreting it,
 
75
        serving it directly, or denying it and returning a 403 Forbidden error.
 
76
        No return value. Writes to req (possibly throwing an HTTP error).
 
77
 
 
78
        req: An IVLE request object.
 
79
        owner: The user who owns the file being served.
 
80
        jail: The user's jail.
 
81
        path: Filename in the jail.
 
82
        download:  Should the file be viewed in browser or downloaded
 
83
        """
 
84
 
 
85
        # We need a no-op trampoline run to ensure that the jail is mounted.
 
86
        # Otherwise we won't be able to authorise for public mode!
 
87
        noop_object = interpret.interpreter_objects["noop"]
 
88
        interpret.interpret_file(req, owner, jail, '', noop_object)
 
89
 
 
90
        # Authorize access. If failure, this throws a HTTP_FORBIDDEN error.
 
91
        if not self.path_authorize(req):
 
92
            raise Unauthorized()
 
93
 
 
94
        args = []
 
95
        if download:
 
96
            args.append('-d')
 
97
 
 
98
        if files and download:
 
99
            args += [os.path.join(path, f) for f in files]
 
100
        else:
 
101
            args.append(path)
 
102
 
 
103
        (out, err) = ivle.interpret.execute_raw(owner, jail, '/home',
 
104
                    os.path.join(ivle.conf.share_path, 'services/serveservice'),
 
105
                    args)
 
106
        assert not err
 
107
 
 
108
        # Remove the JSON from the front of the response, and decode it.
 
109
        json = out.split('\n', 1)[0]
 
110
        out = out[len(json) + 1:]
 
111
        response = cjson.decode(json)
 
112
 
 
113
        if 'error' in response:
 
114
            if response['error'] == 'not-found':
 
115
                raise NotFound()
 
116
            elif response['error'] in ('is-directory', 'forbidden'):
 
117
                raise Forbidden()
 
118
            elif response['error'] == 'is-executable':
 
119
                # We need to execute it. Just run it with Python in the jail.
 
120
                interp_object = interpret.interpreter_objects["cgi-python"]
 
121
                interpret.interpret_file(req, owner, jail, response['path'],
 
122
                                         interp_object, gentle=True)
 
123
                return
 
124
            else:
 
125
                raise AssertionError('Unknown error from serveservice: %s' %
 
126
                                     response['error'])
 
127
 
 
128
        if download:
 
129
            req.headers_out["Content-Disposition"] = \
 
130
                         "attachment; filename=%s" % response['name']
 
131
        req.content_type = response['type']
 
132
        req.write(out)
 
133
 
 
134
class DownloadView(ServeView):
 
135
    def __init__(self, req, path):
 
136
        super(DownloadView, self).__init__(req, path)
 
137
        filelist = req.get_fieldstorage().getlist('path')
 
138
        if filelist:
 
139
            self.files = [f.value for f in filelist]
 
140
        else:
 
141
            self.files = None
 
142
 
 
143
    def serve(self, req, owner, jail, path):
 
144
        self.serve_file(req, owner, jail, path, download=True,files=self.files)
 
145
 
 
146
class PublicServeView(ServeView):
 
147
    def __init__(self, req, path):
 
148
        req.path = path # XXX: Needed because we don't have an app prefix.
 
149
        super(PublicServeView, self).__init__(req, path)
 
150
 
 
151
    def authorize(self, req):
 
152
        # Only accessible in public mode.
 
153
        return req.user is None
 
154
 
 
155
    def path_authorize(self, req):
 
156
        # Public mode authorization: any user can access any other user's
 
157
        # files, BUT the accessed file needs to have a file named '.published'
 
158
        # in its parent directory.
 
159
        return studpath.authorize_public(req)
 
160
 
 
161
    # We don't want to redirect to a login page on Unauthorized.
 
162
    @classmethod
 
163
    def get_error_view(cls, e):
 
164
        return XHTMLErrorView
 
165
 
 
166
class Plugin(ViewPlugin, PublicViewPlugin):
 
167
    urls = [
 
168
        ('serve/*path', ServeView),
 
169
        ('download/*path', DownloadView),
 
170
    ]
 
171
 
 
172
    public_urls = [
 
173
        ('~*path', PublicServeView),
 
174
    ]