25
25
# our safe execution environment.
30
from ivle import (util, studpath, interpret)
31
from ivle import (studpath, interpret)
32
33
from ivle.database import User
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')
38
# Serve all files as application/octet-stream so the browser presents them as
40
default_mimetype = "application/octet-stream"
41
zip_mimetype = "application/zip"
44
"""Handler for the Server application which serves pages."""
45
req.write_html_head_foot = False
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)
51
owner = User.get_by_login(req.store, login)
54
req.throw_error(req.HTTP_NOT_FOUND,
55
"The path specified is invalid.")
57
serve_file(req, owner, path)
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.
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)
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
39
class ServeView(BaseView):
40
def __init__(self, req, path):
43
def authorize(self, req):
44
return req.user is not None
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)
52
owner = User.get_by_login(req.store, login)
57
self.serve(req, owner, jail, path)
59
def serve(self, req, owner, jail, path):
60
self.serve_file(req, owner, jail, path)
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.
66
This can't be done in the usual authorize(), because we rely on the
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)
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).
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
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)
92
# Authorize access. If failure, this throws a HTTP_FORBIDDEN error.
96
interp_object = interpret.interpreter_objects["cgi-python"]
98
req.headers_out["Content-Disposition"] = "attachment"
99
interpret.interpret_file(req, owner, user_jail_dir,
100
serveservice_path, interp_object, gentle=False)
102
interpret.interpret_file(req, owner, user_jail_dir,
103
interpretservice_path, interp_object, gentle=True)
105
def serve_file_direct(req, filename, type):
106
"""Serves a file by directly writing it out to the response.
108
req: An IVLE request object.
109
filename: Filename in the local file system.
110
type: String. Mime type to serve the file with.
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)
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).
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
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)
90
# Authorize access. If failure, this throws a HTTP_FORBIDDEN error.
91
if not self.path_authorize(req):
98
if files and download:
99
args += [os.path.join(path, f) for f in files]
103
(out, err) = ivle.interpret.execute_raw(owner, jail, '/home',
104
os.path.join(ivle.conf.share_path, 'services/serveservice'),
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)
113
if 'error' in response:
114
if response['error'] == 'not-found':
116
elif response['error'] in ('is-directory', '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)
125
raise AssertionError('Unknown error from serveservice: %s' %
129
req.headers_out["Content-Disposition"] = \
130
"attachment; filename=%s" % response['name']
131
req.content_type = response['type']
134
class DownloadView(ServeView):
135
def __init__(self, req, path):
136
super(DownloadView, self).__init__(req, path)
137
filelist = req.get_fieldstorage().getlist('path')
139
self.files = [f.value for f in filelist]
143
def serve(self, req, owner, jail, path):
144
self.serve_file(req, owner, jail, path, download=True,files=self.files)
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)
151
def authorize(self, req):
152
# Only accessible in public mode.
153
return req.user is None
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)
161
# We don't want to redirect to a login page on Unauthorized.
163
def get_error_view(cls, e):
164
return XHTMLErrorView
166
class Plugin(ViewPlugin, PublicViewPlugin):
168
('serve/*path', ServeView),
169
('download/*path', DownloadView),
173
('~*path', PublicServeView),