blob: 6f800e96d2c5f324cc89a480f24c61b330ab807b [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
Jayanth Othayoth9bc94992017-06-29 06:30:40 -050023from bottle import static_file
Brad Bishopb103d2d2016-03-04 16:19:14 -050024import obmc.utils.misc
Brad Bishopb103d2d2016-03-04 16:19:14 -050025from obmc.dbuslib.introspection import IntrospectionNodeParser
26import obmc.mapper
Brad Bishop2f428582015-12-02 10:56:11 -050027import spwd
28import grp
29import crypt
Deepak Kodihalli1af301a2017-04-11 07:29:01 -050030import tempfile
Leonel Gonzalez0bdef952017-04-18 08:17:49 -050031import re
Matt Spinlerd41643e2018-02-02 13:51:38 -060032import mimetypes
Deepak Kodihalli639b5022017-10-13 06:40:26 -050033have_wsock = True
34try:
35 from geventwebsocket import WebSocketError
36except ImportError:
37 have_wsock = False
38if have_wsock:
39 from dbus.mainloop.glib import DBusGMainLoop
40 DBusGMainLoop(set_as_default=True)
CamVan Nguyen249d1322018-03-05 10:08:33 -060041 # TODO: openbmc/openbmc#2994 remove python 2 support
42 try: # python 2
43 import gobject
44 except ImportError: # python 3
45 from gi.repository import GObject as gobject
Deepak Kodihalli639b5022017-10-13 06:40:26 -050046 import gevent
Brad Bishopaa65f6e2015-10-27 16:28:51 -040047
Adriana Kobylakf92cf4d2017-12-13 11:46:50 -060048DBUS_UNKNOWN_INTERFACE = 'org.freedesktop.DBus.Error.UnknownInterface'
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050049DBUS_UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod'
50DBUS_INVALID_ARGS = 'org.freedesktop.DBus.Error.InvalidArgs'
Brad Bishopd4578922015-12-02 11:10:36 -050051DBUS_TYPE_ERROR = 'org.freedesktop.DBus.Python.TypeError'
Deepak Kodihalli6075bb42017-04-04 05:49:17 -050052DELETE_IFACE = 'xyz.openbmc_project.Object.Delete'
Brad Bishop9ee57c42015-11-03 14:59:29 -050053
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050054_4034_msg = "The specified %s cannot be %s: '%s'"
Brad Bishopaa65f6e2015-10-27 16:28:51 -040055
Matt Spinlerd41643e2018-02-02 13:51:38 -060056www_base_path = '/usr/share/www/'
57
Brad Bishop87b63c12016-03-18 14:47:51 -040058
Brad Bishop2f428582015-12-02 10:56:11 -050059def valid_user(session, *a, **kw):
Brad Bishop87b63c12016-03-18 14:47:51 -040060 ''' Authorization plugin callback that checks
61 that the user is logged in. '''
62 if session is None:
Brad Bishopdc3fbfa2016-09-08 09:51:38 -040063 abort(401, 'Login required')
Brad Bishop87b63c12016-03-18 14:47:51 -040064
Brad Bishop2f428582015-12-02 10:56:11 -050065
Leonel Gonzalez0bdef952017-04-18 08:17:49 -050066def get_type_signature_by_introspection(bus, service, object_path,
67 property_name):
68 obj = bus.get_object(service, object_path)
69 iface = dbus.Interface(obj, 'org.freedesktop.DBus.Introspectable')
70 xml_string = iface.Introspect()
71 for child in ElementTree.fromstring(xml_string):
72 # Iterate over each interfaces's properties to find
73 # matching property_name, and return its signature string
74 if child.tag == 'interface':
75 for i in child.iter():
76 if ('name' in i.attrib) and \
77 (i.attrib['name'] == property_name):
78 type_signature = i.attrib['type']
79 return type_signature
80
81
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +053082def get_method_signature(bus, service, object_path, interface, method):
83 obj = bus.get_object(service, object_path)
84 iface = dbus.Interface(obj, 'org.freedesktop.DBus.Introspectable')
85 xml_string = iface.Introspect()
86 arglist = []
87
88 root = ElementTree.fromstring(xml_string)
89 for dbus_intf in root.findall('interface'):
90 if (dbus_intf.get('name') == interface):
91 for dbus_method in dbus_intf.findall('method'):
92 if(dbus_method.get('name') == method):
93 for arg in dbus_method.findall('arg'):
94 arglist.append(arg.get('type'))
95 return arglist
96
97
Leonel Gonzalez0bdef952017-04-18 08:17:49 -050098def split_struct_signature(signature):
99 struct_regex = r'(b|y|n|i|x|q|u|t|d|s|a\(.+?\)|\(.+?\))|a\{.+?\}+?'
100 struct_matches = re.findall(struct_regex, signature)
101 return struct_matches
102
103
104def convert_type(signature, value):
105 # Basic Types
106 converted_value = None
107 converted_container = None
CamVan Nguyen249d1322018-03-05 10:08:33 -0600108 # TODO: openbmc/openbmc#2994 remove python 2 support
109 try: # python 2
110 basic_types = {'b': bool, 'y': dbus.Byte, 'n': dbus.Int16, 'i': int,
111 'x': long, 'q': dbus.UInt16, 'u': dbus.UInt32,
112 't': dbus.UInt64, 'd': float, 's': str}
113 except NameError: # python 3
114 basic_types = {'b': bool, 'y': dbus.Byte, 'n': dbus.Int16, 'i': int,
115 'x': int, 'q': dbus.UInt16, 'u': dbus.UInt32,
116 't': dbus.UInt64, 'd': float, 's': str}
Leonel Gonzalez0bdef952017-04-18 08:17:49 -0500117 array_matches = re.match(r'a\((\S+)\)', signature)
118 struct_matches = re.match(r'\((\S+)\)', signature)
119 dictionary_matches = re.match(r'a{(\S+)}', signature)
120 if signature in basic_types:
121 converted_value = basic_types[signature](value)
122 return converted_value
123 # Array
124 if array_matches:
125 element_type = array_matches.group(1)
126 converted_container = list()
127 # Test if value is a list
128 # to avoid iterating over each character in a string.
129 # Iterate over each item and convert type
130 if isinstance(value, list):
131 for i in value:
132 converted_element = convert_type(element_type, i)
133 converted_container.append(converted_element)
134 # Convert non-sequence to expected type, and append to list
135 else:
136 converted_element = convert_type(element_type, value)
137 converted_container.append(converted_element)
138 return converted_container
139 # Struct
140 if struct_matches:
141 element_types = struct_matches.group(1)
142 split_element_types = split_struct_signature(element_types)
143 converted_container = list()
144 # Test if value is a list
145 if isinstance(value, list):
146 for index, val in enumerate(value):
147 converted_element = convert_type(split_element_types[index],
148 value[index])
149 converted_container.append(converted_element)
150 else:
151 converted_element = convert_type(element_types, value)
152 converted_container.append(converted_element)
153 return tuple(converted_container)
154 # Dictionary
155 if dictionary_matches:
156 element_types = dictionary_matches.group(1)
157 split_element_types = split_struct_signature(element_types)
158 converted_container = dict()
159 # Convert each element of dict
CamVan Nguyen249d1322018-03-05 10:08:33 -0600160 for key, val in value.items():
Leonel Gonzalez0bdef952017-04-18 08:17:49 -0500161 converted_key = convert_type(split_element_types[0], key)
162 converted_val = convert_type(split_element_types[1], val)
163 converted_container[converted_key] = converted_val
164 return converted_container
165
166
Brad Bishop2f428582015-12-02 10:56:11 -0500167class UserInGroup:
Brad Bishop87b63c12016-03-18 14:47:51 -0400168 ''' Authorization plugin callback that checks that the user is logged in
169 and a member of a group. '''
170 def __init__(self, group):
171 self.group = group
Brad Bishop2f428582015-12-02 10:56:11 -0500172
Brad Bishop87b63c12016-03-18 14:47:51 -0400173 def __call__(self, session, *a, **kw):
174 valid_user(session, *a, **kw)
175 res = False
Brad Bishop2f428582015-12-02 10:56:11 -0500176
Brad Bishop87b63c12016-03-18 14:47:51 -0400177 try:
178 res = session['user'] in grp.getgrnam(self.group)[3]
179 except KeyError:
180 pass
Brad Bishop2f428582015-12-02 10:56:11 -0500181
Brad Bishop87b63c12016-03-18 14:47:51 -0400182 if not res:
183 abort(403, 'Insufficient access')
184
Brad Bishop2f428582015-12-02 10:56:11 -0500185
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500186class RouteHandler(object):
Brad Bishop6d190602016-04-15 13:09:39 -0400187 _require_auth = obmc.utils.misc.makelist(valid_user)
Brad Bishopd0c404a2017-02-21 09:23:25 -0500188 _enable_cors = True
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400189
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500190 def __init__(self, app, bus, verbs, rules, content_type=''):
Brad Bishop87b63c12016-03-18 14:47:51 -0400191 self.app = app
192 self.bus = bus
Brad Bishopb103d2d2016-03-04 16:19:14 -0500193 self.mapper = obmc.mapper.Mapper(bus)
Brad Bishop6d190602016-04-15 13:09:39 -0400194 self._verbs = obmc.utils.misc.makelist(verbs)
Brad Bishop87b63c12016-03-18 14:47:51 -0400195 self._rules = rules
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500196 self._content_type = content_type
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400197
Brad Bishop88c76a42017-02-21 00:02:02 -0500198 if 'GET' in self._verbs:
199 self._verbs = list(set(self._verbs + ['HEAD']))
Brad Bishopd4c1c552017-02-21 00:07:28 -0500200 if 'OPTIONS' not in self._verbs:
201 self._verbs.append('OPTIONS')
Brad Bishop88c76a42017-02-21 00:02:02 -0500202
Brad Bishop87b63c12016-03-18 14:47:51 -0400203 def _setup(self, **kw):
204 request.route_data = {}
Brad Bishopd4c1c552017-02-21 00:07:28 -0500205
Brad Bishop87b63c12016-03-18 14:47:51 -0400206 if request.method in self._verbs:
Brad Bishopd4c1c552017-02-21 00:07:28 -0500207 if request.method != 'OPTIONS':
208 return self.setup(**kw)
Brad Bishop88c76a42017-02-21 00:02:02 -0500209
Brad Bishopd4c1c552017-02-21 00:07:28 -0500210 # Javascript implementations will not send credentials
211 # with an OPTIONS request. Don't help malicious clients
212 # by checking the path here and returning a 404 if the
213 # path doesn't exist.
214 return None
Brad Bishop88c76a42017-02-21 00:02:02 -0500215
Brad Bishopd4c1c552017-02-21 00:07:28 -0500216 # Return 405
Brad Bishop88c76a42017-02-21 00:02:02 -0500217 raise HTTPError(
218 405, "Method not allowed.", Allow=','.join(self._verbs))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400219
Brad Bishop87b63c12016-03-18 14:47:51 -0400220 def __call__(self, **kw):
221 return getattr(self, 'do_' + request.method.lower())(**kw)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400222
Brad Bishop88c76a42017-02-21 00:02:02 -0500223 def do_head(self, **kw):
224 return self.do_get(**kw)
225
Brad Bishopd4c1c552017-02-21 00:07:28 -0500226 def do_options(self, **kw):
227 for v in self._verbs:
228 response.set_header(
229 'Allow',
230 ','.join(self._verbs))
231 return None
232
Brad Bishop87b63c12016-03-18 14:47:51 -0400233 def install(self):
234 self.app.route(
235 self._rules, callback=self,
Brad Bishopd4c1c552017-02-21 00:07:28 -0500236 method=['OPTIONS', 'GET', 'PUT', 'PATCH', 'POST', 'DELETE'])
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400237
Brad Bishop87b63c12016-03-18 14:47:51 -0400238 @staticmethod
239 def try_mapper_call(f, callback=None, **kw):
240 try:
241 return f(**kw)
CamVan Nguyen249d1322018-03-05 10:08:33 -0600242 except dbus.exceptions.DBusException as e:
Brad Bishopfce77562016-11-28 15:44:18 -0500243 if e.get_dbus_name() == \
244 'org.freedesktop.DBus.Error.ObjectPathInUse':
245 abort(503, str(e))
Brad Bishopb103d2d2016-03-04 16:19:14 -0500246 if e.get_dbus_name() != obmc.mapper.MAPPER_NOT_FOUND:
Brad Bishop87b63c12016-03-18 14:47:51 -0400247 raise
248 if callback is None:
249 def callback(e, **kw):
250 abort(404, str(e))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400251
Brad Bishop87b63c12016-03-18 14:47:51 -0400252 callback(e, **kw)
253
254 @staticmethod
255 def try_properties_interface(f, *a):
256 try:
257 return f(*a)
CamVan Nguyen249d1322018-03-05 10:08:33 -0600258 except dbus.exceptions.DBusException as e:
Adriana Kobylakf92cf4d2017-12-13 11:46:50 -0600259 if DBUS_UNKNOWN_INTERFACE in e.get_dbus_name():
Brad Bishopf4e74982016-04-01 14:53:05 -0400260 # interface doesn't have any properties
261 return None
Brad Bishop87b63c12016-03-18 14:47:51 -0400262 if DBUS_UNKNOWN_METHOD == e.get_dbus_name():
263 # properties interface not implemented at all
264 return None
265 raise
266
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400267
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500268class DirectoryHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400269 verbs = 'GET'
270 rules = '<path:path>/'
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400271
Brad Bishop87b63c12016-03-18 14:47:51 -0400272 def __init__(self, app, bus):
273 super(DirectoryHandler, self).__init__(
Brad Bishopc431e1a2017-07-10 16:44:51 -0400274 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400275
Brad Bishop87b63c12016-03-18 14:47:51 -0400276 def find(self, path='/'):
277 return self.try_mapper_call(
278 self.mapper.get_subtree_paths, path=path, depth=1)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400279
Brad Bishop87b63c12016-03-18 14:47:51 -0400280 def setup(self, path='/'):
281 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400282
Brad Bishop87b63c12016-03-18 14:47:51 -0400283 def do_get(self, path='/'):
284 return request.route_data['map']
285
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400286
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500287class ListNamesHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400288 verbs = 'GET'
289 rules = ['/list', '<path:path>/list']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400290
Brad Bishop87b63c12016-03-18 14:47:51 -0400291 def __init__(self, app, bus):
292 super(ListNamesHandler, self).__init__(
Brad Bishopc431e1a2017-07-10 16:44:51 -0400293 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400294
Brad Bishop87b63c12016-03-18 14:47:51 -0400295 def find(self, path='/'):
CamVan Nguyen249d1322018-03-05 10:08:33 -0600296 return list(self.try_mapper_call(
297 self.mapper.get_subtree, path=path).keys())
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400298
Brad Bishop87b63c12016-03-18 14:47:51 -0400299 def setup(self, path='/'):
300 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400301
Brad Bishop87b63c12016-03-18 14:47:51 -0400302 def do_get(self, path='/'):
303 return request.route_data['map']
304
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400305
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500306class ListHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400307 verbs = 'GET'
308 rules = ['/enumerate', '<path:path>/enumerate']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400309
Brad Bishop87b63c12016-03-18 14:47:51 -0400310 def __init__(self, app, bus):
311 super(ListHandler, self).__init__(
Brad Bishopc431e1a2017-07-10 16:44:51 -0400312 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400313
Brad Bishop87b63c12016-03-18 14:47:51 -0400314 def find(self, path='/'):
315 return self.try_mapper_call(
316 self.mapper.get_subtree, path=path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400317
Brad Bishop87b63c12016-03-18 14:47:51 -0400318 def setup(self, path='/'):
319 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400320
Brad Bishop87b63c12016-03-18 14:47:51 -0400321 def do_get(self, path='/'):
Brad Bishop71527b42016-04-01 14:51:14 -0400322 return {x: y for x, y in self.mapper.enumerate_subtree(
323 path,
324 mapper_data=request.route_data['map']).dataitems()}
Brad Bishop87b63c12016-03-18 14:47:51 -0400325
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400326
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500327class MethodHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400328 verbs = 'POST'
329 rules = '<path:path>/action/<method>'
330 request_type = list
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500331 content_type = 'application/json'
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400332
Brad Bishop87b63c12016-03-18 14:47:51 -0400333 def __init__(self, app, bus):
334 super(MethodHandler, self).__init__(
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500335 app, bus, self.verbs, self.rules, self.content_type)
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530336 self.service = ''
337 self.interface = ''
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400338
Brad Bishop87b63c12016-03-18 14:47:51 -0400339 def find(self, path, method):
Saqib Khan3a00b1f2017-11-04 15:56:21 -0500340 method_list = []
Brad Bishop87b63c12016-03-18 14:47:51 -0400341 busses = self.try_mapper_call(
342 self.mapper.get_object, path=path)
CamVan Nguyen249d1322018-03-05 10:08:33 -0600343 for items in busses.items():
Brad Bishop87b63c12016-03-18 14:47:51 -0400344 m = self.find_method_on_bus(path, method, *items)
345 if m:
Saqib Khan3a00b1f2017-11-04 15:56:21 -0500346 method_list.append(m)
Nagaraju Goruganti765c2c82017-11-13 06:17:13 -0600347 if method_list:
348 return method_list
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400349
Brad Bishop87b63c12016-03-18 14:47:51 -0400350 abort(404, _4034_msg % ('method', 'found', method))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400351
Brad Bishop87b63c12016-03-18 14:47:51 -0400352 def setup(self, path, method):
Saqib Khan3a00b1f2017-11-04 15:56:21 -0500353 request.route_data['map'] = self.find(path, method)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400354
Marri Devender Raobc0c6732017-11-20 00:15:47 -0600355 def do_post(self, path, method, retry=True):
Brad Bishop87b63c12016-03-18 14:47:51 -0400356 try:
Nagaraju Goruganti765c2c82017-11-13 06:17:13 -0600357 args = []
358 if request.parameter_list:
359 args = request.parameter_list
360 # To see if the return type is capable of being merged
361 if len(request.route_data['map']) > 1:
362 results = None
363 for item in request.route_data['map']:
364 tmp = item(*args)
365 if not results:
366 if tmp is not None:
367 results = type(tmp)()
368 if isinstance(results, dict):
369 results = results.update(tmp)
370 elif isinstance(results, list):
371 results = results + tmp
372 elif isinstance(results, type(None)):
373 results = None
374 else:
375 abort(501, 'Don\'t know how to merge method call '
376 'results of {}'.format(type(tmp)))
377 return results
378 # There is only one method
379 return request.route_data['map'][0](*args)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400380
CamVan Nguyen249d1322018-03-05 10:08:33 -0600381 except dbus.exceptions.DBusException as e:
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530382 paramlist = []
Brad Bishopb7fca9b2018-01-23 12:16:50 -0500383 if e.get_dbus_name() == DBUS_INVALID_ARGS and retry:
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530384
385 signature_list = get_method_signature(self.bus, self.service,
386 path, self.interface,
387 method)
388 if not signature_list:
389 abort(400, "Failed to get method signature: %s" % str(e))
390 if len(signature_list) != len(request.parameter_list):
391 abort(400, "Invalid number of args")
392 converted_value = None
393 try:
394 for index, expected_type in enumerate(signature_list):
395 value = request.parameter_list[index]
396 converted_value = convert_type(expected_type, value)
397 paramlist.append(converted_value)
398 request.parameter_list = paramlist
Marri Devender Raobc0c6732017-11-20 00:15:47 -0600399 self.do_post(path, method, False)
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530400 return
401 except Exception as ex:
Nagaraju Gorugantiab404fa2017-12-14 10:24:40 -0600402 abort(400, "Bad Request/Invalid Args given")
Brad Bishop87b63c12016-03-18 14:47:51 -0400403 abort(400, str(e))
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530404
Brad Bishop87b63c12016-03-18 14:47:51 -0400405 if e.get_dbus_name() == DBUS_TYPE_ERROR:
406 abort(400, str(e))
407 raise
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400408
Brad Bishop87b63c12016-03-18 14:47:51 -0400409 @staticmethod
410 def find_method_in_interface(method, obj, interface, methods):
411 if methods is None:
412 return None
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400413
CamVan Nguyen249d1322018-03-05 10:08:33 -0600414 method = obmc.utils.misc.find_case_insensitive(method, list(methods.keys()))
Brad Bishop87b63c12016-03-18 14:47:51 -0400415 if method is not None:
416 iface = dbus.Interface(obj, interface)
417 return iface.get_dbus_method(method)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400418
Brad Bishop87b63c12016-03-18 14:47:51 -0400419 def find_method_on_bus(self, path, method, bus, interfaces):
420 obj = self.bus.get_object(bus, path, introspect=False)
421 iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
422 data = iface.Introspect()
423 parser = IntrospectionNodeParser(
424 ElementTree.fromstring(data),
Brad Bishopaeb995d2018-04-04 22:28:42 -0400425 intf_match=lambda x: x in interfaces)
CamVan Nguyen249d1322018-03-05 10:08:33 -0600426 for x, y in parser.get_interfaces().items():
Brad Bishop87b63c12016-03-18 14:47:51 -0400427 m = self.find_method_in_interface(
428 method, obj, x, y.get('method'))
429 if m:
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530430 self.service = bus
431 self.interface = x
Brad Bishop87b63c12016-03-18 14:47:51 -0400432 return m
433
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400434
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500435class PropertyHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400436 verbs = ['PUT', 'GET']
437 rules = '<path:path>/attr/<prop>'
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500438 content_type = 'application/json'
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400439
Brad Bishop87b63c12016-03-18 14:47:51 -0400440 def __init__(self, app, bus):
441 super(PropertyHandler, self).__init__(
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500442 app, bus, self.verbs, self.rules, self.content_type)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400443
Brad Bishop87b63c12016-03-18 14:47:51 -0400444 def find(self, path, prop):
445 self.app.instance_handler.setup(path)
446 obj = self.app.instance_handler.do_get(path)
Brad Bishop56ad87f2017-02-21 23:33:29 -0500447 real_name = obmc.utils.misc.find_case_insensitive(
CamVan Nguyen249d1322018-03-05 10:08:33 -0600448 prop, list(obj.keys()))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400449
Brad Bishop56ad87f2017-02-21 23:33:29 -0500450 if not real_name:
451 if request.method == 'PUT':
452 abort(403, _4034_msg % ('property', 'created', prop))
453 else:
454 abort(404, _4034_msg % ('property', 'found', prop))
455 return real_name, {path: obj}
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500456
Brad Bishop87b63c12016-03-18 14:47:51 -0400457 def setup(self, path, prop):
Brad Bishop56ad87f2017-02-21 23:33:29 -0500458 name, obj = self.find(path, prop)
459 request.route_data['obj'] = obj
460 request.route_data['name'] = name
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500461
Brad Bishop87b63c12016-03-18 14:47:51 -0400462 def do_get(self, path, prop):
Brad Bishop56ad87f2017-02-21 23:33:29 -0500463 name = request.route_data['name']
464 return request.route_data['obj'][path][name]
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500465
Marri Devender Raobc0c6732017-11-20 00:15:47 -0600466 def do_put(self, path, prop, value=None, retry=True):
Brad Bishop87b63c12016-03-18 14:47:51 -0400467 if value is None:
468 value = request.parameter_list
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500469
Brad Bishop87b63c12016-03-18 14:47:51 -0400470 prop, iface, properties_iface = self.get_host_interface(
471 path, prop, request.route_data['map'][path])
472 try:
473 properties_iface.Set(iface, prop, value)
CamVan Nguyen249d1322018-03-05 10:08:33 -0600474 except ValueError as e:
Brad Bishop87b63c12016-03-18 14:47:51 -0400475 abort(400, str(e))
CamVan Nguyen249d1322018-03-05 10:08:33 -0600476 except dbus.exceptions.DBusException as e:
Brad Bishopb7fca9b2018-01-23 12:16:50 -0500477 if e.get_dbus_name() == DBUS_INVALID_ARGS and retry:
Leonel Gonzalez0bdef952017-04-18 08:17:49 -0500478 bus_name = properties_iface.bus_name
479 expected_type = get_type_signature_by_introspection(self.bus,
480 bus_name,
481 path,
482 prop)
483 if not expected_type:
484 abort(403, "Failed to get expected type: %s" % str(e))
485 converted_value = None
486 try:
487 converted_value = convert_type(expected_type, value)
Marri Devender Raobc0c6732017-11-20 00:15:47 -0600488 self.do_put(path, prop, converted_value, False)
Leonel Gonzalez0bdef952017-04-18 08:17:49 -0500489 return
490 except Exception as ex:
491 abort(403, "Failed to convert %s to type %s" %
492 (value, expected_type))
Brad Bishop87b63c12016-03-18 14:47:51 -0400493 abort(403, str(e))
494 raise
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500495
Brad Bishop87b63c12016-03-18 14:47:51 -0400496 def get_host_interface(self, path, prop, bus_info):
CamVan Nguyen249d1322018-03-05 10:08:33 -0600497 for bus, interfaces in bus_info.items():
Brad Bishop87b63c12016-03-18 14:47:51 -0400498 obj = self.bus.get_object(bus, path, introspect=True)
499 properties_iface = dbus.Interface(
500 obj, dbus_interface=dbus.PROPERTIES_IFACE)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500501
Brad Bishop87b63c12016-03-18 14:47:51 -0400502 info = self.get_host_interface_on_bus(
503 path, prop, properties_iface, bus, interfaces)
504 if info is not None:
505 prop, iface = info
506 return prop, iface, properties_iface
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500507
Brad Bishop87b63c12016-03-18 14:47:51 -0400508 def get_host_interface_on_bus(self, path, prop, iface, bus, interfaces):
509 for i in interfaces:
510 properties = self.try_properties_interface(iface.GetAll, i)
Brad Bishop69cb6d12017-02-21 12:01:52 -0500511 if not properties:
Brad Bishop87b63c12016-03-18 14:47:51 -0400512 continue
Leonel Gonzalez409f6712017-05-24 09:51:55 -0500513 match = obmc.utils.misc.find_case_insensitive(
CamVan Nguyen249d1322018-03-05 10:08:33 -0600514 prop, list(properties.keys()))
Leonel Gonzalez409f6712017-05-24 09:51:55 -0500515 if match is None:
Brad Bishop87b63c12016-03-18 14:47:51 -0400516 continue
Leonel Gonzalez409f6712017-05-24 09:51:55 -0500517 prop = match
Brad Bishop87b63c12016-03-18 14:47:51 -0400518 return prop, i
519
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500520
Brad Bishop2503bd62015-12-16 17:56:12 -0500521class SchemaHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400522 verbs = ['GET']
523 rules = '<path:path>/schema'
Brad Bishop2503bd62015-12-16 17:56:12 -0500524
Brad Bishop87b63c12016-03-18 14:47:51 -0400525 def __init__(self, app, bus):
526 super(SchemaHandler, self).__init__(
Brad Bishop529029b2017-07-10 16:46:01 -0400527 app, bus, self.verbs, self.rules)
Brad Bishop2503bd62015-12-16 17:56:12 -0500528
Brad Bishop87b63c12016-03-18 14:47:51 -0400529 def find(self, path):
530 return self.try_mapper_call(
531 self.mapper.get_object,
532 path=path)
Brad Bishop2503bd62015-12-16 17:56:12 -0500533
Brad Bishop87b63c12016-03-18 14:47:51 -0400534 def setup(self, path):
535 request.route_data['map'] = self.find(path)
Brad Bishop2503bd62015-12-16 17:56:12 -0500536
Brad Bishop87b63c12016-03-18 14:47:51 -0400537 def do_get(self, path):
538 schema = {}
CamVan Nguyen249d1322018-03-05 10:08:33 -0600539 for x in request.route_data['map'].keys():
Brad Bishop87b63c12016-03-18 14:47:51 -0400540 obj = self.bus.get_object(x, path, introspect=False)
541 iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
542 data = iface.Introspect()
543 parser = IntrospectionNodeParser(
544 ElementTree.fromstring(data))
CamVan Nguyen249d1322018-03-05 10:08:33 -0600545 for x, y in parser.get_interfaces().items():
Brad Bishop87b63c12016-03-18 14:47:51 -0400546 schema[x] = y
Brad Bishop2503bd62015-12-16 17:56:12 -0500547
Brad Bishop87b63c12016-03-18 14:47:51 -0400548 return schema
549
Brad Bishop2503bd62015-12-16 17:56:12 -0500550
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500551class InstanceHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400552 verbs = ['GET', 'PUT', 'DELETE']
553 rules = '<path:path>'
554 request_type = dict
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500555
Brad Bishop87b63c12016-03-18 14:47:51 -0400556 def __init__(self, app, bus):
557 super(InstanceHandler, self).__init__(
Brad Bishop529029b2017-07-10 16:46:01 -0400558 app, bus, self.verbs, self.rules)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500559
Brad Bishop87b63c12016-03-18 14:47:51 -0400560 def find(self, path, callback=None):
561 return {path: self.try_mapper_call(
562 self.mapper.get_object,
563 callback,
564 path=path)}
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500565
Brad Bishop87b63c12016-03-18 14:47:51 -0400566 def setup(self, path):
567 callback = None
568 if request.method == 'PUT':
569 def callback(e, **kw):
570 abort(403, _4034_msg % ('resource', 'created', path))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500571
Brad Bishop87b63c12016-03-18 14:47:51 -0400572 if request.route_data.get('map') is None:
573 request.route_data['map'] = self.find(path, callback)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500574
Brad Bishop87b63c12016-03-18 14:47:51 -0400575 def do_get(self, path):
Brad Bishop71527b42016-04-01 14:51:14 -0400576 return self.mapper.enumerate_object(
577 path,
578 mapper_data=request.route_data['map'])
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500579
Brad Bishop87b63c12016-03-18 14:47:51 -0400580 def do_put(self, path):
581 # make sure all properties exist in the request
582 obj = set(self.do_get(path).keys())
583 req = set(request.parameter_list.keys())
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500584
Brad Bishop87b63c12016-03-18 14:47:51 -0400585 diff = list(obj.difference(req))
586 if diff:
587 abort(403, _4034_msg % (
588 'resource', 'removed', '%s/attr/%s' % (path, diff[0])))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500589
Brad Bishop87b63c12016-03-18 14:47:51 -0400590 diff = list(req.difference(obj))
591 if diff:
592 abort(403, _4034_msg % (
593 'resource', 'created', '%s/attr/%s' % (path, diff[0])))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500594
CamVan Nguyen249d1322018-03-05 10:08:33 -0600595 for p, v in request.parameter_list.items():
Brad Bishop87b63c12016-03-18 14:47:51 -0400596 self.app.property_handler.do_put(
597 path, p, v)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500598
Brad Bishop87b63c12016-03-18 14:47:51 -0400599 def do_delete(self, path):
CamVan Nguyen249d1322018-03-05 10:08:33 -0600600 for bus_info in request.route_data['map'][path].items():
Brad Bishop87b63c12016-03-18 14:47:51 -0400601 if self.bus_missing_delete(path, *bus_info):
602 abort(403, _4034_msg % ('resource', 'removed', path))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500603
CamVan Nguyen249d1322018-03-05 10:08:33 -0600604 for bus in request.route_data['map'][path].keys():
Brad Bishop87b63c12016-03-18 14:47:51 -0400605 self.delete_on_bus(path, bus)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500606
Brad Bishop87b63c12016-03-18 14:47:51 -0400607 def bus_missing_delete(self, path, bus, interfaces):
608 return DELETE_IFACE not in interfaces
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500609
Brad Bishop87b63c12016-03-18 14:47:51 -0400610 def delete_on_bus(self, path, bus):
611 obj = self.bus.get_object(bus, path, introspect=False)
612 delete_iface = dbus.Interface(
613 obj, dbus_interface=DELETE_IFACE)
614 delete_iface.Delete()
615
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500616
Brad Bishop2f428582015-12-02 10:56:11 -0500617class SessionHandler(MethodHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400618 ''' Handles the /login and /logout routes, manages
619 server side session store and session cookies. '''
Brad Bishop2f428582015-12-02 10:56:11 -0500620
Brad Bishop87b63c12016-03-18 14:47:51 -0400621 rules = ['/login', '/logout']
622 login_str = "User '%s' logged %s"
623 bad_passwd_str = "Invalid username or password"
624 no_user_str = "No user logged in"
625 bad_json_str = "Expecting request format { 'data': " \
626 "[<username>, <password>] }, got '%s'"
627 _require_auth = None
628 MAX_SESSIONS = 16
Brad Bishop2f428582015-12-02 10:56:11 -0500629
Brad Bishop87b63c12016-03-18 14:47:51 -0400630 def __init__(self, app, bus):
631 super(SessionHandler, self).__init__(
632 app, bus)
633 self.hmac_key = os.urandom(128)
634 self.session_store = []
Brad Bishop2f428582015-12-02 10:56:11 -0500635
Brad Bishop87b63c12016-03-18 14:47:51 -0400636 @staticmethod
637 def authenticate(username, clear):
638 try:
639 encoded = spwd.getspnam(username)[1]
640 return encoded == crypt.crypt(clear, encoded)
641 except KeyError:
642 return False
Brad Bishop2f428582015-12-02 10:56:11 -0500643
Brad Bishop87b63c12016-03-18 14:47:51 -0400644 def invalidate_session(self, session):
645 try:
646 self.session_store.remove(session)
647 except ValueError:
648 pass
Brad Bishop2f428582015-12-02 10:56:11 -0500649
Brad Bishop87b63c12016-03-18 14:47:51 -0400650 def new_session(self):
651 sid = os.urandom(32)
652 if self.MAX_SESSIONS <= len(self.session_store):
653 self.session_store.pop()
654 self.session_store.insert(0, {'sid': sid})
Brad Bishop2f428582015-12-02 10:56:11 -0500655
Brad Bishop87b63c12016-03-18 14:47:51 -0400656 return self.session_store[0]
Brad Bishop2f428582015-12-02 10:56:11 -0500657
Brad Bishop87b63c12016-03-18 14:47:51 -0400658 def get_session(self, sid):
659 sids = [x['sid'] for x in self.session_store]
660 try:
661 return self.session_store[sids.index(sid)]
662 except ValueError:
663 return None
Brad Bishop2f428582015-12-02 10:56:11 -0500664
Brad Bishop87b63c12016-03-18 14:47:51 -0400665 def get_session_from_cookie(self):
666 return self.get_session(
667 request.get_cookie(
668 'sid', secret=self.hmac_key))
Brad Bishop2f428582015-12-02 10:56:11 -0500669
Brad Bishop87b63c12016-03-18 14:47:51 -0400670 def do_post(self, **kw):
671 if request.path == '/login':
672 return self.do_login(**kw)
673 else:
674 return self.do_logout(**kw)
Brad Bishop2f428582015-12-02 10:56:11 -0500675
Brad Bishop87b63c12016-03-18 14:47:51 -0400676 def do_logout(self, **kw):
677 session = self.get_session_from_cookie()
678 if session is not None:
679 user = session['user']
680 self.invalidate_session(session)
681 response.delete_cookie('sid')
682 return self.login_str % (user, 'out')
Brad Bishop2f428582015-12-02 10:56:11 -0500683
Brad Bishop87b63c12016-03-18 14:47:51 -0400684 return self.no_user_str
Brad Bishop2f428582015-12-02 10:56:11 -0500685
Brad Bishop87b63c12016-03-18 14:47:51 -0400686 def do_login(self, **kw):
687 session = self.get_session_from_cookie()
688 if session is not None:
689 return self.login_str % (session['user'], 'in')
Brad Bishop2f428582015-12-02 10:56:11 -0500690
Brad Bishop87b63c12016-03-18 14:47:51 -0400691 if len(request.parameter_list) != 2:
692 abort(400, self.bad_json_str % (request.json))
Brad Bishop2f428582015-12-02 10:56:11 -0500693
Brad Bishop87b63c12016-03-18 14:47:51 -0400694 if not self.authenticate(*request.parameter_list):
Brad Bishopdc3fbfa2016-09-08 09:51:38 -0400695 abort(401, self.bad_passwd_str)
Brad Bishop2f428582015-12-02 10:56:11 -0500696
Brad Bishop87b63c12016-03-18 14:47:51 -0400697 user = request.parameter_list[0]
698 session = self.new_session()
699 session['user'] = user
700 response.set_cookie(
701 'sid', session['sid'], secret=self.hmac_key,
702 secure=True,
703 httponly=True)
704 return self.login_str % (user, 'in')
Brad Bishop2f428582015-12-02 10:56:11 -0500705
Brad Bishop87b63c12016-03-18 14:47:51 -0400706 def find(self, **kw):
707 pass
Brad Bishop2f428582015-12-02 10:56:11 -0500708
Brad Bishop87b63c12016-03-18 14:47:51 -0400709 def setup(self, **kw):
710 pass
711
Brad Bishop2f428582015-12-02 10:56:11 -0500712
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500713class ImageUploadUtils:
714 ''' Provides common utils for image upload. '''
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500715
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500716 file_loc = '/tmp/images'
717 file_prefix = 'img'
718 file_suffix = ''
719
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500720 @classmethod
721 def do_upload(cls, filename=''):
722 if not os.path.exists(cls.file_loc):
Gunnar Millsfb515792017-11-09 15:52:17 -0600723 abort(500, "Error Directory not found")
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500724 if not filename:
725 handle, filename = tempfile.mkstemp(cls.file_suffix,
726 cls.file_prefix, cls.file_loc)
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500727 else:
728 filename = os.path.join(cls.file_loc, filename)
Gunnar Millsb66b18c2017-08-21 16:17:21 -0500729 handle = os.open(filename, os.O_WRONLY | os.O_CREAT)
Leonel Gonzalez0b62edf2017-06-08 15:10:03 -0500730 try:
731 file_contents = request.body.read()
732 request.body.close()
Gunnar Millsb66b18c2017-08-21 16:17:21 -0500733 os.write(handle, file_contents)
CamVan Nguyen249d1322018-03-05 10:08:33 -0600734 except (IOError, ValueError) as e:
Leonel Gonzalez0b62edf2017-06-08 15:10:03 -0500735 abort(400, str(e))
736 except:
737 abort(400, "Unexpected Error")
Gunnar Millsb66b18c2017-08-21 16:17:21 -0500738 finally:
739 os.close(handle)
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500740
741
742class ImagePostHandler(RouteHandler):
743 ''' Handles the /upload/image route. '''
744
745 verbs = ['POST']
746 rules = ['/upload/image']
747 content_type = 'application/octet-stream'
748
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500749 def __init__(self, app, bus):
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500750 super(ImagePostHandler, self).__init__(
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500751 app, bus, self.verbs, self.rules, self.content_type)
752
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500753 def do_post(self, filename=''):
754 ImageUploadUtils.do_upload()
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500755
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500756 def find(self, **kw):
757 pass
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500758
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500759 def setup(self, **kw):
760 pass
761
762
Deepak Kodihalli639b5022017-10-13 06:40:26 -0500763class EventNotifier:
764 keyNames = {}
765 keyNames['event'] = 'event'
766 keyNames['path'] = 'path'
767 keyNames['intfMap'] = 'interfaces'
768 keyNames['propMap'] = 'properties'
769 keyNames['intf'] = 'interface'
770
771 def __init__(self, wsock, filters):
772 self.wsock = wsock
773 self.paths = filters.get("paths", [])
774 self.interfaces = filters.get("interfaces", [])
775 if not self.paths:
776 self.paths.append(None)
777 bus = dbus.SystemBus()
778 # Add a signal receiver for every path the client is interested in
779 for path in self.paths:
780 bus.add_signal_receiver(
781 self.interfaces_added_handler,
782 dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager',
783 signal_name='InterfacesAdded',
784 path=path)
785 bus.add_signal_receiver(
786 self.properties_changed_handler,
787 dbus_interface=dbus.PROPERTIES_IFACE,
788 signal_name='PropertiesChanged',
789 path=path,
790 path_keyword='path')
791 loop = gobject.MainLoop()
792 # gobject's mainloop.run() will block the entire process, so the gevent
793 # scheduler and hence greenlets won't execute. The while-loop below
794 # works around this limitation by using gevent's sleep, instead of
795 # calling loop.run()
796 gcontext = loop.get_context()
797 while loop is not None:
798 try:
799 if gcontext.pending():
800 gcontext.iteration()
801 else:
802 # gevent.sleep puts only the current greenlet to sleep,
803 # not the entire process.
804 gevent.sleep(5)
805 except WebSocketError:
806 break
807
808 def interfaces_added_handler(self, path, iprops, **kw):
809 ''' If the client is interested in these changes, respond to the
810 client. This handles d-bus interface additions.'''
811 if (not self.interfaces) or \
812 (not set(iprops).isdisjoint(self.interfaces)):
813 response = {}
814 response[self.keyNames['event']] = "InterfacesAdded"
815 response[self.keyNames['path']] = path
816 response[self.keyNames['intfMap']] = iprops
817 try:
818 self.wsock.send(json.dumps(response))
819 except WebSocketError:
820 return
821
822 def properties_changed_handler(self, interface, new, old, **kw):
823 ''' If the client is interested in these changes, respond to the
824 client. This handles d-bus property changes. '''
825 if (not self.interfaces) or (interface in self.interfaces):
826 path = str(kw['path'])
827 response = {}
828 response[self.keyNames['event']] = "PropertiesChanged"
829 response[self.keyNames['path']] = path
830 response[self.keyNames['intf']] = interface
831 response[self.keyNames['propMap']] = new
832 try:
833 self.wsock.send(json.dumps(response))
834 except WebSocketError:
835 return
836
837
Deepak Kodihallib209dd12017-10-11 01:19:17 -0500838class EventHandler(RouteHandler):
839 ''' Handles the /subscribe route, for clients to be able
840 to subscribe to BMC events. '''
841
842 verbs = ['GET']
843 rules = ['/subscribe']
844
845 def __init__(self, app, bus):
846 super(EventHandler, self).__init__(
847 app, bus, self.verbs, self.rules)
848
849 def find(self, **kw):
850 pass
851
852 def setup(self, **kw):
853 pass
854
855 def do_get(self):
856 wsock = request.environ.get('wsgi.websocket')
857 if not wsock:
858 abort(400, 'Expected WebSocket request.')
Deepak Kodihalli639b5022017-10-13 06:40:26 -0500859 filters = wsock.receive()
860 filters = json.loads(filters)
861 notifier = EventNotifier(wsock, filters)
Deepak Kodihallib209dd12017-10-11 01:19:17 -0500862
863
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500864class ImagePutHandler(RouteHandler):
865 ''' Handles the /upload/image/<filename> route. '''
866
867 verbs = ['PUT']
868 rules = ['/upload/image/<filename>']
869 content_type = 'application/octet-stream'
870
871 def __init__(self, app, bus):
872 super(ImagePutHandler, self).__init__(
873 app, bus, self.verbs, self.rules, self.content_type)
874
875 def do_put(self, filename=''):
876 ImageUploadUtils.do_upload(filename)
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500877
878 def find(self, **kw):
879 pass
880
881 def setup(self, **kw):
882 pass
883
884
Jayanth Othayoth9bc94992017-06-29 06:30:40 -0500885class DownloadDumpHandler(RouteHandler):
886 ''' Handles the /download/dump route. '''
887
888 verbs = 'GET'
889 rules = ['/download/dump/<dumpid>']
890 content_type = 'application/octet-stream'
Jayanth Othayoth18c3a242017-08-02 08:16:11 -0500891 dump_loc = '/var/lib/phosphor-debug-collector/dumps'
Brad Bishop944cd042017-07-10 16:42:41 -0400892 suppress_json_resp = True
Jayanth Othayoth9bc94992017-06-29 06:30:40 -0500893
894 def __init__(self, app, bus):
895 super(DownloadDumpHandler, self).__init__(
896 app, bus, self.verbs, self.rules, self.content_type)
897
898 def do_get(self, dumpid):
899 return self.do_download(dumpid)
900
901 def find(self, **kw):
902 pass
903
904 def setup(self, **kw):
905 pass
906
907 def do_download(self, dumpid):
908 dump_loc = os.path.join(self.dump_loc, dumpid)
909 if not os.path.exists(dump_loc):
910 abort(404, "Path not found")
911
912 files = os.listdir(dump_loc)
913 num_files = len(files)
914 if num_files == 0:
915 abort(404, "Dump not found")
916
917 return static_file(os.path.basename(files[0]), root=dump_loc,
918 download=True, mimetype=self.content_type)
919
920
Matt Spinlerd41643e2018-02-02 13:51:38 -0600921class WebHandler(RouteHandler):
922 ''' Handles the routes for the web UI files. '''
923
924 verbs = 'GET'
925
926 # Match only what we know are web files, so everything else
927 # can get routed to the REST handlers.
928 rules = ['//', '/<filename:re:.+\.js>', '/<filename:re:.+\.svg>',
929 '/<filename:re:.+\.css>', '/<filename:re:.+\.ttf>',
930 '/<filename:re:.+\.eot>', '/<filename:re:.+\.woff>',
931 '/<filename:re:.+\.woff2>', '/<filename:re:.+\.map>',
932 '/<filename:re:.+\.png>', '/<filename:re:.+\.html>',
933 '/<filename:re:.+\.ico>']
934
935 # The mimetypes module knows about most types, but not these
936 content_types = {
937 '.eot': 'application/vnd.ms-fontobject',
938 '.woff': 'application/x-font-woff',
939 '.woff2': 'application/x-font-woff2',
940 '.ttf': 'application/x-font-ttf',
941 '.map': 'application/json'
942 }
943
944 _require_auth = None
945 suppress_json_resp = True
946
947 def __init__(self, app, bus):
948 super(WebHandler, self).__init__(
949 app, bus, self.verbs, self.rules)
950
951 def get_type(self, filename):
952 ''' Returns the content type and encoding for a file '''
953
954 content_type, encoding = mimetypes.guess_type(filename)
955
956 # Try our own list if mimetypes didn't recognize it
957 if content_type is None:
958 if filename[-3:] == '.gz':
959 filename = filename[:-3]
960 extension = filename[filename.rfind('.'):]
961 content_type = self.content_types.get(extension, None)
962
963 return content_type, encoding
964
965 def do_get(self, filename='index.html'):
966
967 # If a gzipped version exists, use that instead.
968 # Possible future enhancement: if the client doesn't
969 # accept compressed files, unzip it ourselves before sending.
970 if not os.path.exists(os.path.join(www_base_path, filename)):
971 filename = filename + '.gz'
972
973 # Though bottle should protect us, ensure path is valid
974 realpath = os.path.realpath(filename)
975 if realpath[0] == '/':
976 realpath = realpath[1:]
977 if not os.path.exists(os.path.join(www_base_path, realpath)):
978 abort(404, "Path not found")
979
980 mimetype, encoding = self.get_type(filename)
981
982 # Couldn't find the type - let static_file() deal with it,
983 # though this should never happen.
984 if mimetype is None:
985 print("Can't figure out content-type for %s" % filename)
986 mimetype = 'auto'
987
988 # This call will set several header fields for us,
989 # including the charset if the type is text.
990 response = static_file(filename, www_base_path, mimetype)
991
992 # static_file() will only set the encoding if the
993 # mimetype was auto, so set it here.
994 if encoding is not None:
995 response.set_header('Content-Encoding', encoding)
996
997 return response
998
999 def find(self, **kw):
1000 pass
1001
1002 def setup(self, **kw):
1003 pass
1004
1005
Brad Bishop2f428582015-12-02 10:56:11 -05001006class AuthorizationPlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -04001007 ''' Invokes an optional list of authorization callbacks. '''
Brad Bishop2f428582015-12-02 10:56:11 -05001008
Brad Bishop87b63c12016-03-18 14:47:51 -04001009 name = 'authorization'
1010 api = 2
Brad Bishop2f428582015-12-02 10:56:11 -05001011
Brad Bishop87b63c12016-03-18 14:47:51 -04001012 class Compose:
1013 def __init__(self, validators, callback, session_mgr):
1014 self.validators = validators
1015 self.callback = callback
1016 self.session_mgr = session_mgr
Brad Bishop2f428582015-12-02 10:56:11 -05001017
Brad Bishop87b63c12016-03-18 14:47:51 -04001018 def __call__(self, *a, **kw):
1019 sid = request.get_cookie('sid', secret=self.session_mgr.hmac_key)
1020 session = self.session_mgr.get_session(sid)
Brad Bishopd4c1c552017-02-21 00:07:28 -05001021 if request.method != 'OPTIONS':
1022 for x in self.validators:
1023 x(session, *a, **kw)
Brad Bishop2f428582015-12-02 10:56:11 -05001024
Brad Bishop87b63c12016-03-18 14:47:51 -04001025 return self.callback(*a, **kw)
Brad Bishop2f428582015-12-02 10:56:11 -05001026
Brad Bishop87b63c12016-03-18 14:47:51 -04001027 def apply(self, callback, route):
1028 undecorated = route.get_undecorated_callback()
1029 if not isinstance(undecorated, RouteHandler):
1030 return callback
Brad Bishop2f428582015-12-02 10:56:11 -05001031
Brad Bishop87b63c12016-03-18 14:47:51 -04001032 auth_types = getattr(
1033 undecorated, '_require_auth', None)
1034 if not auth_types:
1035 return callback
Brad Bishop2f428582015-12-02 10:56:11 -05001036
Brad Bishop87b63c12016-03-18 14:47:51 -04001037 return self.Compose(
1038 auth_types, callback, undecorated.app.session_handler)
1039
Brad Bishop2f428582015-12-02 10:56:11 -05001040
Brad Bishopd0c404a2017-02-21 09:23:25 -05001041class CorsPlugin(object):
1042 ''' Add CORS headers. '''
1043
1044 name = 'cors'
1045 api = 2
1046
1047 @staticmethod
1048 def process_origin():
1049 origin = request.headers.get('Origin')
1050 if origin:
1051 response.add_header('Access-Control-Allow-Origin', origin)
1052 response.add_header(
1053 'Access-Control-Allow-Credentials', 'true')
1054
1055 @staticmethod
1056 def process_method_and_headers(verbs):
1057 method = request.headers.get('Access-Control-Request-Method')
1058 headers = request.headers.get('Access-Control-Request-Headers')
1059 if headers:
1060 headers = [x.lower() for x in headers.split(',')]
1061
1062 if method in verbs \
1063 and headers == ['content-type']:
1064 response.add_header('Access-Control-Allow-Methods', method)
1065 response.add_header(
1066 'Access-Control-Allow-Headers', 'Content-Type')
Ratan Gupta91b46f82018-01-14 12:52:23 +05301067 response.add_header('X-Frame-Options', 'deny')
1068 response.add_header('X-Content-Type-Options', 'nosniff')
1069 response.add_header('X-XSS-Protection', '1; mode=block')
1070 response.add_header(
1071 'Content-Security-Policy', "default-src 'self'")
1072 response.add_header(
1073 'Strict-Transport-Security',
1074 'max-age=31536000; includeSubDomains; preload')
Brad Bishopd0c404a2017-02-21 09:23:25 -05001075
1076 def __init__(self, app):
1077 app.install_error_callback(self.error_callback)
1078
1079 def apply(self, callback, route):
1080 undecorated = route.get_undecorated_callback()
1081 if not isinstance(undecorated, RouteHandler):
1082 return callback
1083
1084 if not getattr(undecorated, '_enable_cors', None):
1085 return callback
1086
1087 def wrap(*a, **kw):
1088 self.process_origin()
1089 self.process_method_and_headers(undecorated._verbs)
1090 return callback(*a, **kw)
1091
1092 return wrap
1093
1094 def error_callback(self, **kw):
1095 self.process_origin()
1096
1097
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001098class JsonApiRequestPlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -04001099 ''' Ensures request content satisfies the OpenBMC json api format. '''
1100 name = 'json_api_request'
1101 api = 2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001102
Brad Bishop87b63c12016-03-18 14:47:51 -04001103 error_str = "Expecting request format { 'data': <value> }, got '%s'"
1104 type_error_str = "Unsupported Content-Type: '%s'"
1105 json_type = "application/json"
1106 request_methods = ['PUT', 'POST', 'PATCH']
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001107
Brad Bishop87b63c12016-03-18 14:47:51 -04001108 @staticmethod
1109 def content_expected():
1110 return request.method in JsonApiRequestPlugin.request_methods
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001111
Brad Bishop87b63c12016-03-18 14:47:51 -04001112 def validate_request(self):
1113 if request.content_length > 0 and \
1114 request.content_type != self.json_type:
1115 abort(415, self.type_error_str % request.content_type)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001116
Brad Bishop87b63c12016-03-18 14:47:51 -04001117 try:
1118 request.parameter_list = request.json.get('data')
CamVan Nguyen249d1322018-03-05 10:08:33 -06001119 except ValueError as e:
Brad Bishop87b63c12016-03-18 14:47:51 -04001120 abort(400, str(e))
1121 except (AttributeError, KeyError, TypeError):
1122 abort(400, self.error_str % request.json)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001123
Brad Bishop87b63c12016-03-18 14:47:51 -04001124 def apply(self, callback, route):
Deepak Kodihallifb6cd482017-04-10 07:27:09 -05001125 content_type = getattr(
1126 route.get_undecorated_callback(), '_content_type', None)
1127 if self.json_type != content_type:
1128 return callback
1129
Brad Bishop87b63c12016-03-18 14:47:51 -04001130 verbs = getattr(
1131 route.get_undecorated_callback(), '_verbs', None)
1132 if verbs is None:
1133 return callback
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001134
Brad Bishop87b63c12016-03-18 14:47:51 -04001135 if not set(self.request_methods).intersection(verbs):
1136 return callback
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001137
Brad Bishop87b63c12016-03-18 14:47:51 -04001138 def wrap(*a, **kw):
1139 if self.content_expected():
1140 self.validate_request()
1141 return callback(*a, **kw)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001142
Brad Bishop87b63c12016-03-18 14:47:51 -04001143 return wrap
1144
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001145
1146class JsonApiRequestTypePlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -04001147 ''' Ensures request content type satisfies the OpenBMC json api format. '''
1148 name = 'json_api_method_request'
1149 api = 2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001150
Brad Bishop87b63c12016-03-18 14:47:51 -04001151 error_str = "Expecting request format { 'data': %s }, got '%s'"
Deepak Kodihallifb6cd482017-04-10 07:27:09 -05001152 json_type = "application/json"
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001153
Brad Bishop87b63c12016-03-18 14:47:51 -04001154 def apply(self, callback, route):
Deepak Kodihallifb6cd482017-04-10 07:27:09 -05001155 content_type = getattr(
1156 route.get_undecorated_callback(), '_content_type', None)
1157 if self.json_type != content_type:
1158 return callback
1159
Brad Bishop87b63c12016-03-18 14:47:51 -04001160 request_type = getattr(
1161 route.get_undecorated_callback(), 'request_type', None)
1162 if request_type is None:
1163 return callback
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001164
Brad Bishop87b63c12016-03-18 14:47:51 -04001165 def validate_request():
1166 if not isinstance(request.parameter_list, request_type):
1167 abort(400, self.error_str % (str(request_type), request.json))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001168
Brad Bishop87b63c12016-03-18 14:47:51 -04001169 def wrap(*a, **kw):
1170 if JsonApiRequestPlugin.content_expected():
1171 validate_request()
1172 return callback(*a, **kw)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001173
Brad Bishop87b63c12016-03-18 14:47:51 -04001174 return wrap
1175
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001176
Brad Bishop080a48e2017-02-21 22:34:43 -05001177class JsonErrorsPlugin(JSONPlugin):
1178 ''' Extend the Bottle JSONPlugin such that it also encodes error
1179 responses. '''
1180
1181 def __init__(self, app, **kw):
1182 super(JsonErrorsPlugin, self).__init__(**kw)
1183 self.json_opts = {
CamVan Nguyen249d1322018-03-05 10:08:33 -06001184 x: y for x, y in kw.items()
Brad Bishop080a48e2017-02-21 22:34:43 -05001185 if x in ['indent', 'sort_keys']}
1186 app.install_error_callback(self.error_callback)
1187
1188 def error_callback(self, response_object, response_body, **kw):
1189 response_body['body'] = json.dumps(response_object, **self.json_opts)
1190 response.content_type = 'application/json'
1191
1192
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001193class JsonApiResponsePlugin(object):
Brad Bishop080a48e2017-02-21 22:34:43 -05001194 ''' Emits responses in the OpenBMC json api format. '''
Brad Bishop87b63c12016-03-18 14:47:51 -04001195 name = 'json_api_response'
1196 api = 2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001197
Brad Bishopd4c1c552017-02-21 00:07:28 -05001198 @staticmethod
1199 def has_body():
1200 return request.method not in ['OPTIONS']
1201
Brad Bishop080a48e2017-02-21 22:34:43 -05001202 def __init__(self, app):
1203 app.install_error_callback(self.error_callback)
1204
Brad Bishop87b63c12016-03-18 14:47:51 -04001205 def apply(self, callback, route):
Brad Bishop944cd042017-07-10 16:42:41 -04001206 skip = getattr(
1207 route.get_undecorated_callback(), 'suppress_json_resp', None)
1208 if skip:
Jayanth Othayoth1444fd82017-06-29 05:45:07 -05001209 return callback
1210
Brad Bishop87b63c12016-03-18 14:47:51 -04001211 def wrap(*a, **kw):
Brad Bishopd4c1c552017-02-21 00:07:28 -05001212 data = callback(*a, **kw)
1213 if self.has_body():
1214 resp = {'data': data}
1215 resp['status'] = 'ok'
1216 resp['message'] = response.status_line
1217 return resp
Brad Bishop87b63c12016-03-18 14:47:51 -04001218 return wrap
1219
Brad Bishop080a48e2017-02-21 22:34:43 -05001220 def error_callback(self, error, response_object, **kw):
Brad Bishop87b63c12016-03-18 14:47:51 -04001221 response_object['message'] = error.status_line
Brad Bishop9c2531e2017-03-07 10:22:40 -05001222 response_object['status'] = 'error'
Brad Bishop080a48e2017-02-21 22:34:43 -05001223 response_object.setdefault('data', {})['description'] = str(error.body)
Brad Bishop87b63c12016-03-18 14:47:51 -04001224 if error.status_code == 500:
1225 response_object['data']['exception'] = repr(error.exception)
1226 response_object['data']['traceback'] = error.traceback.splitlines()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001227
Brad Bishop87b63c12016-03-18 14:47:51 -04001228
Brad Bishop080a48e2017-02-21 22:34:43 -05001229class JsonpPlugin(object):
Brad Bishop80fe37a2016-03-29 10:54:54 -04001230 ''' Json javascript wrapper. '''
1231 name = 'jsonp'
1232 api = 2
1233
Brad Bishop080a48e2017-02-21 22:34:43 -05001234 def __init__(self, app, **kw):
1235 app.install_error_callback(self.error_callback)
Brad Bishop80fe37a2016-03-29 10:54:54 -04001236
1237 @staticmethod
1238 def to_jsonp(json):
1239 jwrapper = request.query.callback or None
1240 if(jwrapper):
1241 response.set_header('Content-Type', 'application/javascript')
1242 json = jwrapper + '(' + json + ');'
1243 return json
1244
1245 def apply(self, callback, route):
1246 def wrap(*a, **kw):
1247 return self.to_jsonp(callback(*a, **kw))
1248 return wrap
1249
Brad Bishop080a48e2017-02-21 22:34:43 -05001250 def error_callback(self, response_body, **kw):
1251 response_body['body'] = self.to_jsonp(response_body['body'])
Brad Bishop80fe37a2016-03-29 10:54:54 -04001252
1253
Deepak Kodihalli461367a2017-04-10 07:11:38 -05001254class ContentCheckerPlugin(object):
1255 ''' Ensures that a route is associated with the expected content-type
1256 header. '''
1257 name = 'content_checker'
1258 api = 2
1259
1260 class Checker:
1261 def __init__(self, type, callback):
1262 self.expected_type = type
1263 self.callback = callback
1264 self.error_str = "Expecting content type '%s', got '%s'"
1265
1266 def __call__(self, *a, **kw):
Deepak Kodihallidb1a21e2017-04-27 06:30:11 -05001267 if request.method in ['PUT', 'POST', 'PATCH'] and \
1268 self.expected_type and \
Deepak Kodihalli461367a2017-04-10 07:11:38 -05001269 self.expected_type != request.content_type:
1270 abort(415, self.error_str % (self.expected_type,
1271 request.content_type))
1272
1273 return self.callback(*a, **kw)
1274
1275 def apply(self, callback, route):
1276 content_type = getattr(
1277 route.get_undecorated_callback(), '_content_type', None)
1278
1279 return self.Checker(content_type, callback)
1280
1281
Brad Bishop2c6fc762016-08-29 15:53:25 -04001282class App(Bottle):
Deepak Kodihalli0fe213f2017-10-11 00:08:48 -05001283 def __init__(self, **kw):
Brad Bishop2c6fc762016-08-29 15:53:25 -04001284 super(App, self).__init__(autojson=False)
Deepak Kodihallib209dd12017-10-11 01:19:17 -05001285
1286 self.have_wsock = kw.get('have_wsock', False)
1287
Brad Bishop2ddfa002016-08-29 15:11:55 -04001288 self.bus = dbus.SystemBus()
1289 self.mapper = obmc.mapper.Mapper(self.bus)
Brad Bishop080a48e2017-02-21 22:34:43 -05001290 self.error_callbacks = []
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001291
Brad Bishop87b63c12016-03-18 14:47:51 -04001292 self.install_hooks()
1293 self.install_plugins()
1294 self.create_handlers()
1295 self.install_handlers()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001296
Brad Bishop87b63c12016-03-18 14:47:51 -04001297 def install_plugins(self):
1298 # install json api plugins
1299 json_kw = {'indent': 2, 'sort_keys': True}
Brad Bishop87b63c12016-03-18 14:47:51 -04001300 self.install(AuthorizationPlugin())
Brad Bishopd0c404a2017-02-21 09:23:25 -05001301 self.install(CorsPlugin(self))
Deepak Kodihalli461367a2017-04-10 07:11:38 -05001302 self.install(ContentCheckerPlugin())
Brad Bishop080a48e2017-02-21 22:34:43 -05001303 self.install(JsonpPlugin(self, **json_kw))
1304 self.install(JsonErrorsPlugin(self, **json_kw))
1305 self.install(JsonApiResponsePlugin(self))
Brad Bishop87b63c12016-03-18 14:47:51 -04001306 self.install(JsonApiRequestPlugin())
1307 self.install(JsonApiRequestTypePlugin())
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001308
Brad Bishop87b63c12016-03-18 14:47:51 -04001309 def install_hooks(self):
Brad Bishop080a48e2017-02-21 22:34:43 -05001310 self.error_handler_type = type(self.default_error_handler)
1311 self.original_error_handler = self.default_error_handler
1312 self.default_error_handler = self.error_handler_type(
1313 self.custom_error_handler, self, Bottle)
1314
Brad Bishop87b63c12016-03-18 14:47:51 -04001315 self.real_router_match = self.router.match
1316 self.router.match = self.custom_router_match
1317 self.add_hook('before_request', self.strip_extra_slashes)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001318
Brad Bishop87b63c12016-03-18 14:47:51 -04001319 def create_handlers(self):
1320 # create route handlers
1321 self.session_handler = SessionHandler(self, self.bus)
Matt Spinlerd41643e2018-02-02 13:51:38 -06001322 self.web_handler = WebHandler(self, self.bus)
Brad Bishop87b63c12016-03-18 14:47:51 -04001323 self.directory_handler = DirectoryHandler(self, self.bus)
1324 self.list_names_handler = ListNamesHandler(self, self.bus)
1325 self.list_handler = ListHandler(self, self.bus)
1326 self.method_handler = MethodHandler(self, self.bus)
1327 self.property_handler = PropertyHandler(self, self.bus)
1328 self.schema_handler = SchemaHandler(self, self.bus)
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -05001329 self.image_upload_post_handler = ImagePostHandler(self, self.bus)
1330 self.image_upload_put_handler = ImagePutHandler(self, self.bus)
Jayanth Othayoth9bc94992017-06-29 06:30:40 -05001331 self.download_dump_get_handler = DownloadDumpHandler(self, self.bus)
Deepak Kodihallib209dd12017-10-11 01:19:17 -05001332 if self.have_wsock:
1333 self.event_handler = EventHandler(self, self.bus)
Brad Bishop87b63c12016-03-18 14:47:51 -04001334 self.instance_handler = InstanceHandler(self, self.bus)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001335
Brad Bishop87b63c12016-03-18 14:47:51 -04001336 def install_handlers(self):
1337 self.session_handler.install()
Matt Spinlerd41643e2018-02-02 13:51:38 -06001338 self.web_handler.install()
Brad Bishop87b63c12016-03-18 14:47:51 -04001339 self.directory_handler.install()
1340 self.list_names_handler.install()
1341 self.list_handler.install()
1342 self.method_handler.install()
1343 self.property_handler.install()
1344 self.schema_handler.install()
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -05001345 self.image_upload_post_handler.install()
1346 self.image_upload_put_handler.install()
Jayanth Othayoth9bc94992017-06-29 06:30:40 -05001347 self.download_dump_get_handler.install()
Deepak Kodihallib209dd12017-10-11 01:19:17 -05001348 if self.have_wsock:
1349 self.event_handler.install()
Brad Bishop87b63c12016-03-18 14:47:51 -04001350 # this has to come last, since it matches everything
1351 self.instance_handler.install()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001352
Brad Bishop080a48e2017-02-21 22:34:43 -05001353 def install_error_callback(self, callback):
1354 self.error_callbacks.insert(0, callback)
1355
Brad Bishop87b63c12016-03-18 14:47:51 -04001356 def custom_router_match(self, environ):
1357 ''' The built-in Bottle algorithm for figuring out if a 404 or 405 is
1358 needed doesn't work for us since the instance rules match
1359 everything. This monkey-patch lets the route handler figure
1360 out which response is needed. This could be accomplished
1361 with a hook but that would require calling the router match
1362 function twice.
1363 '''
1364 route, args = self.real_router_match(environ)
1365 if isinstance(route.callback, RouteHandler):
1366 route.callback._setup(**args)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001367
Brad Bishop87b63c12016-03-18 14:47:51 -04001368 return route, args
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001369
Brad Bishop080a48e2017-02-21 22:34:43 -05001370 def custom_error_handler(self, res, error):
Gunnar Millsf01d0ba2017-10-25 20:37:24 -05001371 ''' Allow plugins to modify error responses too via this custom
Brad Bishop080a48e2017-02-21 22:34:43 -05001372 error handler. '''
1373
1374 response_object = {}
1375 response_body = {}
1376 for x in self.error_callbacks:
1377 x(error=error,
1378 response_object=response_object,
1379 response_body=response_body)
1380
1381 return response_body.get('body', "")
1382
Brad Bishop87b63c12016-03-18 14:47:51 -04001383 @staticmethod
1384 def strip_extra_slashes():
1385 path = request.environ['PATH_INFO']
1386 trailing = ("", "/")[path[-1] == '/']
CamVan Nguyen249d1322018-03-05 10:08:33 -06001387 parts = list(filter(bool, path.split('/')))
Brad Bishop87b63c12016-03-18 14:47:51 -04001388 request.environ['PATH_INFO'] = '/' + '/'.join(parts) + trailing