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

« back to all changes in this revision

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

Merge from new-dispatch.

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.plugins import ViewPlugin, PublicViewPlugin
 
36
from ivle.webapp.errors import NotFound, Unauthorized, Forbidden
 
37
 
 
38
class ServeView(BaseView):
 
39
    def __init__(self, req, path):
 
40
        self.path = path
 
41
 
 
42
    def authorize(self, req):
 
43
        return req.user is not None
 
44
 
 
45
    def render(self, req):
 
46
        """Handler for the Server application which serves pages."""
 
47
        # Get the username of the student whose work we are browsing, and the
 
48
        # path on the local machine where the file is stored.
 
49
        (login, jail, path) = studpath.url_to_jailpaths(self.path)
 
50
 
 
51
        owner = User.get_by_login(req.store, login)
 
52
        if not owner:
 
53
            # There is no user.
 
54
            raise NotFound()
 
55
 
 
56
        self.serve(req, owner, jail, path)
 
57
 
 
58
    def serve(self, req, owner, jail, path):
 
59
        self.serve_file(req, owner, jail, path)
 
60
 
 
61
    def authorize(self, req):
 
62
        """Given a request, checks whether req.username is allowed to
 
63
        access req.path. Returns True on authz success, False on failure.
 
64
        """
70
65
        # Private mode authorization: standard (only logged in user can access
71
66
        # 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)
 
67
        return studpath.authorize(req, req.user)
 
68
 
 
69
    def serve_file(self, req, owner, jail, path, download=False, files=None):
 
70
        """Serves a file, using one of three possibilities: interpreting it,
 
71
        serving it directly, or denying it and returning a 403 Forbidden error.
 
72
        No return value. Writes to req (possibly throwing an HTTP error).
 
73
 
 
74
        req: An IVLE request object.
 
75
        owner: The user who owns the file being served.
 
76
        jail: The user's jail.
 
77
        path: Filename in the jail.
 
78
        download:  Should the file be viewed in browser or downloaded
 
79
        """
 
80
 
 
81
        # We need a no-op trampoline run to ensure that the jail is mounted.
 
82
        # Otherwise we won't be able to authorise for public mode!
 
83
        noop_object = interpret.interpreter_objects["noop"]
 
84
        interpret.interpret_file(req, owner, jail, '', noop_object)
 
85
 
 
86
        # Authorize access. If failure, this throws a HTTP_FORBIDDEN error.
 
87
        if not self.authorize(req):
 
88
            raise Unauthorized()
 
89
 
 
90
        args = []
 
91
        if download:
 
92
            args.append('-d')
 
93
 
 
94
        if files and download:
 
95
            args += [os.path.join(path, f) for f in files]
 
96
        else:
 
97
            args.append(path)
 
98
 
 
99
        (out, err) = ivle.interpret.execute_raw(owner, jail, '/home',
 
100
                    os.path.join(ivle.conf.share_path, 'services/serveservice'),
 
101
                    args)
 
102
        assert not err
 
103
 
 
104
        # Remove the JSON from the front of the response, and decode it.
 
105
        json = out.split('\n', 1)[0]
 
106
        out = out[len(json) + 1:]
 
107
        response = cjson.decode(json)
 
108
 
 
109
        if 'error' in response:
 
110
            if response['error'] == 'not-found':
 
111
                raise NotFound()
 
112
            elif response['error'] in ('is-directory', 'forbidden'):
 
113
                raise Forbidden()
 
114
            elif response['error'] == 'is-executable':
 
115
                # We need to execute it. Just run it with Python in the jail.
 
116
                interp_object = interpret.interpreter_objects["cgi-python"]
 
117
                interpret.interpret_file(req, owner, jail, response['path'],
 
118
                                         interp_object, gentle=True)
 
119
                return
 
120
            else:
 
121
                raise AssertionError('Unknown error from serveservice: %s' %
 
122
                                     response['error'])
 
123
 
 
124
        if download:
 
125
            req.headers_out["Content-Disposition"] = \
 
126
                         "attachment; filename=%s" % response['name']
 
127
        req.content_type = response['type']
 
128
        req.write(out)
 
129
 
 
130
class DownloadView(ServeView):
 
131
    def __init__(self, req, path):
 
132
        super(DownloadView, self).__init__(req, path)
 
133
        filelist = req.get_fieldstorage().getlist('path')
 
134
        if filelist:
 
135
            self.files = [f.value for f in filelist]
 
136
        else:
 
137
            self.files = None
 
138
 
 
139
    def serve(self, req, owner, jail, path):
 
140
        self.serve_file(req, owner, jail, path, download=True,files=self.files)
 
141
 
 
142
class PublicServeView(ServeView):
 
143
    def __init__(self, req, path):
 
144
        req.path = path # XXX: Needed because we don't have an app prefix.
 
145
        super(PublicServeView, self).__init__(req, path)
 
146
 
 
147
    def authorize(self, req):
 
148
        # Public mode authorization: any user can access any other user's
 
149
        # files, BUT the accessed file needs to have a file named '.published'
 
150
        # in its parent directory.
 
151
        return studpath.authorize_public(req)
 
152
 
 
153
class Plugin(ViewPlugin, PublicViewPlugin):
 
154
    urls = [
 
155
        ('serve/*path', ServeView),
 
156
        ('download/*path', DownloadView),
 
157
    ]
 
158
 
 
159
    public_urls = [
 
160
        ('~*path', PublicServeView),
 
161
    ]