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.plugins import ViewPlugin, PublicViewPlugin
36
from ivle.webapp.errors import NotFound, Unauthorized, Forbidden
38
class ServeView(BaseView):
39
def __init__(self, req, path):
42
def authorize(self, req):
43
return req.user is not None
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)
51
owner = User.get_by_login(req.store, login)
56
self.serve(req, owner, jail, path)
58
def serve(self, req, owner, jail, path):
59
self.serve_file(req, owner, jail, path)
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.
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)
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)
67
return studpath.authorize(req, req.user)
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).
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
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)
86
# Authorize access. If failure, this throws a HTTP_FORBIDDEN error.
87
if not self.authorize(req):
94
if files and download:
95
args += [os.path.join(path, f) for f in files]
99
(out, err) = ivle.interpret.execute_raw(owner, jail, '/home',
100
os.path.join(ivle.conf.share_path, 'services/serveservice'),
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)
109
if 'error' in response:
110
if response['error'] == 'not-found':
112
elif response['error'] in ('is-directory', '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)
121
raise AssertionError('Unknown error from serveservice: %s' %
125
req.headers_out["Content-Disposition"] = \
126
"attachment; filename=%s" % response['name']
127
req.content_type = response['type']
130
class DownloadView(ServeView):
131
def __init__(self, req, path):
132
super(DownloadView, self).__init__(req, path)
133
filelist = req.get_fieldstorage().getlist('path')
135
self.files = [f.value for f in filelist]
139
def serve(self, req, owner, jail, path):
140
self.serve_file(req, owner, jail, path, download=True,files=self.files)
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)
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)
153
class Plugin(ViewPlugin, PublicViewPlugin):
155
('serve/*path', ServeView),
156
('download/*path', DownloadView),
160
('~*path', PublicServeView),