15
15
# along with this program; if not, write to the Free Software
16
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
# Author: Matt Giuca, Will Grant
18
# Author: Matt Giuca, Will Grant, Nick Chadwick
26
import genshi.template
25
28
from ivle.webapp.base.views import BaseView
26
from ivle.webapp.errors import BadRequest, MethodNotAllowed
29
from ivle.webapp.errors import BadRequest, MethodNotAllowed, Unauthorized
28
31
class RESTView(BaseView):
33
36
content_type = "application/octet-stream"
35
def __init__(self, req, *args, **kwargs):
37
setattr(self, key, kwargs[key])
39
38
def render(self, req):
40
39
raise NotImplementedError()
49
48
lambda self: [m for m in ('GET', 'PUT', 'PATCH')
50
49
if hasattr(self, m)] + ['POST'])
51
def authorize(self, req):
52
return True # Real authz performed in render().
54
def authorize_method(self, req, op):
55
if not hasattr(op, '_rest_api_permission'):
58
if op._rest_api_permission not in self.get_permissions(req.user):
61
def convert_bool(self, value):
62
if value in ('True', 'true', True):
64
elif value in ('False', 'false', False):
52
69
def render(self, req):
53
70
if req.method not in self._allowed_methods:
54
71
raise MethodNotAllowed(allowed=self._allowed_methods)
56
73
if req.method == 'GET':
74
self.authorize_method(req, self.GET)
57
75
outjson = self.GET(req)
58
76
# Since PATCH isn't yet an official HTTP method, we allow users to
59
77
# turn a PUT into a PATCH by supplying a special header.
60
78
elif req.method == 'PATCH' or (req.method == 'PUT' and
61
79
'X-IVLE-Patch-Semantics' in req.headers_in and
62
80
req.headers_in['X-IVLE-Patch-Semantics'].lower() == 'yes'):
81
self.authorize_method(req, self.PATCH)
64
83
input = cjson.decode(req.read())
65
84
except cjson.DecodeError:
66
85
raise BadRequest('Invalid JSON data')
67
86
outjson = self.PATCH(req, input)
68
87
elif req.method == 'PUT':
88
self.authorize_method(req, self.PUT)
70
90
input = cjson.decode(req.read())
71
91
except cjson.DecodeError:
74
94
# POST implies named operation.
75
95
elif req.method == 'POST':
76
96
# TODO: Check Content-Type and implement multipart/form-data.
77
opargs = dict(cgi.parse_qsl(req.read()))
98
opargs = dict(cgi.parse_qsl(data, keep_blank_values=1))
79
100
opname = opargs['ivle.op']
80
101
del opargs['ivle.op']
90
111
not op._rest_api_callable:
91
112
raise BadRequest('Invalid named operation.')
114
self.authorize_method(req, op)
93
116
# Find any missing arguments, except for the first two (self, req)
94
117
(args, vaargs, varkw, defaults) = inspect.getargspec(op)
116
139
outjson = op(req, **opargs)
118
141
req.content_type = self.content_type
142
self.write_json(req, outjson)
144
#This is a separate function to allow additional data to be passed through
145
def write_json(self, req, outjson):
119
146
if outjson is not None:
120
147
req.write(cjson.encode(outjson))
123
def named_operation(meth):
151
class XHTMLRESTView(JSONRESTView):
152
"""A special type of RESTView which takes enhances the standard JSON
153
with genshi XHTML functions.
155
XHTMLRESTViews should have a template, which is rendered using their
156
context. This is returned in the JSON as 'html'"""
158
ctx = genshi.template.Context()
160
def render_fragment(self):
161
if self.template is None:
162
raise NotImplementedError()
164
rest_template = os.path.join(os.path.dirname(
165
inspect.getmodule(self).__file__), self.template)
166
loader = genshi.template.TemplateLoader(".", auto_reload=True)
167
tmpl = loader.load(rest_template)
169
return tmpl.generate(self.ctx).render('xhtml', doctype='xhtml')
171
# This renders the template and adds it to the json
172
def write_json(self, req, outjson):
173
outjson["html"] = self.render_fragment()
174
req.write(cjson.encode(outjson))
177
class named_operation(object):
124
178
'''Declare a function to be accessible to HTTP users via the REST API.
126
meth._rest_api_callable = True
180
def __init__(self, permission):
181
self.permission = permission
183
def __call__(self, func):
184
func._rest_api_callable = True
185
func._rest_api_permission = self.permission
188
class require_permission(object):
189
'''Declare the permission required for use of a method via the REST API.
191
def __init__(self, permission):
192
self.permission = permission
194
def __call__(self, func):
195
func._rest_api_permission = self.permission