blob: c51e4f9eecc960a9351b471fabb300537ae5584b [file] [log] [blame]
Brad Bishop68caa1e2016-03-04 15:42:08 -05001# Contributors Listed Below - COPYRIGHT 2016
2# [+] International Business Machines Corp.
3#
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14# implied. See the License for the specific language governing
15# permissions and limitations under the License.
16
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050017import os
Brad Bishopaa65f6e2015-10-27 16:28:51 -040018import dbus
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050019import dbus.exceptions
20import json
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050021from xml.etree import ElementTree
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050022from bottle import Bottle, abort, request, response, JSONPlugin, HTTPError
Brad Bishopb103d2d2016-03-04 16:19:14 -050023import obmc.utils.misc
Brad Bishopb103d2d2016-03-04 16:19:14 -050024from obmc.dbuslib.introspection import IntrospectionNodeParser
25import obmc.mapper
Brad Bishop2f428582015-12-02 10:56:11 -050026import spwd
27import grp
28import crypt
Deepak Kodihalli1af301a2017-04-11 07:29:01 -050029import tempfile
Brad Bishopaa65f6e2015-10-27 16:28:51 -040030
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050031DBUS_UNKNOWN_INTERFACE = 'org.freedesktop.UnknownInterface'
Brad Bishopf4e74982016-04-01 14:53:05 -040032DBUS_UNKNOWN_INTERFACE_ERROR = 'org.freedesktop.DBus.Error.UnknownInterface'
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050033DBUS_UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod'
34DBUS_INVALID_ARGS = 'org.freedesktop.DBus.Error.InvalidArgs'
Brad Bishopd4578922015-12-02 11:10:36 -050035DBUS_TYPE_ERROR = 'org.freedesktop.DBus.Python.TypeError'
Deepak Kodihalli6075bb42017-04-04 05:49:17 -050036DELETE_IFACE = 'xyz.openbmc_project.Object.Delete'
Brad Bishop9ee57c42015-11-03 14:59:29 -050037
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050038_4034_msg = "The specified %s cannot be %s: '%s'"
Brad Bishopaa65f6e2015-10-27 16:28:51 -040039
Brad Bishop87b63c12016-03-18 14:47:51 -040040
Brad Bishop2f428582015-12-02 10:56:11 -050041def valid_user(session, *a, **kw):
Brad Bishop87b63c12016-03-18 14:47:51 -040042 ''' Authorization plugin callback that checks
43 that the user is logged in. '''
44 if session is None:
Brad Bishopdc3fbfa2016-09-08 09:51:38 -040045 abort(401, 'Login required')
Brad Bishop87b63c12016-03-18 14:47:51 -040046
Brad Bishop2f428582015-12-02 10:56:11 -050047
48class UserInGroup:
Brad Bishop87b63c12016-03-18 14:47:51 -040049 ''' Authorization plugin callback that checks that the user is logged in
50 and a member of a group. '''
51 def __init__(self, group):
52 self.group = group
Brad Bishop2f428582015-12-02 10:56:11 -050053
Brad Bishop87b63c12016-03-18 14:47:51 -040054 def __call__(self, session, *a, **kw):
55 valid_user(session, *a, **kw)
56 res = False
Brad Bishop2f428582015-12-02 10:56:11 -050057
Brad Bishop87b63c12016-03-18 14:47:51 -040058 try:
59 res = session['user'] in grp.getgrnam(self.group)[3]
60 except KeyError:
61 pass
Brad Bishop2f428582015-12-02 10:56:11 -050062
Brad Bishop87b63c12016-03-18 14:47:51 -040063 if not res:
64 abort(403, 'Insufficient access')
65
Brad Bishop2f428582015-12-02 10:56:11 -050066
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050067class RouteHandler(object):
Brad Bishop6d190602016-04-15 13:09:39 -040068 _require_auth = obmc.utils.misc.makelist(valid_user)
Brad Bishopd0c404a2017-02-21 09:23:25 -050069 _enable_cors = True
Brad Bishopaa65f6e2015-10-27 16:28:51 -040070
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -050071 def __init__(self, app, bus, verbs, rules, content_type=''):
Brad Bishop87b63c12016-03-18 14:47:51 -040072 self.app = app
73 self.bus = bus
Brad Bishopb103d2d2016-03-04 16:19:14 -050074 self.mapper = obmc.mapper.Mapper(bus)
Brad Bishop6d190602016-04-15 13:09:39 -040075 self._verbs = obmc.utils.misc.makelist(verbs)
Brad Bishop87b63c12016-03-18 14:47:51 -040076 self._rules = rules
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -050077 self._content_type = content_type
Brad Bishop0f79e522016-03-18 13:33:17 -040078 self.intf_match = obmc.utils.misc.org_dot_openbmc_match
Brad Bishopaa65f6e2015-10-27 16:28:51 -040079
Brad Bishop88c76a42017-02-21 00:02:02 -050080 if 'GET' in self._verbs:
81 self._verbs = list(set(self._verbs + ['HEAD']))
Brad Bishopd4c1c552017-02-21 00:07:28 -050082 if 'OPTIONS' not in self._verbs:
83 self._verbs.append('OPTIONS')
Brad Bishop88c76a42017-02-21 00:02:02 -050084
Brad Bishop87b63c12016-03-18 14:47:51 -040085 def _setup(self, **kw):
86 request.route_data = {}
Brad Bishopd4c1c552017-02-21 00:07:28 -050087
Brad Bishop87b63c12016-03-18 14:47:51 -040088 if request.method in self._verbs:
Brad Bishopd4c1c552017-02-21 00:07:28 -050089 if request.method != 'OPTIONS':
90 return self.setup(**kw)
Brad Bishop88c76a42017-02-21 00:02:02 -050091
Brad Bishopd4c1c552017-02-21 00:07:28 -050092 # Javascript implementations will not send credentials
93 # with an OPTIONS request. Don't help malicious clients
94 # by checking the path here and returning a 404 if the
95 # path doesn't exist.
96 return None
Brad Bishop88c76a42017-02-21 00:02:02 -050097
Brad Bishopd4c1c552017-02-21 00:07:28 -050098 # Return 405
Brad Bishop88c76a42017-02-21 00:02:02 -050099 raise HTTPError(
100 405, "Method not allowed.", Allow=','.join(self._verbs))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400101
Brad Bishop87b63c12016-03-18 14:47:51 -0400102 def __call__(self, **kw):
103 return getattr(self, 'do_' + request.method.lower())(**kw)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400104
Brad Bishop88c76a42017-02-21 00:02:02 -0500105 def do_head(self, **kw):
106 return self.do_get(**kw)
107
Brad Bishopd4c1c552017-02-21 00:07:28 -0500108 def do_options(self, **kw):
109 for v in self._verbs:
110 response.set_header(
111 'Allow',
112 ','.join(self._verbs))
113 return None
114
Brad Bishop87b63c12016-03-18 14:47:51 -0400115 def install(self):
116 self.app.route(
117 self._rules, callback=self,
Brad Bishopd4c1c552017-02-21 00:07:28 -0500118 method=['OPTIONS', 'GET', 'PUT', 'PATCH', 'POST', 'DELETE'])
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400119
Brad Bishop87b63c12016-03-18 14:47:51 -0400120 @staticmethod
121 def try_mapper_call(f, callback=None, **kw):
122 try:
123 return f(**kw)
124 except dbus.exceptions.DBusException, e:
Brad Bishopfce77562016-11-28 15:44:18 -0500125 if e.get_dbus_name() == \
126 'org.freedesktop.DBus.Error.ObjectPathInUse':
127 abort(503, str(e))
Brad Bishopb103d2d2016-03-04 16:19:14 -0500128 if e.get_dbus_name() != obmc.mapper.MAPPER_NOT_FOUND:
Brad Bishop87b63c12016-03-18 14:47:51 -0400129 raise
130 if callback is None:
131 def callback(e, **kw):
132 abort(404, str(e))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400133
Brad Bishop87b63c12016-03-18 14:47:51 -0400134 callback(e, **kw)
135
136 @staticmethod
137 def try_properties_interface(f, *a):
138 try:
139 return f(*a)
140 except dbus.exceptions.DBusException, e:
141 if DBUS_UNKNOWN_INTERFACE in e.get_dbus_message():
142 # interface doesn't have any properties
143 return None
Brad Bishopf4e74982016-04-01 14:53:05 -0400144 if DBUS_UNKNOWN_INTERFACE_ERROR in e.get_dbus_name():
145 # interface doesn't have any properties
146 return None
Brad Bishop87b63c12016-03-18 14:47:51 -0400147 if DBUS_UNKNOWN_METHOD == e.get_dbus_name():
148 # properties interface not implemented at all
149 return None
150 raise
151
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400152
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500153class DirectoryHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400154 verbs = 'GET'
155 rules = '<path:path>/'
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400156
Brad Bishop87b63c12016-03-18 14:47:51 -0400157 def __init__(self, app, bus):
158 super(DirectoryHandler, self).__init__(
159 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400160
Brad Bishop87b63c12016-03-18 14:47:51 -0400161 def find(self, path='/'):
162 return self.try_mapper_call(
163 self.mapper.get_subtree_paths, path=path, depth=1)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400164
Brad Bishop87b63c12016-03-18 14:47:51 -0400165 def setup(self, path='/'):
166 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400167
Brad Bishop87b63c12016-03-18 14:47:51 -0400168 def do_get(self, path='/'):
169 return request.route_data['map']
170
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400171
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500172class ListNamesHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400173 verbs = 'GET'
174 rules = ['/list', '<path:path>/list']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400175
Brad Bishop87b63c12016-03-18 14:47:51 -0400176 def __init__(self, app, bus):
177 super(ListNamesHandler, self).__init__(
178 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400179
Brad Bishop87b63c12016-03-18 14:47:51 -0400180 def find(self, path='/'):
181 return self.try_mapper_call(
182 self.mapper.get_subtree, path=path).keys()
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400183
Brad Bishop87b63c12016-03-18 14:47:51 -0400184 def setup(self, path='/'):
185 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400186
Brad Bishop87b63c12016-03-18 14:47:51 -0400187 def do_get(self, path='/'):
188 return request.route_data['map']
189
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400190
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500191class ListHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400192 verbs = 'GET'
193 rules = ['/enumerate', '<path:path>/enumerate']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400194
Brad Bishop87b63c12016-03-18 14:47:51 -0400195 def __init__(self, app, bus):
196 super(ListHandler, self).__init__(
197 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400198
Brad Bishop87b63c12016-03-18 14:47:51 -0400199 def find(self, path='/'):
200 return self.try_mapper_call(
201 self.mapper.get_subtree, path=path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400202
Brad Bishop87b63c12016-03-18 14:47:51 -0400203 def setup(self, path='/'):
204 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400205
Brad Bishop87b63c12016-03-18 14:47:51 -0400206 def do_get(self, path='/'):
Brad Bishop71527b42016-04-01 14:51:14 -0400207 return {x: y for x, y in self.mapper.enumerate_subtree(
208 path,
209 mapper_data=request.route_data['map']).dataitems()}
Brad Bishop87b63c12016-03-18 14:47:51 -0400210
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400211
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500212class MethodHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400213 verbs = 'POST'
214 rules = '<path:path>/action/<method>'
215 request_type = list
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500216 content_type = 'application/json'
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400217
Brad Bishop87b63c12016-03-18 14:47:51 -0400218 def __init__(self, app, bus):
219 super(MethodHandler, self).__init__(
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500220 app, bus, self.verbs, self.rules, self.content_type)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400221
Brad Bishop87b63c12016-03-18 14:47:51 -0400222 def find(self, path, method):
223 busses = self.try_mapper_call(
224 self.mapper.get_object, path=path)
225 for items in busses.iteritems():
226 m = self.find_method_on_bus(path, method, *items)
227 if m:
228 return m
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400229
Brad Bishop87b63c12016-03-18 14:47:51 -0400230 abort(404, _4034_msg % ('method', 'found', method))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400231
Brad Bishop87b63c12016-03-18 14:47:51 -0400232 def setup(self, path, method):
233 request.route_data['method'] = self.find(path, method)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400234
Brad Bishop87b63c12016-03-18 14:47:51 -0400235 def do_post(self, path, method):
236 try:
237 if request.parameter_list:
238 return request.route_data['method'](*request.parameter_list)
239 else:
240 return request.route_data['method']()
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400241
Brad Bishop87b63c12016-03-18 14:47:51 -0400242 except dbus.exceptions.DBusException, e:
243 if e.get_dbus_name() == DBUS_INVALID_ARGS:
244 abort(400, str(e))
245 if e.get_dbus_name() == DBUS_TYPE_ERROR:
246 abort(400, str(e))
247 raise
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400248
Brad Bishop87b63c12016-03-18 14:47:51 -0400249 @staticmethod
250 def find_method_in_interface(method, obj, interface, methods):
251 if methods is None:
252 return None
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400253
Brad Bishop6d190602016-04-15 13:09:39 -0400254 method = obmc.utils.misc.find_case_insensitive(method, methods.keys())
Brad Bishop87b63c12016-03-18 14:47:51 -0400255 if method is not None:
256 iface = dbus.Interface(obj, interface)
257 return iface.get_dbus_method(method)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400258
Brad Bishop87b63c12016-03-18 14:47:51 -0400259 def find_method_on_bus(self, path, method, bus, interfaces):
260 obj = self.bus.get_object(bus, path, introspect=False)
261 iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
262 data = iface.Introspect()
263 parser = IntrospectionNodeParser(
264 ElementTree.fromstring(data),
Brad Bishopb103d2d2016-03-04 16:19:14 -0500265 intf_match=obmc.utils.misc.ListMatch(interfaces))
Brad Bishop87b63c12016-03-18 14:47:51 -0400266 for x, y in parser.get_interfaces().iteritems():
267 m = self.find_method_in_interface(
268 method, obj, x, y.get('method'))
269 if m:
270 return m
271
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400272
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500273class PropertyHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400274 verbs = ['PUT', 'GET']
275 rules = '<path:path>/attr/<prop>'
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500276 content_type = 'application/json'
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400277
Brad Bishop87b63c12016-03-18 14:47:51 -0400278 def __init__(self, app, bus):
279 super(PropertyHandler, self).__init__(
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500280 app, bus, self.verbs, self.rules, self.content_type)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400281
Brad Bishop87b63c12016-03-18 14:47:51 -0400282 def find(self, path, prop):
283 self.app.instance_handler.setup(path)
284 obj = self.app.instance_handler.do_get(path)
Brad Bishop56ad87f2017-02-21 23:33:29 -0500285 real_name = obmc.utils.misc.find_case_insensitive(
286 prop, obj.keys())
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400287
Brad Bishop56ad87f2017-02-21 23:33:29 -0500288 if not real_name:
289 if request.method == 'PUT':
290 abort(403, _4034_msg % ('property', 'created', prop))
291 else:
292 abort(404, _4034_msg % ('property', 'found', prop))
293 return real_name, {path: obj}
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500294
Brad Bishop87b63c12016-03-18 14:47:51 -0400295 def setup(self, path, prop):
Brad Bishop56ad87f2017-02-21 23:33:29 -0500296 name, obj = self.find(path, prop)
297 request.route_data['obj'] = obj
298 request.route_data['name'] = name
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500299
Brad Bishop87b63c12016-03-18 14:47:51 -0400300 def do_get(self, path, prop):
Brad Bishop56ad87f2017-02-21 23:33:29 -0500301 name = request.route_data['name']
302 return request.route_data['obj'][path][name]
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500303
Brad Bishop87b63c12016-03-18 14:47:51 -0400304 def do_put(self, path, prop, value=None):
305 if value is None:
306 value = request.parameter_list
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500307
Brad Bishop87b63c12016-03-18 14:47:51 -0400308 prop, iface, properties_iface = self.get_host_interface(
309 path, prop, request.route_data['map'][path])
310 try:
311 properties_iface.Set(iface, prop, value)
312 except ValueError, e:
313 abort(400, str(e))
314 except dbus.exceptions.DBusException, e:
315 if e.get_dbus_name() == DBUS_INVALID_ARGS:
316 abort(403, str(e))
317 raise
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500318
Brad Bishop87b63c12016-03-18 14:47:51 -0400319 def get_host_interface(self, path, prop, bus_info):
320 for bus, interfaces in bus_info.iteritems():
321 obj = self.bus.get_object(bus, path, introspect=True)
322 properties_iface = dbus.Interface(
323 obj, dbus_interface=dbus.PROPERTIES_IFACE)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500324
Brad Bishop87b63c12016-03-18 14:47:51 -0400325 info = self.get_host_interface_on_bus(
326 path, prop, properties_iface, bus, interfaces)
327 if info is not None:
328 prop, iface = info
329 return prop, iface, properties_iface
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500330
Brad Bishop87b63c12016-03-18 14:47:51 -0400331 def get_host_interface_on_bus(self, path, prop, iface, bus, interfaces):
332 for i in interfaces:
333 properties = self.try_properties_interface(iface.GetAll, i)
Brad Bishop69cb6d12017-02-21 12:01:52 -0500334 if not properties:
Brad Bishop87b63c12016-03-18 14:47:51 -0400335 continue
Leonel Gonzalez409f6712017-05-24 09:51:55 -0500336 match = obmc.utils.misc.find_case_insensitive(
Brad Bishop8b0d3fa2016-11-28 15:41:47 -0500337 prop, properties.keys())
Leonel Gonzalez409f6712017-05-24 09:51:55 -0500338 if match is None:
Brad Bishop87b63c12016-03-18 14:47:51 -0400339 continue
Leonel Gonzalez409f6712017-05-24 09:51:55 -0500340 prop = match
Brad Bishop87b63c12016-03-18 14:47:51 -0400341 return prop, i
342
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500343
Brad Bishop2503bd62015-12-16 17:56:12 -0500344class SchemaHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400345 verbs = ['GET']
346 rules = '<path:path>/schema'
Brad Bishop2503bd62015-12-16 17:56:12 -0500347
Brad Bishop87b63c12016-03-18 14:47:51 -0400348 def __init__(self, app, bus):
349 super(SchemaHandler, self).__init__(
350 app, bus, self.verbs, self.rules)
Brad Bishop2503bd62015-12-16 17:56:12 -0500351
Brad Bishop87b63c12016-03-18 14:47:51 -0400352 def find(self, path):
353 return self.try_mapper_call(
354 self.mapper.get_object,
355 path=path)
Brad Bishop2503bd62015-12-16 17:56:12 -0500356
Brad Bishop87b63c12016-03-18 14:47:51 -0400357 def setup(self, path):
358 request.route_data['map'] = self.find(path)
Brad Bishop2503bd62015-12-16 17:56:12 -0500359
Brad Bishop87b63c12016-03-18 14:47:51 -0400360 def do_get(self, path):
361 schema = {}
362 for x in request.route_data['map'].iterkeys():
363 obj = self.bus.get_object(x, path, introspect=False)
364 iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
365 data = iface.Introspect()
366 parser = IntrospectionNodeParser(
367 ElementTree.fromstring(data))
368 for x, y in parser.get_interfaces().iteritems():
369 schema[x] = y
Brad Bishop2503bd62015-12-16 17:56:12 -0500370
Brad Bishop87b63c12016-03-18 14:47:51 -0400371 return schema
372
Brad Bishop2503bd62015-12-16 17:56:12 -0500373
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500374class InstanceHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400375 verbs = ['GET', 'PUT', 'DELETE']
376 rules = '<path:path>'
377 request_type = dict
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500378
Brad Bishop87b63c12016-03-18 14:47:51 -0400379 def __init__(self, app, bus):
380 super(InstanceHandler, self).__init__(
381 app, bus, self.verbs, self.rules)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500382
Brad Bishop87b63c12016-03-18 14:47:51 -0400383 def find(self, path, callback=None):
384 return {path: self.try_mapper_call(
385 self.mapper.get_object,
386 callback,
387 path=path)}
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500388
Brad Bishop87b63c12016-03-18 14:47:51 -0400389 def setup(self, path):
390 callback = None
391 if request.method == 'PUT':
392 def callback(e, **kw):
393 abort(403, _4034_msg % ('resource', 'created', path))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500394
Brad Bishop87b63c12016-03-18 14:47:51 -0400395 if request.route_data.get('map') is None:
396 request.route_data['map'] = self.find(path, callback)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500397
Brad Bishop87b63c12016-03-18 14:47:51 -0400398 def do_get(self, path):
Brad Bishop71527b42016-04-01 14:51:14 -0400399 return self.mapper.enumerate_object(
400 path,
401 mapper_data=request.route_data['map'])
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500402
Brad Bishop87b63c12016-03-18 14:47:51 -0400403 def do_put(self, path):
404 # make sure all properties exist in the request
405 obj = set(self.do_get(path).keys())
406 req = set(request.parameter_list.keys())
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500407
Brad Bishop87b63c12016-03-18 14:47:51 -0400408 diff = list(obj.difference(req))
409 if diff:
410 abort(403, _4034_msg % (
411 'resource', 'removed', '%s/attr/%s' % (path, diff[0])))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500412
Brad Bishop87b63c12016-03-18 14:47:51 -0400413 diff = list(req.difference(obj))
414 if diff:
415 abort(403, _4034_msg % (
416 'resource', 'created', '%s/attr/%s' % (path, diff[0])))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500417
Brad Bishop87b63c12016-03-18 14:47:51 -0400418 for p, v in request.parameter_list.iteritems():
419 self.app.property_handler.do_put(
420 path, p, v)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500421
Brad Bishop87b63c12016-03-18 14:47:51 -0400422 def do_delete(self, path):
423 for bus_info in request.route_data['map'][path].iteritems():
424 if self.bus_missing_delete(path, *bus_info):
425 abort(403, _4034_msg % ('resource', 'removed', path))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500426
Brad Bishop87b63c12016-03-18 14:47:51 -0400427 for bus in request.route_data['map'][path].iterkeys():
428 self.delete_on_bus(path, bus)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500429
Brad Bishop87b63c12016-03-18 14:47:51 -0400430 def bus_missing_delete(self, path, bus, interfaces):
431 return DELETE_IFACE not in interfaces
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500432
Brad Bishop87b63c12016-03-18 14:47:51 -0400433 def delete_on_bus(self, path, bus):
434 obj = self.bus.get_object(bus, path, introspect=False)
435 delete_iface = dbus.Interface(
436 obj, dbus_interface=DELETE_IFACE)
437 delete_iface.Delete()
438
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500439
Brad Bishop2f428582015-12-02 10:56:11 -0500440class SessionHandler(MethodHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400441 ''' Handles the /login and /logout routes, manages
442 server side session store and session cookies. '''
Brad Bishop2f428582015-12-02 10:56:11 -0500443
Brad Bishop87b63c12016-03-18 14:47:51 -0400444 rules = ['/login', '/logout']
445 login_str = "User '%s' logged %s"
446 bad_passwd_str = "Invalid username or password"
447 no_user_str = "No user logged in"
448 bad_json_str = "Expecting request format { 'data': " \
449 "[<username>, <password>] }, got '%s'"
450 _require_auth = None
451 MAX_SESSIONS = 16
Brad Bishop2f428582015-12-02 10:56:11 -0500452
Brad Bishop87b63c12016-03-18 14:47:51 -0400453 def __init__(self, app, bus):
454 super(SessionHandler, self).__init__(
455 app, bus)
456 self.hmac_key = os.urandom(128)
457 self.session_store = []
Brad Bishop2f428582015-12-02 10:56:11 -0500458
Brad Bishop87b63c12016-03-18 14:47:51 -0400459 @staticmethod
460 def authenticate(username, clear):
461 try:
462 encoded = spwd.getspnam(username)[1]
463 return encoded == crypt.crypt(clear, encoded)
464 except KeyError:
465 return False
Brad Bishop2f428582015-12-02 10:56:11 -0500466
Brad Bishop87b63c12016-03-18 14:47:51 -0400467 def invalidate_session(self, session):
468 try:
469 self.session_store.remove(session)
470 except ValueError:
471 pass
Brad Bishop2f428582015-12-02 10:56:11 -0500472
Brad Bishop87b63c12016-03-18 14:47:51 -0400473 def new_session(self):
474 sid = os.urandom(32)
475 if self.MAX_SESSIONS <= len(self.session_store):
476 self.session_store.pop()
477 self.session_store.insert(0, {'sid': sid})
Brad Bishop2f428582015-12-02 10:56:11 -0500478
Brad Bishop87b63c12016-03-18 14:47:51 -0400479 return self.session_store[0]
Brad Bishop2f428582015-12-02 10:56:11 -0500480
Brad Bishop87b63c12016-03-18 14:47:51 -0400481 def get_session(self, sid):
482 sids = [x['sid'] for x in self.session_store]
483 try:
484 return self.session_store[sids.index(sid)]
485 except ValueError:
486 return None
Brad Bishop2f428582015-12-02 10:56:11 -0500487
Brad Bishop87b63c12016-03-18 14:47:51 -0400488 def get_session_from_cookie(self):
489 return self.get_session(
490 request.get_cookie(
491 'sid', secret=self.hmac_key))
Brad Bishop2f428582015-12-02 10:56:11 -0500492
Brad Bishop87b63c12016-03-18 14:47:51 -0400493 def do_post(self, **kw):
494 if request.path == '/login':
495 return self.do_login(**kw)
496 else:
497 return self.do_logout(**kw)
Brad Bishop2f428582015-12-02 10:56:11 -0500498
Brad Bishop87b63c12016-03-18 14:47:51 -0400499 def do_logout(self, **kw):
500 session = self.get_session_from_cookie()
501 if session is not None:
502 user = session['user']
503 self.invalidate_session(session)
504 response.delete_cookie('sid')
505 return self.login_str % (user, 'out')
Brad Bishop2f428582015-12-02 10:56:11 -0500506
Brad Bishop87b63c12016-03-18 14:47:51 -0400507 return self.no_user_str
Brad Bishop2f428582015-12-02 10:56:11 -0500508
Brad Bishop87b63c12016-03-18 14:47:51 -0400509 def do_login(self, **kw):
510 session = self.get_session_from_cookie()
511 if session is not None:
512 return self.login_str % (session['user'], 'in')
Brad Bishop2f428582015-12-02 10:56:11 -0500513
Brad Bishop87b63c12016-03-18 14:47:51 -0400514 if len(request.parameter_list) != 2:
515 abort(400, self.bad_json_str % (request.json))
Brad Bishop2f428582015-12-02 10:56:11 -0500516
Brad Bishop87b63c12016-03-18 14:47:51 -0400517 if not self.authenticate(*request.parameter_list):
Brad Bishopdc3fbfa2016-09-08 09:51:38 -0400518 abort(401, self.bad_passwd_str)
Brad Bishop2f428582015-12-02 10:56:11 -0500519
Brad Bishop87b63c12016-03-18 14:47:51 -0400520 user = request.parameter_list[0]
521 session = self.new_session()
522 session['user'] = user
523 response.set_cookie(
524 'sid', session['sid'], secret=self.hmac_key,
525 secure=True,
526 httponly=True)
527 return self.login_str % (user, 'in')
Brad Bishop2f428582015-12-02 10:56:11 -0500528
Brad Bishop87b63c12016-03-18 14:47:51 -0400529 def find(self, **kw):
530 pass
Brad Bishop2f428582015-12-02 10:56:11 -0500531
Brad Bishop87b63c12016-03-18 14:47:51 -0400532 def setup(self, **kw):
533 pass
534
Brad Bishop2f428582015-12-02 10:56:11 -0500535
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500536class ImageUploadUtils:
537 ''' Provides common utils for image upload. '''
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500538
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500539 file_loc = '/tmp/images'
540 file_prefix = 'img'
541 file_suffix = ''
542
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500543 @classmethod
544 def do_upload(cls, filename=''):
545 if not os.path.exists(cls.file_loc):
546 os.makedirs(cls.file_loc)
547 if not filename:
548 handle, filename = tempfile.mkstemp(cls.file_suffix,
549 cls.file_prefix, cls.file_loc)
550 os.close(handle)
551 else:
552 filename = os.path.join(cls.file_loc, filename)
553
554 with open(filename, "w") as fd:
555 fd.write(request.body.read())
556
557
558class ImagePostHandler(RouteHandler):
559 ''' Handles the /upload/image route. '''
560
561 verbs = ['POST']
562 rules = ['/upload/image']
563 content_type = 'application/octet-stream'
564
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500565 def __init__(self, app, bus):
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500566 super(ImagePostHandler, self).__init__(
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500567 app, bus, self.verbs, self.rules, self.content_type)
568
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500569 def do_post(self, filename=''):
570 ImageUploadUtils.do_upload()
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500571
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500572 def find(self, **kw):
573 pass
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500574
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500575 def setup(self, **kw):
576 pass
577
578
579class ImagePutHandler(RouteHandler):
580 ''' Handles the /upload/image/<filename> route. '''
581
582 verbs = ['PUT']
583 rules = ['/upload/image/<filename>']
584 content_type = 'application/octet-stream'
585
586 def __init__(self, app, bus):
587 super(ImagePutHandler, self).__init__(
588 app, bus, self.verbs, self.rules, self.content_type)
589
590 def do_put(self, filename=''):
591 ImageUploadUtils.do_upload(filename)
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500592
593 def find(self, **kw):
594 pass
595
596 def setup(self, **kw):
597 pass
598
599
Brad Bishop2f428582015-12-02 10:56:11 -0500600class AuthorizationPlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -0400601 ''' Invokes an optional list of authorization callbacks. '''
Brad Bishop2f428582015-12-02 10:56:11 -0500602
Brad Bishop87b63c12016-03-18 14:47:51 -0400603 name = 'authorization'
604 api = 2
Brad Bishop2f428582015-12-02 10:56:11 -0500605
Brad Bishop87b63c12016-03-18 14:47:51 -0400606 class Compose:
607 def __init__(self, validators, callback, session_mgr):
608 self.validators = validators
609 self.callback = callback
610 self.session_mgr = session_mgr
Brad Bishop2f428582015-12-02 10:56:11 -0500611
Brad Bishop87b63c12016-03-18 14:47:51 -0400612 def __call__(self, *a, **kw):
613 sid = request.get_cookie('sid', secret=self.session_mgr.hmac_key)
614 session = self.session_mgr.get_session(sid)
Brad Bishopd4c1c552017-02-21 00:07:28 -0500615 if request.method != 'OPTIONS':
616 for x in self.validators:
617 x(session, *a, **kw)
Brad Bishop2f428582015-12-02 10:56:11 -0500618
Brad Bishop87b63c12016-03-18 14:47:51 -0400619 return self.callback(*a, **kw)
Brad Bishop2f428582015-12-02 10:56:11 -0500620
Brad Bishop87b63c12016-03-18 14:47:51 -0400621 def apply(self, callback, route):
622 undecorated = route.get_undecorated_callback()
623 if not isinstance(undecorated, RouteHandler):
624 return callback
Brad Bishop2f428582015-12-02 10:56:11 -0500625
Brad Bishop87b63c12016-03-18 14:47:51 -0400626 auth_types = getattr(
627 undecorated, '_require_auth', None)
628 if not auth_types:
629 return callback
Brad Bishop2f428582015-12-02 10:56:11 -0500630
Brad Bishop87b63c12016-03-18 14:47:51 -0400631 return self.Compose(
632 auth_types, callback, undecorated.app.session_handler)
633
Brad Bishop2f428582015-12-02 10:56:11 -0500634
Brad Bishopd0c404a2017-02-21 09:23:25 -0500635class CorsPlugin(object):
636 ''' Add CORS headers. '''
637
638 name = 'cors'
639 api = 2
640
641 @staticmethod
642 def process_origin():
643 origin = request.headers.get('Origin')
644 if origin:
645 response.add_header('Access-Control-Allow-Origin', origin)
646 response.add_header(
647 'Access-Control-Allow-Credentials', 'true')
648
649 @staticmethod
650 def process_method_and_headers(verbs):
651 method = request.headers.get('Access-Control-Request-Method')
652 headers = request.headers.get('Access-Control-Request-Headers')
653 if headers:
654 headers = [x.lower() for x in headers.split(',')]
655
656 if method in verbs \
657 and headers == ['content-type']:
658 response.add_header('Access-Control-Allow-Methods', method)
659 response.add_header(
660 'Access-Control-Allow-Headers', 'Content-Type')
661
662 def __init__(self, app):
663 app.install_error_callback(self.error_callback)
664
665 def apply(self, callback, route):
666 undecorated = route.get_undecorated_callback()
667 if not isinstance(undecorated, RouteHandler):
668 return callback
669
670 if not getattr(undecorated, '_enable_cors', None):
671 return callback
672
673 def wrap(*a, **kw):
674 self.process_origin()
675 self.process_method_and_headers(undecorated._verbs)
676 return callback(*a, **kw)
677
678 return wrap
679
680 def error_callback(self, **kw):
681 self.process_origin()
682
683
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500684class JsonApiRequestPlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -0400685 ''' Ensures request content satisfies the OpenBMC json api format. '''
686 name = 'json_api_request'
687 api = 2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500688
Brad Bishop87b63c12016-03-18 14:47:51 -0400689 error_str = "Expecting request format { 'data': <value> }, got '%s'"
690 type_error_str = "Unsupported Content-Type: '%s'"
691 json_type = "application/json"
692 request_methods = ['PUT', 'POST', 'PATCH']
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500693
Brad Bishop87b63c12016-03-18 14:47:51 -0400694 @staticmethod
695 def content_expected():
696 return request.method in JsonApiRequestPlugin.request_methods
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500697
Brad Bishop87b63c12016-03-18 14:47:51 -0400698 def validate_request(self):
699 if request.content_length > 0 and \
700 request.content_type != self.json_type:
701 abort(415, self.type_error_str % request.content_type)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500702
Brad Bishop87b63c12016-03-18 14:47:51 -0400703 try:
704 request.parameter_list = request.json.get('data')
705 except ValueError, e:
706 abort(400, str(e))
707 except (AttributeError, KeyError, TypeError):
708 abort(400, self.error_str % request.json)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500709
Brad Bishop87b63c12016-03-18 14:47:51 -0400710 def apply(self, callback, route):
Deepak Kodihallifb6cd482017-04-10 07:27:09 -0500711 content_type = getattr(
712 route.get_undecorated_callback(), '_content_type', None)
713 if self.json_type != content_type:
714 return callback
715
Brad Bishop87b63c12016-03-18 14:47:51 -0400716 verbs = getattr(
717 route.get_undecorated_callback(), '_verbs', None)
718 if verbs is None:
719 return callback
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500720
Brad Bishop87b63c12016-03-18 14:47:51 -0400721 if not set(self.request_methods).intersection(verbs):
722 return callback
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500723
Brad Bishop87b63c12016-03-18 14:47:51 -0400724 def wrap(*a, **kw):
725 if self.content_expected():
726 self.validate_request()
727 return callback(*a, **kw)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500728
Brad Bishop87b63c12016-03-18 14:47:51 -0400729 return wrap
730
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500731
732class JsonApiRequestTypePlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -0400733 ''' Ensures request content type satisfies the OpenBMC json api format. '''
734 name = 'json_api_method_request'
735 api = 2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500736
Brad Bishop87b63c12016-03-18 14:47:51 -0400737 error_str = "Expecting request format { 'data': %s }, got '%s'"
Deepak Kodihallifb6cd482017-04-10 07:27:09 -0500738 json_type = "application/json"
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500739
Brad Bishop87b63c12016-03-18 14:47:51 -0400740 def apply(self, callback, route):
Deepak Kodihallifb6cd482017-04-10 07:27:09 -0500741 content_type = getattr(
742 route.get_undecorated_callback(), '_content_type', None)
743 if self.json_type != content_type:
744 return callback
745
Brad Bishop87b63c12016-03-18 14:47:51 -0400746 request_type = getattr(
747 route.get_undecorated_callback(), 'request_type', None)
748 if request_type is None:
749 return callback
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500750
Brad Bishop87b63c12016-03-18 14:47:51 -0400751 def validate_request():
752 if not isinstance(request.parameter_list, request_type):
753 abort(400, self.error_str % (str(request_type), request.json))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500754
Brad Bishop87b63c12016-03-18 14:47:51 -0400755 def wrap(*a, **kw):
756 if JsonApiRequestPlugin.content_expected():
757 validate_request()
758 return callback(*a, **kw)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500759
Brad Bishop87b63c12016-03-18 14:47:51 -0400760 return wrap
761
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500762
Brad Bishop080a48e2017-02-21 22:34:43 -0500763class JsonErrorsPlugin(JSONPlugin):
764 ''' Extend the Bottle JSONPlugin such that it also encodes error
765 responses. '''
766
767 def __init__(self, app, **kw):
768 super(JsonErrorsPlugin, self).__init__(**kw)
769 self.json_opts = {
770 x: y for x, y in kw.iteritems()
771 if x in ['indent', 'sort_keys']}
772 app.install_error_callback(self.error_callback)
773
774 def error_callback(self, response_object, response_body, **kw):
775 response_body['body'] = json.dumps(response_object, **self.json_opts)
776 response.content_type = 'application/json'
777
778
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500779class JsonApiResponsePlugin(object):
Brad Bishop080a48e2017-02-21 22:34:43 -0500780 ''' Emits responses in the OpenBMC json api format. '''
Brad Bishop87b63c12016-03-18 14:47:51 -0400781 name = 'json_api_response'
782 api = 2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500783
Brad Bishopd4c1c552017-02-21 00:07:28 -0500784 @staticmethod
785 def has_body():
786 return request.method not in ['OPTIONS']
787
Brad Bishop080a48e2017-02-21 22:34:43 -0500788 def __init__(self, app):
789 app.install_error_callback(self.error_callback)
790
Brad Bishop87b63c12016-03-18 14:47:51 -0400791 def apply(self, callback, route):
792 def wrap(*a, **kw):
Brad Bishopd4c1c552017-02-21 00:07:28 -0500793 data = callback(*a, **kw)
794 if self.has_body():
795 resp = {'data': data}
796 resp['status'] = 'ok'
797 resp['message'] = response.status_line
798 return resp
Brad Bishop87b63c12016-03-18 14:47:51 -0400799 return wrap
800
Brad Bishop080a48e2017-02-21 22:34:43 -0500801 def error_callback(self, error, response_object, **kw):
Brad Bishop87b63c12016-03-18 14:47:51 -0400802 response_object['message'] = error.status_line
Brad Bishop9c2531e2017-03-07 10:22:40 -0500803 response_object['status'] = 'error'
Brad Bishop080a48e2017-02-21 22:34:43 -0500804 response_object.setdefault('data', {})['description'] = str(error.body)
Brad Bishop87b63c12016-03-18 14:47:51 -0400805 if error.status_code == 500:
806 response_object['data']['exception'] = repr(error.exception)
807 response_object['data']['traceback'] = error.traceback.splitlines()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500808
Brad Bishop87b63c12016-03-18 14:47:51 -0400809
Brad Bishop080a48e2017-02-21 22:34:43 -0500810class JsonpPlugin(object):
Brad Bishop80fe37a2016-03-29 10:54:54 -0400811 ''' Json javascript wrapper. '''
812 name = 'jsonp'
813 api = 2
814
Brad Bishop080a48e2017-02-21 22:34:43 -0500815 def __init__(self, app, **kw):
816 app.install_error_callback(self.error_callback)
Brad Bishop80fe37a2016-03-29 10:54:54 -0400817
818 @staticmethod
819 def to_jsonp(json):
820 jwrapper = request.query.callback or None
821 if(jwrapper):
822 response.set_header('Content-Type', 'application/javascript')
823 json = jwrapper + '(' + json + ');'
824 return json
825
826 def apply(self, callback, route):
827 def wrap(*a, **kw):
828 return self.to_jsonp(callback(*a, **kw))
829 return wrap
830
Brad Bishop080a48e2017-02-21 22:34:43 -0500831 def error_callback(self, response_body, **kw):
832 response_body['body'] = self.to_jsonp(response_body['body'])
Brad Bishop80fe37a2016-03-29 10:54:54 -0400833
834
Deepak Kodihalli461367a2017-04-10 07:11:38 -0500835class ContentCheckerPlugin(object):
836 ''' Ensures that a route is associated with the expected content-type
837 header. '''
838 name = 'content_checker'
839 api = 2
840
841 class Checker:
842 def __init__(self, type, callback):
843 self.expected_type = type
844 self.callback = callback
845 self.error_str = "Expecting content type '%s', got '%s'"
846
847 def __call__(self, *a, **kw):
Deepak Kodihallidb1a21e2017-04-27 06:30:11 -0500848 if request.method in ['PUT', 'POST', 'PATCH'] and \
849 self.expected_type and \
Deepak Kodihalli461367a2017-04-10 07:11:38 -0500850 self.expected_type != request.content_type:
851 abort(415, self.error_str % (self.expected_type,
852 request.content_type))
853
854 return self.callback(*a, **kw)
855
856 def apply(self, callback, route):
857 content_type = getattr(
858 route.get_undecorated_callback(), '_content_type', None)
859
860 return self.Checker(content_type, callback)
861
862
Brad Bishop2c6fc762016-08-29 15:53:25 -0400863class App(Bottle):
Brad Bishop2ddfa002016-08-29 15:11:55 -0400864 def __init__(self):
Brad Bishop2c6fc762016-08-29 15:53:25 -0400865 super(App, self).__init__(autojson=False)
Brad Bishop2ddfa002016-08-29 15:11:55 -0400866 self.bus = dbus.SystemBus()
867 self.mapper = obmc.mapper.Mapper(self.bus)
Brad Bishop080a48e2017-02-21 22:34:43 -0500868 self.error_callbacks = []
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500869
Brad Bishop87b63c12016-03-18 14:47:51 -0400870 self.install_hooks()
871 self.install_plugins()
872 self.create_handlers()
873 self.install_handlers()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500874
Brad Bishop87b63c12016-03-18 14:47:51 -0400875 def install_plugins(self):
876 # install json api plugins
877 json_kw = {'indent': 2, 'sort_keys': True}
Brad Bishop87b63c12016-03-18 14:47:51 -0400878 self.install(AuthorizationPlugin())
Brad Bishopd0c404a2017-02-21 09:23:25 -0500879 self.install(CorsPlugin(self))
Deepak Kodihalli461367a2017-04-10 07:11:38 -0500880 self.install(ContentCheckerPlugin())
Brad Bishop080a48e2017-02-21 22:34:43 -0500881 self.install(JsonpPlugin(self, **json_kw))
882 self.install(JsonErrorsPlugin(self, **json_kw))
883 self.install(JsonApiResponsePlugin(self))
Brad Bishop87b63c12016-03-18 14:47:51 -0400884 self.install(JsonApiRequestPlugin())
885 self.install(JsonApiRequestTypePlugin())
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500886
Brad Bishop87b63c12016-03-18 14:47:51 -0400887 def install_hooks(self):
Brad Bishop080a48e2017-02-21 22:34:43 -0500888 self.error_handler_type = type(self.default_error_handler)
889 self.original_error_handler = self.default_error_handler
890 self.default_error_handler = self.error_handler_type(
891 self.custom_error_handler, self, Bottle)
892
Brad Bishop87b63c12016-03-18 14:47:51 -0400893 self.real_router_match = self.router.match
894 self.router.match = self.custom_router_match
895 self.add_hook('before_request', self.strip_extra_slashes)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500896
Brad Bishop87b63c12016-03-18 14:47:51 -0400897 def create_handlers(self):
898 # create route handlers
899 self.session_handler = SessionHandler(self, self.bus)
900 self.directory_handler = DirectoryHandler(self, self.bus)
901 self.list_names_handler = ListNamesHandler(self, self.bus)
902 self.list_handler = ListHandler(self, self.bus)
903 self.method_handler = MethodHandler(self, self.bus)
904 self.property_handler = PropertyHandler(self, self.bus)
905 self.schema_handler = SchemaHandler(self, self.bus)
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500906 self.image_upload_post_handler = ImagePostHandler(self, self.bus)
907 self.image_upload_put_handler = ImagePutHandler(self, self.bus)
Brad Bishop87b63c12016-03-18 14:47:51 -0400908 self.instance_handler = InstanceHandler(self, self.bus)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500909
Brad Bishop87b63c12016-03-18 14:47:51 -0400910 def install_handlers(self):
911 self.session_handler.install()
912 self.directory_handler.install()
913 self.list_names_handler.install()
914 self.list_handler.install()
915 self.method_handler.install()
916 self.property_handler.install()
917 self.schema_handler.install()
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500918 self.image_upload_post_handler.install()
919 self.image_upload_put_handler.install()
Brad Bishop87b63c12016-03-18 14:47:51 -0400920 # this has to come last, since it matches everything
921 self.instance_handler.install()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500922
Brad Bishop080a48e2017-02-21 22:34:43 -0500923 def install_error_callback(self, callback):
924 self.error_callbacks.insert(0, callback)
925
Brad Bishop87b63c12016-03-18 14:47:51 -0400926 def custom_router_match(self, environ):
927 ''' The built-in Bottle algorithm for figuring out if a 404 or 405 is
928 needed doesn't work for us since the instance rules match
929 everything. This monkey-patch lets the route handler figure
930 out which response is needed. This could be accomplished
931 with a hook but that would require calling the router match
932 function twice.
933 '''
934 route, args = self.real_router_match(environ)
935 if isinstance(route.callback, RouteHandler):
936 route.callback._setup(**args)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500937
Brad Bishop87b63c12016-03-18 14:47:51 -0400938 return route, args
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500939
Brad Bishop080a48e2017-02-21 22:34:43 -0500940 def custom_error_handler(self, res, error):
941 ''' Allow plugins to modify error reponses too via this custom
942 error handler. '''
943
944 response_object = {}
945 response_body = {}
946 for x in self.error_callbacks:
947 x(error=error,
948 response_object=response_object,
949 response_body=response_body)
950
951 return response_body.get('body', "")
952
Brad Bishop87b63c12016-03-18 14:47:51 -0400953 @staticmethod
954 def strip_extra_slashes():
955 path = request.environ['PATH_INFO']
956 trailing = ("", "/")[path[-1] == '/']
957 parts = filter(bool, path.split('/'))
958 request.environ['PATH_INFO'] = '/' + '/'.join(parts) + trailing