93
by mattgiuca
New directory hierarchy. |
1 |
# IVLE
|
2 |
# Copyright (C) 2007-2008 The University of Melbourne
|
|
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 |
||
18 |
# App: server
|
|
19 |
# Author: Tom Conway, Matt Giuca
|
|
20 |
# Date: 13/12/2007
|
|
21 |
||
22 |
# Serves content to the user (acting as a web server for students files).
|
|
23 |
# For most file types we just serve the static file, but
|
|
24 |
# for python files, we evaluate the python script inside
|
|
25 |
# our safe execution environment.
|
|
26 |
||
130
by mattgiuca
server: Imports os module correctly (needed by serve_file_directly). |
27 |
import os |
93
by mattgiuca
New directory hierarchy. |
28 |
|
1099.3.7
by William Grant
Fix ServeView to use the new serveservice protocol. |
29 |
import cjson |
30 |
||
1099.3.15
by William Grant
Remove interpretservice, and clean up ivle.webapp.filesystem.serve. |
31 |
from ivle import (studpath, interpret) |
1080.1.65
by William Grant
www/apps/server: Don't use ivle.interpret.get_uid() just to check that a user |
32 |
from ivle.database import User |
1099.3.6
by William Grant
Move serve over to the new framework. It sort of works, except not. |
33 |
from ivle.webapp.base.views import BaseView |
1099.1.203
by William Grant
Fix PublicServeView authorisation. |
34 |
from ivle.webapp.base.xhtml import XHTMLErrorView |
1099.1.147
by William Grant
Provide a public serve view. Under /~user/path/to/script. How shiny. |
35 |
from ivle.webapp.base.plugins import ViewPlugin, PublicViewPlugin |
1099.3.6
by William Grant
Move serve over to the new framework. It sort of works, except not. |
36 |
from ivle.webapp.errors import NotFound, Unauthorized, Forbidden |
1294.2.57
by William Grant
Serve/Download done too. |
37 |
from ivle.webapp import ApplicationRoot |
38 |
||
1099.3.6
by William Grant
Move serve over to the new framework. It sort of works, except not. |
39 |
class ServeView(BaseView): |
1294.2.60
by William Grant
Move the already-ported filesystem views to use subpaths. |
40 |
subpath_allowed = True |
1673
by William Grant
Reject off-site non-GET requests. |
41 |
offsite_posts_allowed = True |
1294.2.60
by William Grant
Move the already-ported filesystem views to use subpaths. |
42 |
|
1099.3.6
by William Grant
Move serve over to the new framework. It sort of works, except not. |
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.
|
|
1294.2.57
by William Grant
Serve/Download done too. |
50 |
(login, jail, path) = studpath.url_to_jailpaths(req.config, |
1294.2.60
by William Grant
Move the already-ported filesystem views to use subpaths. |
51 |
self.path) |
1099.3.6
by William Grant
Move serve over to the new framework. It sort of works, except not. |
52 |
|
53 |
owner = User.get_by_login(req.store, login) |
|
54 |
if not owner: |
|
55 |
# There is no user.
|
|
56 |
raise NotFound() |
|
57 |
||
1099.3.11
by William Grant
Implement a DownloadView. It mostly works. |
58 |
self.serve(req, owner, jail, path) |
59 |
||
1294.2.60
by William Grant
Move the already-ported filesystem views to use subpaths. |
60 |
@property
|
61 |
def path(self): |
|
62 |
return os.path.join(*self.subpath) if self.subpath else '' |
|
63 |
||
1099.3.11
by William Grant
Implement a DownloadView. It mostly works. |
64 |
def serve(self, req, owner, jail, path): |
1099.1.147
by William Grant
Provide a public serve view. Under /~user/path/to/script. How shiny. |
65 |
self.serve_file(req, owner, jail, path) |
66 |
||
1099.1.169
by William Grant
Don't shadow the dispatch-called authorize() in ServeView. |
67 |
def path_authorize(self, req): |
1099.1.147
by William Grant
Provide a public serve view. Under /~user/path/to/script. How shiny. |
68 |
"""Given a request, checks whether req.username is allowed to
|
69 |
access req.path. Returns True on authz success, False on failure.
|
|
1099.1.169
by William Grant
Don't shadow the dispatch-called authorize() in ServeView. |
70 |
|
71 |
This can't be done in the usual authorize(), because we rely on the
|
|
72 |
jail being mounted.
|
|
1099.1.147
by William Grant
Provide a public serve view. Under /~user/path/to/script. How shiny. |
73 |
"""
|
74 |
# Private mode authorization: standard (only logged in user can access
|
|
75 |
# their own files, and can access all of them).
|
|
76 |
return studpath.authorize(req, req.user) |
|
77 |
||
78 |
def serve_file(self, req, owner, jail, path, download=False, files=None): |
|
79 |
"""Serves a file, using one of three possibilities: interpreting it,
|
|
80 |
serving it directly, or denying it and returning a 403 Forbidden error.
|
|
81 |
No return value. Writes to req (possibly throwing an HTTP error).
|
|
82 |
||
83 |
req: An IVLE request object.
|
|
84 |
owner: The user who owns the file being served.
|
|
85 |
jail: The user's jail.
|
|
86 |
path: Filename in the jail.
|
|
87 |
download: Should the file be viewed in browser or downloaded
|
|
88 |
"""
|
|
89 |
||
90 |
# We need a no-op trampoline run to ensure that the jail is mounted.
|
|
91 |
# Otherwise we won't be able to authorise for public mode!
|
|
92 |
noop_object = interpret.interpreter_objects["noop"] |
|
93 |
interpret.interpret_file(req, owner, jail, '', noop_object) |
|
94 |
||
95 |
# Authorize access. If failure, this throws a HTTP_FORBIDDEN error.
|
|
1099.1.169
by William Grant
Don't shadow the dispatch-called authorize() in ServeView. |
96 |
if not self.path_authorize(req): |
1099.1.147
by William Grant
Provide a public serve view. Under /~user/path/to/script. How shiny. |
97 |
raise Unauthorized() |
98 |
||
99 |
args = [] |
|
100 |
if download: |
|
101 |
args.append('-d') |
|
102 |
||
103 |
if files and download: |
|
104 |
args += [os.path.join(path, f) for f in files] |
|
105 |
else: |
|
106 |
args.append(path) |
|
107 |
||
1276
by William Grant
Drop ivle.conf usage from ivle.interpret. |
108 |
(out, err) = interpret.execute_raw(req.config, owner, jail, '/home', |
1250
by William Grant
Unbreak serve - it was depending on the old names of a module. |
109 |
os.path.join(req.config['paths']['share'], |
1224
by William Grant
Remove ivle.conf dependency from ivle.webapp.filesystem.serve. |
110 |
'services/serveservice'), |
1099.1.147
by William Grant
Provide a public serve view. Under /~user/path/to/script. How shiny. |
111 |
args) |
112 |
assert not err |
|
113 |
||
114 |
# Remove the JSON from the front of the response, and decode it.
|
|
115 |
json = out.split('\n', 1)[0] |
|
116 |
out = out[len(json) + 1:] |
|
117 |
response = cjson.decode(json) |
|
118 |
||
119 |
if 'error' in response: |
|
120 |
if response['error'] == 'not-found': |
|
121 |
raise NotFound() |
|
122 |
elif response['error'] in ('is-directory', 'forbidden'): |
|
123 |
raise Forbidden() |
|
124 |
elif response['error'] == 'is-executable': |
|
125 |
# We need to execute it. Just run it with Python in the jail.
|
|
126 |
interp_object = interpret.interpreter_objects["cgi-python"] |
|
127 |
interpret.interpret_file(req, owner, jail, response['path'], |
|
128 |
interp_object, gentle=True) |
|
129 |
return
|
|
130 |
else: |
|
131 |
raise AssertionError('Unknown error from serveservice: %s' % |
|
132 |
response['error']) |
|
133 |
||
134 |
if download: |
|
1649
by Matt Giuca
serveservice and serve: Fixed download of non-ASCII-named files, by decoding filenames in serveservice (so they aren't mangled by JSON) and re-encoding them in the response headers (as you can't put unicode strings in the HTTP response). This is, as far as I can tell, the last outstanding non-ASCII filenames bug in IVLE, so I am closing bug #523500. Hooray. |
135 |
req.headers_out["Content-Disposition"] = ( |
136 |
"attachment; filename=%s" % |
|
137 |
response['name'].encode('utf-8')) |
|
1099.1.147
by William Grant
Provide a public serve view. Under /~user/path/to/script. How shiny. |
138 |
req.content_type = response['type'] |
139 |
req.write(out) |
|
93
by mattgiuca
New directory hierarchy. |
140 |
|
1099.3.11
by William Grant
Implement a DownloadView. It mostly works. |
141 |
class DownloadView(ServeView): |
1294.2.60
by William Grant
Move the already-ported filesystem views to use subpaths. |
142 |
def __init__(self, req, context, subpath=None): |
143 |
super(DownloadView, self).__init__(req, context, subpath) |
|
1099.3.12
by William Grant
Support downloading of a selection of files as a zip. |
144 |
filelist = req.get_fieldstorage().getlist('path') |
145 |
if filelist: |
|
146 |
self.files = [f.value for f in filelist] |
|
147 |
else: |
|
148 |
self.files = None |
|
149 |
||
1099.3.11
by William Grant
Implement a DownloadView. It mostly works. |
150 |
def serve(self, req, owner, jail, path): |
1099.1.147
by William Grant
Provide a public serve view. Under /~user/path/to/script. How shiny. |
151 |
self.serve_file(req, owner, jail, path, download=True,files=self.files) |
152 |
||
153 |
class PublicServeView(ServeView): |
|
1294.2.139
by William Grant
Revive PublicServeView. |
154 |
def __init__(self, req, context, subpath=None): |
155 |
# XXX: Prepend the username to the path, since the authorization
|
|
156 |
# code expects that, but req.path drops the first path segment
|
|
157 |
# for historical reasons.
|
|
158 |
req.path = os.path.join(context.login, req.path) |
|
159 |
super(PublicServeView, self).__init__(req, context, subpath) |
|
160 |
||
161 |
@property
|
|
162 |
def path(self): |
|
163 |
# XXX: Prepend the username again. See above for explanation.
|
|
164 |
return os.path.join( |
|
165 |
self.context.login, super(PublicServeView, self).path) |
|
1099.1.147
by William Grant
Provide a public serve view. Under /~user/path/to/script. How shiny. |
166 |
|
167 |
def authorize(self, req): |
|
1099.1.203
by William Grant
Fix PublicServeView authorisation. |
168 |
# Only accessible in public mode.
|
169 |
return req.user is None |
|
170 |
||
171 |
def path_authorize(self, req): |
|
262
by mattgiuca
studpath: Added "authorize" function which checks the logged in user against |
172 |
# Public mode authorization: any user can access any other user's
|
1099.1.147
by William Grant
Provide a public serve view. Under /~user/path/to/script. How shiny. |
173 |
# files, BUT the accessed file needs to have a file named '.published'
|
174 |
# in its parent directory.
|
|
175 |
return studpath.authorize_public(req) |
|
176 |
||
177 |
class Plugin(ViewPlugin, PublicViewPlugin): |
|
1294.2.60
by William Grant
Move the already-ported filesystem views to use subpaths. |
178 |
views = [(ApplicationRoot, 'serve', ServeView), |
179 |
(ApplicationRoot, 'download', DownloadView) |
|
1294.2.57
by William Grant
Serve/Download done too. |
180 |
]
|
181 |
||
1294.2.139
by William Grant
Revive PublicServeView. |
182 |
public_views = [(User, None, PublicServeView)] |