blob: 20294fd3a6027c1b12050a64f0abcbdb909101d5 [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)
41 import gobject
42 import gevent
Brad Bishopaa65f6e2015-10-27 16:28:51 -040043
Adriana Kobylakf92cf4d2017-12-13 11:46:50 -060044DBUS_UNKNOWN_INTERFACE = 'org.freedesktop.DBus.Error.UnknownInterface'
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050045DBUS_UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod'
46DBUS_INVALID_ARGS = 'org.freedesktop.DBus.Error.InvalidArgs'
Brad Bishopd4578922015-12-02 11:10:36 -050047DBUS_TYPE_ERROR = 'org.freedesktop.DBus.Python.TypeError'
Deepak Kodihalli6075bb42017-04-04 05:49:17 -050048DELETE_IFACE = 'xyz.openbmc_project.Object.Delete'
Brad Bishop9ee57c42015-11-03 14:59:29 -050049
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050050_4034_msg = "The specified %s cannot be %s: '%s'"
Brad Bishopaa65f6e2015-10-27 16:28:51 -040051
Matt Spinlerd41643e2018-02-02 13:51:38 -060052www_base_path = '/usr/share/www/'
53
Brad Bishop87b63c12016-03-18 14:47:51 -040054
Brad Bishop2f428582015-12-02 10:56:11 -050055def valid_user(session, *a, **kw):
Brad Bishop87b63c12016-03-18 14:47:51 -040056 ''' Authorization plugin callback that checks
57 that the user is logged in. '''
58 if session is None:
Brad Bishopdc3fbfa2016-09-08 09:51:38 -040059 abort(401, 'Login required')
Brad Bishop87b63c12016-03-18 14:47:51 -040060
Brad Bishop2f428582015-12-02 10:56:11 -050061
Leonel Gonzalez0bdef952017-04-18 08:17:49 -050062def get_type_signature_by_introspection(bus, service, object_path,
63 property_name):
64 obj = bus.get_object(service, object_path)
65 iface = dbus.Interface(obj, 'org.freedesktop.DBus.Introspectable')
66 xml_string = iface.Introspect()
67 for child in ElementTree.fromstring(xml_string):
68 # Iterate over each interfaces's properties to find
69 # matching property_name, and return its signature string
70 if child.tag == 'interface':
71 for i in child.iter():
72 if ('name' in i.attrib) and \
73 (i.attrib['name'] == property_name):
74 type_signature = i.attrib['type']
75 return type_signature
76
77
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +053078def get_method_signature(bus, service, object_path, interface, method):
79 obj = bus.get_object(service, object_path)
80 iface = dbus.Interface(obj, 'org.freedesktop.DBus.Introspectable')
81 xml_string = iface.Introspect()
82 arglist = []
83
84 root = ElementTree.fromstring(xml_string)
85 for dbus_intf in root.findall('interface'):
86 if (dbus_intf.get('name') == interface):
87 for dbus_method in dbus_intf.findall('method'):
88 if(dbus_method.get('name') == method):
89 for arg in dbus_method.findall('arg'):
90 arglist.append(arg.get('type'))
91 return arglist
92
93
Leonel Gonzalez0bdef952017-04-18 08:17:49 -050094def split_struct_signature(signature):
95 struct_regex = r'(b|y|n|i|x|q|u|t|d|s|a\(.+?\)|\(.+?\))|a\{.+?\}+?'
96 struct_matches = re.findall(struct_regex, signature)
97 return struct_matches
98
99
100def convert_type(signature, value):
101 # Basic Types
102 converted_value = None
103 converted_container = None
104 basic_types = {'b': bool, 'y': dbus.Byte, 'n': dbus.Int16, 'i': int,
105 'x': long, 'q': dbus.UInt16, 'u': dbus.UInt32,
106 't': dbus.UInt64, 'd': float, 's': str}
107 array_matches = re.match(r'a\((\S+)\)', signature)
108 struct_matches = re.match(r'\((\S+)\)', signature)
109 dictionary_matches = re.match(r'a{(\S+)}', signature)
110 if signature in basic_types:
111 converted_value = basic_types[signature](value)
112 return converted_value
113 # Array
114 if array_matches:
115 element_type = array_matches.group(1)
116 converted_container = list()
117 # Test if value is a list
118 # to avoid iterating over each character in a string.
119 # Iterate over each item and convert type
120 if isinstance(value, list):
121 for i in value:
122 converted_element = convert_type(element_type, i)
123 converted_container.append(converted_element)
124 # Convert non-sequence to expected type, and append to list
125 else:
126 converted_element = convert_type(element_type, value)
127 converted_container.append(converted_element)
128 return converted_container
129 # Struct
130 if struct_matches:
131 element_types = struct_matches.group(1)
132 split_element_types = split_struct_signature(element_types)
133 converted_container = list()
134 # Test if value is a list
135 if isinstance(value, list):
136 for index, val in enumerate(value):
137 converted_element = convert_type(split_element_types[index],
138 value[index])
139 converted_container.append(converted_element)
140 else:
141 converted_element = convert_type(element_types, value)
142 converted_container.append(converted_element)
143 return tuple(converted_container)
144 # Dictionary
145 if dictionary_matches:
146 element_types = dictionary_matches.group(1)
147 split_element_types = split_struct_signature(element_types)
148 converted_container = dict()
149 # Convert each element of dict
150 for key, val in value.iteritems():
151 converted_key = convert_type(split_element_types[0], key)
152 converted_val = convert_type(split_element_types[1], val)
153 converted_container[converted_key] = converted_val
154 return converted_container
155
156
Brad Bishop2f428582015-12-02 10:56:11 -0500157class UserInGroup:
Brad Bishop87b63c12016-03-18 14:47:51 -0400158 ''' Authorization plugin callback that checks that the user is logged in
159 and a member of a group. '''
160 def __init__(self, group):
161 self.group = group
Brad Bishop2f428582015-12-02 10:56:11 -0500162
Brad Bishop87b63c12016-03-18 14:47:51 -0400163 def __call__(self, session, *a, **kw):
164 valid_user(session, *a, **kw)
165 res = False
Brad Bishop2f428582015-12-02 10:56:11 -0500166
Brad Bishop87b63c12016-03-18 14:47:51 -0400167 try:
168 res = session['user'] in grp.getgrnam(self.group)[3]
169 except KeyError:
170 pass
Brad Bishop2f428582015-12-02 10:56:11 -0500171
Brad Bishop87b63c12016-03-18 14:47:51 -0400172 if not res:
173 abort(403, 'Insufficient access')
174
Brad Bishop2f428582015-12-02 10:56:11 -0500175
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500176class RouteHandler(object):
Brad Bishop6d190602016-04-15 13:09:39 -0400177 _require_auth = obmc.utils.misc.makelist(valid_user)
Brad Bishopd0c404a2017-02-21 09:23:25 -0500178 _enable_cors = True
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400179
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500180 def __init__(self, app, bus, verbs, rules, content_type=''):
Brad Bishop87b63c12016-03-18 14:47:51 -0400181 self.app = app
182 self.bus = bus
Brad Bishopb103d2d2016-03-04 16:19:14 -0500183 self.mapper = obmc.mapper.Mapper(bus)
Brad Bishop6d190602016-04-15 13:09:39 -0400184 self._verbs = obmc.utils.misc.makelist(verbs)
Brad Bishop87b63c12016-03-18 14:47:51 -0400185 self._rules = rules
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500186 self._content_type = content_type
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400187
Brad Bishop88c76a42017-02-21 00:02:02 -0500188 if 'GET' in self._verbs:
189 self._verbs = list(set(self._verbs + ['HEAD']))
Brad Bishopd4c1c552017-02-21 00:07:28 -0500190 if 'OPTIONS' not in self._verbs:
191 self._verbs.append('OPTIONS')
Brad Bishop88c76a42017-02-21 00:02:02 -0500192
Brad Bishop87b63c12016-03-18 14:47:51 -0400193 def _setup(self, **kw):
194 request.route_data = {}
Brad Bishopd4c1c552017-02-21 00:07:28 -0500195
Brad Bishop87b63c12016-03-18 14:47:51 -0400196 if request.method in self._verbs:
Brad Bishopd4c1c552017-02-21 00:07:28 -0500197 if request.method != 'OPTIONS':
198 return self.setup(**kw)
Brad Bishop88c76a42017-02-21 00:02:02 -0500199
Brad Bishopd4c1c552017-02-21 00:07:28 -0500200 # Javascript implementations will not send credentials
201 # with an OPTIONS request. Don't help malicious clients
202 # by checking the path here and returning a 404 if the
203 # path doesn't exist.
204 return None
Brad Bishop88c76a42017-02-21 00:02:02 -0500205
Brad Bishopd4c1c552017-02-21 00:07:28 -0500206 # Return 405
Brad Bishop88c76a42017-02-21 00:02:02 -0500207 raise HTTPError(
208 405, "Method not allowed.", Allow=','.join(self._verbs))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400209
Brad Bishop87b63c12016-03-18 14:47:51 -0400210 def __call__(self, **kw):
211 return getattr(self, 'do_' + request.method.lower())(**kw)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400212
Brad Bishop88c76a42017-02-21 00:02:02 -0500213 def do_head(self, **kw):
214 return self.do_get(**kw)
215
Brad Bishopd4c1c552017-02-21 00:07:28 -0500216 def do_options(self, **kw):
217 for v in self._verbs:
218 response.set_header(
219 'Allow',
220 ','.join(self._verbs))
221 return None
222
Brad Bishop87b63c12016-03-18 14:47:51 -0400223 def install(self):
224 self.app.route(
225 self._rules, callback=self,
Brad Bishopd4c1c552017-02-21 00:07:28 -0500226 method=['OPTIONS', 'GET', 'PUT', 'PATCH', 'POST', 'DELETE'])
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400227
Brad Bishop87b63c12016-03-18 14:47:51 -0400228 @staticmethod
229 def try_mapper_call(f, callback=None, **kw):
230 try:
231 return f(**kw)
232 except dbus.exceptions.DBusException, e:
Brad Bishopfce77562016-11-28 15:44:18 -0500233 if e.get_dbus_name() == \
234 'org.freedesktop.DBus.Error.ObjectPathInUse':
235 abort(503, str(e))
Brad Bishopb103d2d2016-03-04 16:19:14 -0500236 if e.get_dbus_name() != obmc.mapper.MAPPER_NOT_FOUND:
Brad Bishop87b63c12016-03-18 14:47:51 -0400237 raise
238 if callback is None:
239 def callback(e, **kw):
240 abort(404, str(e))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400241
Brad Bishop87b63c12016-03-18 14:47:51 -0400242 callback(e, **kw)
243
244 @staticmethod
245 def try_properties_interface(f, *a):
246 try:
247 return f(*a)
248 except dbus.exceptions.DBusException, e:
Adriana Kobylakf92cf4d2017-12-13 11:46:50 -0600249 if DBUS_UNKNOWN_INTERFACE in e.get_dbus_name():
Brad Bishopf4e74982016-04-01 14:53:05 -0400250 # interface doesn't have any properties
251 return None
Brad Bishop87b63c12016-03-18 14:47:51 -0400252 if DBUS_UNKNOWN_METHOD == e.get_dbus_name():
253 # properties interface not implemented at all
254 return None
255 raise
256
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400257
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500258class DirectoryHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400259 verbs = 'GET'
260 rules = '<path:path>/'
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400261
Brad Bishop87b63c12016-03-18 14:47:51 -0400262 def __init__(self, app, bus):
263 super(DirectoryHandler, self).__init__(
Brad Bishopc431e1a2017-07-10 16:44:51 -0400264 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400265
Brad Bishop87b63c12016-03-18 14:47:51 -0400266 def find(self, path='/'):
267 return self.try_mapper_call(
268 self.mapper.get_subtree_paths, path=path, depth=1)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400269
Brad Bishop87b63c12016-03-18 14:47:51 -0400270 def setup(self, path='/'):
271 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400272
Brad Bishop87b63c12016-03-18 14:47:51 -0400273 def do_get(self, path='/'):
274 return request.route_data['map']
275
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400276
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500277class ListNamesHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400278 verbs = 'GET'
279 rules = ['/list', '<path:path>/list']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400280
Brad Bishop87b63c12016-03-18 14:47:51 -0400281 def __init__(self, app, bus):
282 super(ListNamesHandler, self).__init__(
Brad Bishopc431e1a2017-07-10 16:44:51 -0400283 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400284
Brad Bishop87b63c12016-03-18 14:47:51 -0400285 def find(self, path='/'):
286 return self.try_mapper_call(
287 self.mapper.get_subtree, path=path).keys()
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400288
Brad Bishop87b63c12016-03-18 14:47:51 -0400289 def setup(self, path='/'):
290 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400291
Brad Bishop87b63c12016-03-18 14:47:51 -0400292 def do_get(self, path='/'):
293 return request.route_data['map']
294
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400295
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500296class ListHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400297 verbs = 'GET'
298 rules = ['/enumerate', '<path:path>/enumerate']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400299
Brad Bishop87b63c12016-03-18 14:47:51 -0400300 def __init__(self, app, bus):
301 super(ListHandler, self).__init__(
Brad Bishopc431e1a2017-07-10 16:44:51 -0400302 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400303
Brad Bishop87b63c12016-03-18 14:47:51 -0400304 def find(self, path='/'):
305 return self.try_mapper_call(
306 self.mapper.get_subtree, path=path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400307
Brad Bishop87b63c12016-03-18 14:47:51 -0400308 def setup(self, path='/'):
309 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400310
Brad Bishop87b63c12016-03-18 14:47:51 -0400311 def do_get(self, path='/'):
Brad Bishop71527b42016-04-01 14:51:14 -0400312 return {x: y for x, y in self.mapper.enumerate_subtree(
313 path,
314 mapper_data=request.route_data['map']).dataitems()}
Brad Bishop87b63c12016-03-18 14:47:51 -0400315
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400316
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500317class MethodHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400318 verbs = 'POST'
319 rules = '<path:path>/action/<method>'
320 request_type = list
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500321 content_type = 'application/json'
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400322
Brad Bishop87b63c12016-03-18 14:47:51 -0400323 def __init__(self, app, bus):
324 super(MethodHandler, self).__init__(
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500325 app, bus, self.verbs, self.rules, self.content_type)
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530326 self.service = ''
327 self.interface = ''
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400328
Brad Bishop87b63c12016-03-18 14:47:51 -0400329 def find(self, path, method):
Saqib Khan3a00b1f2017-11-04 15:56:21 -0500330 method_list = []
Brad Bishop87b63c12016-03-18 14:47:51 -0400331 busses = self.try_mapper_call(
332 self.mapper.get_object, path=path)
333 for items in busses.iteritems():
334 m = self.find_method_on_bus(path, method, *items)
335 if m:
Saqib Khan3a00b1f2017-11-04 15:56:21 -0500336 method_list.append(m)
Nagaraju Goruganti765c2c82017-11-13 06:17:13 -0600337 if method_list:
338 return method_list
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400339
Brad Bishop87b63c12016-03-18 14:47:51 -0400340 abort(404, _4034_msg % ('method', 'found', method))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400341
Brad Bishop87b63c12016-03-18 14:47:51 -0400342 def setup(self, path, method):
Saqib Khan3a00b1f2017-11-04 15:56:21 -0500343 request.route_data['map'] = self.find(path, method)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400344
Marri Devender Raobc0c6732017-11-20 00:15:47 -0600345 def do_post(self, path, method, retry=True):
Brad Bishop87b63c12016-03-18 14:47:51 -0400346 try:
Nagaraju Goruganti765c2c82017-11-13 06:17:13 -0600347 args = []
348 if request.parameter_list:
349 args = request.parameter_list
350 # To see if the return type is capable of being merged
351 if len(request.route_data['map']) > 1:
352 results = None
353 for item in request.route_data['map']:
354 tmp = item(*args)
355 if not results:
356 if tmp is not None:
357 results = type(tmp)()
358 if isinstance(results, dict):
359 results = results.update(tmp)
360 elif isinstance(results, list):
361 results = results + tmp
362 elif isinstance(results, type(None)):
363 results = None
364 else:
365 abort(501, 'Don\'t know how to merge method call '
366 'results of {}'.format(type(tmp)))
367 return results
368 # There is only one method
369 return request.route_data['map'][0](*args)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400370
Brad Bishop87b63c12016-03-18 14:47:51 -0400371 except dbus.exceptions.DBusException, e:
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530372 paramlist = []
Brad Bishopb7fca9b2018-01-23 12:16:50 -0500373 if e.get_dbus_name() == DBUS_INVALID_ARGS and retry:
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530374
375 signature_list = get_method_signature(self.bus, self.service,
376 path, self.interface,
377 method)
378 if not signature_list:
379 abort(400, "Failed to get method signature: %s" % str(e))
380 if len(signature_list) != len(request.parameter_list):
381 abort(400, "Invalid number of args")
382 converted_value = None
383 try:
384 for index, expected_type in enumerate(signature_list):
385 value = request.parameter_list[index]
386 converted_value = convert_type(expected_type, value)
387 paramlist.append(converted_value)
388 request.parameter_list = paramlist
Marri Devender Raobc0c6732017-11-20 00:15:47 -0600389 self.do_post(path, method, False)
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530390 return
391 except Exception as ex:
Nagaraju Gorugantiab404fa2017-12-14 10:24:40 -0600392 abort(400, "Bad Request/Invalid Args given")
Brad Bishop87b63c12016-03-18 14:47:51 -0400393 abort(400, str(e))
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530394
Brad Bishop87b63c12016-03-18 14:47:51 -0400395 if e.get_dbus_name() == DBUS_TYPE_ERROR:
396 abort(400, str(e))
397 raise
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400398
Brad Bishop87b63c12016-03-18 14:47:51 -0400399 @staticmethod
400 def find_method_in_interface(method, obj, interface, methods):
401 if methods is None:
402 return None
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400403
Brad Bishop6d190602016-04-15 13:09:39 -0400404 method = obmc.utils.misc.find_case_insensitive(method, methods.keys())
Brad Bishop87b63c12016-03-18 14:47:51 -0400405 if method is not None:
406 iface = dbus.Interface(obj, interface)
407 return iface.get_dbus_method(method)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400408
Brad Bishop87b63c12016-03-18 14:47:51 -0400409 def find_method_on_bus(self, path, method, bus, interfaces):
410 obj = self.bus.get_object(bus, path, introspect=False)
411 iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
412 data = iface.Introspect()
413 parser = IntrospectionNodeParser(
414 ElementTree.fromstring(data),
Brad Bishopb103d2d2016-03-04 16:19:14 -0500415 intf_match=obmc.utils.misc.ListMatch(interfaces))
Brad Bishop87b63c12016-03-18 14:47:51 -0400416 for x, y in parser.get_interfaces().iteritems():
417 m = self.find_method_in_interface(
418 method, obj, x, y.get('method'))
419 if m:
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530420 self.service = bus
421 self.interface = x
Brad Bishop87b63c12016-03-18 14:47:51 -0400422 return m
423
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400424
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500425class PropertyHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400426 verbs = ['PUT', 'GET']
427 rules = '<path:path>/attr/<prop>'
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500428 content_type = 'application/json'
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400429
Brad Bishop87b63c12016-03-18 14:47:51 -0400430 def __init__(self, app, bus):
431 super(PropertyHandler, self).__init__(
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500432 app, bus, self.verbs, self.rules, self.content_type)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400433
Brad Bishop87b63c12016-03-18 14:47:51 -0400434 def find(self, path, prop):
435 self.app.instance_handler.setup(path)
436 obj = self.app.instance_handler.do_get(path)
Brad Bishop56ad87f2017-02-21 23:33:29 -0500437 real_name = obmc.utils.misc.find_case_insensitive(
438 prop, obj.keys())
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400439
Brad Bishop56ad87f2017-02-21 23:33:29 -0500440 if not real_name:
441 if request.method == 'PUT':
442 abort(403, _4034_msg % ('property', 'created', prop))
443 else:
444 abort(404, _4034_msg % ('property', 'found', prop))
445 return real_name, {path: obj}
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500446
Brad Bishop87b63c12016-03-18 14:47:51 -0400447 def setup(self, path, prop):
Brad Bishop56ad87f2017-02-21 23:33:29 -0500448 name, obj = self.find(path, prop)
449 request.route_data['obj'] = obj
450 request.route_data['name'] = name
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500451
Brad Bishop87b63c12016-03-18 14:47:51 -0400452 def do_get(self, path, prop):
Brad Bishop56ad87f2017-02-21 23:33:29 -0500453 name = request.route_data['name']
454 return request.route_data['obj'][path][name]
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500455
Marri Devender Raobc0c6732017-11-20 00:15:47 -0600456 def do_put(self, path, prop, value=None, retry=True):
Brad Bishop87b63c12016-03-18 14:47:51 -0400457 if value is None:
458 value = request.parameter_list
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500459
Brad Bishop87b63c12016-03-18 14:47:51 -0400460 prop, iface, properties_iface = self.get_host_interface(
461 path, prop, request.route_data['map'][path])
462 try:
463 properties_iface.Set(iface, prop, value)
464 except ValueError, e:
465 abort(400, str(e))
466 except dbus.exceptions.DBusException, e:
Brad Bishopb7fca9b2018-01-23 12:16:50 -0500467 if e.get_dbus_name() == DBUS_INVALID_ARGS and retry:
Leonel Gonzalez0bdef952017-04-18 08:17:49 -0500468 bus_name = properties_iface.bus_name
469 expected_type = get_type_signature_by_introspection(self.bus,
470 bus_name,
471 path,
472 prop)
473 if not expected_type:
474 abort(403, "Failed to get expected type: %s" % str(e))
475 converted_value = None
476 try:
477 converted_value = convert_type(expected_type, value)
Marri Devender Raobc0c6732017-11-20 00:15:47 -0600478 self.do_put(path, prop, converted_value, False)
Leonel Gonzalez0bdef952017-04-18 08:17:49 -0500479 return
480 except Exception as ex:
481 abort(403, "Failed to convert %s to type %s" %
482 (value, expected_type))
Brad Bishop87b63c12016-03-18 14:47:51 -0400483 abort(403, str(e))
484 raise
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500485
Brad Bishop87b63c12016-03-18 14:47:51 -0400486 def get_host_interface(self, path, prop, bus_info):
487 for bus, interfaces in bus_info.iteritems():
488 obj = self.bus.get_object(bus, path, introspect=True)
489 properties_iface = dbus.Interface(
490 obj, dbus_interface=dbus.PROPERTIES_IFACE)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500491
Brad Bishop87b63c12016-03-18 14:47:51 -0400492 info = self.get_host_interface_on_bus(
493 path, prop, properties_iface, bus, interfaces)
494 if info is not None:
495 prop, iface = info
496 return prop, iface, properties_iface
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500497
Brad Bishop87b63c12016-03-18 14:47:51 -0400498 def get_host_interface_on_bus(self, path, prop, iface, bus, interfaces):
499 for i in interfaces:
500 properties = self.try_properties_interface(iface.GetAll, i)
Brad Bishop69cb6d12017-02-21 12:01:52 -0500501 if not properties:
Brad Bishop87b63c12016-03-18 14:47:51 -0400502 continue
Leonel Gonzalez409f6712017-05-24 09:51:55 -0500503 match = obmc.utils.misc.find_case_insensitive(
Brad Bishop8b0d3fa2016-11-28 15:41:47 -0500504 prop, properties.keys())
Leonel Gonzalez409f6712017-05-24 09:51:55 -0500505 if match is None:
Brad Bishop87b63c12016-03-18 14:47:51 -0400506 continue
Leonel Gonzalez409f6712017-05-24 09:51:55 -0500507 prop = match
Brad Bishop87b63c12016-03-18 14:47:51 -0400508 return prop, i
509
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500510
Brad Bishop2503bd62015-12-16 17:56:12 -0500511class SchemaHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400512 verbs = ['GET']
513 rules = '<path:path>/schema'
Brad Bishop2503bd62015-12-16 17:56:12 -0500514
Brad Bishop87b63c12016-03-18 14:47:51 -0400515 def __init__(self, app, bus):
516 super(SchemaHandler, self).__init__(
Brad Bishop529029b2017-07-10 16:46:01 -0400517 app, bus, self.verbs, self.rules)
Brad Bishop2503bd62015-12-16 17:56:12 -0500518
Brad Bishop87b63c12016-03-18 14:47:51 -0400519 def find(self, path):
520 return self.try_mapper_call(
521 self.mapper.get_object,
522 path=path)
Brad Bishop2503bd62015-12-16 17:56:12 -0500523
Brad Bishop87b63c12016-03-18 14:47:51 -0400524 def setup(self, path):
525 request.route_data['map'] = self.find(path)
Brad Bishop2503bd62015-12-16 17:56:12 -0500526
Brad Bishop87b63c12016-03-18 14:47:51 -0400527 def do_get(self, path):
528 schema = {}
529 for x in request.route_data['map'].iterkeys():
530 obj = self.bus.get_object(x, path, introspect=False)
531 iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
532 data = iface.Introspect()
533 parser = IntrospectionNodeParser(
534 ElementTree.fromstring(data))
535 for x, y in parser.get_interfaces().iteritems():
536 schema[x] = y
Brad Bishop2503bd62015-12-16 17:56:12 -0500537
Brad Bishop87b63c12016-03-18 14:47:51 -0400538 return schema
539
Brad Bishop2503bd62015-12-16 17:56:12 -0500540
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500541class InstanceHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400542 verbs = ['GET', 'PUT', 'DELETE']
543 rules = '<path:path>'
544 request_type = dict
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500545
Brad Bishop87b63c12016-03-18 14:47:51 -0400546 def __init__(self, app, bus):
547 super(InstanceHandler, self).__init__(
Brad Bishop529029b2017-07-10 16:46:01 -0400548 app, bus, self.verbs, self.rules)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500549
Brad Bishop87b63c12016-03-18 14:47:51 -0400550 def find(self, path, callback=None):
551 return {path: self.try_mapper_call(
552 self.mapper.get_object,
553 callback,
554 path=path)}
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500555
Brad Bishop87b63c12016-03-18 14:47:51 -0400556 def setup(self, path):
557 callback = None
558 if request.method == 'PUT':
559 def callback(e, **kw):
560 abort(403, _4034_msg % ('resource', 'created', path))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500561
Brad Bishop87b63c12016-03-18 14:47:51 -0400562 if request.route_data.get('map') is None:
563 request.route_data['map'] = self.find(path, callback)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500564
Brad Bishop87b63c12016-03-18 14:47:51 -0400565 def do_get(self, path):
Brad Bishop71527b42016-04-01 14:51:14 -0400566 return self.mapper.enumerate_object(
567 path,
568 mapper_data=request.route_data['map'])
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500569
Brad Bishop87b63c12016-03-18 14:47:51 -0400570 def do_put(self, path):
571 # make sure all properties exist in the request
572 obj = set(self.do_get(path).keys())
573 req = set(request.parameter_list.keys())
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500574
Brad Bishop87b63c12016-03-18 14:47:51 -0400575 diff = list(obj.difference(req))
576 if diff:
577 abort(403, _4034_msg % (
578 'resource', 'removed', '%s/attr/%s' % (path, diff[0])))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500579
Brad Bishop87b63c12016-03-18 14:47:51 -0400580 diff = list(req.difference(obj))
581 if diff:
582 abort(403, _4034_msg % (
583 'resource', 'created', '%s/attr/%s' % (path, diff[0])))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500584
Brad Bishop87b63c12016-03-18 14:47:51 -0400585 for p, v in request.parameter_list.iteritems():
586 self.app.property_handler.do_put(
587 path, p, v)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500588
Brad Bishop87b63c12016-03-18 14:47:51 -0400589 def do_delete(self, path):
590 for bus_info in request.route_data['map'][path].iteritems():
591 if self.bus_missing_delete(path, *bus_info):
592 abort(403, _4034_msg % ('resource', 'removed', path))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500593
Brad Bishop87b63c12016-03-18 14:47:51 -0400594 for bus in request.route_data['map'][path].iterkeys():
595 self.delete_on_bus(path, bus)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500596
Brad Bishop87b63c12016-03-18 14:47:51 -0400597 def bus_missing_delete(self, path, bus, interfaces):
598 return DELETE_IFACE not in interfaces
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500599
Brad Bishop87b63c12016-03-18 14:47:51 -0400600 def delete_on_bus(self, path, bus):
601 obj = self.bus.get_object(bus, path, introspect=False)
602 delete_iface = dbus.Interface(
603 obj, dbus_interface=DELETE_IFACE)
604 delete_iface.Delete()
605
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500606
Brad Bishop2f428582015-12-02 10:56:11 -0500607class SessionHandler(MethodHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400608 ''' Handles the /login and /logout routes, manages
609 server side session store and session cookies. '''
Brad Bishop2f428582015-12-02 10:56:11 -0500610
Brad Bishop87b63c12016-03-18 14:47:51 -0400611 rules = ['/login', '/logout']
612 login_str = "User '%s' logged %s"
613 bad_passwd_str = "Invalid username or password"
614 no_user_str = "No user logged in"
615 bad_json_str = "Expecting request format { 'data': " \
616 "[<username>, <password>] }, got '%s'"
617 _require_auth = None
618 MAX_SESSIONS = 16
Brad Bishop2f428582015-12-02 10:56:11 -0500619
Brad Bishop87b63c12016-03-18 14:47:51 -0400620 def __init__(self, app, bus):
621 super(SessionHandler, self).__init__(
622 app, bus)
623 self.hmac_key = os.urandom(128)
624 self.session_store = []
Brad Bishop2f428582015-12-02 10:56:11 -0500625
Brad Bishop87b63c12016-03-18 14:47:51 -0400626 @staticmethod
627 def authenticate(username, clear):
628 try:
629 encoded = spwd.getspnam(username)[1]
630 return encoded == crypt.crypt(clear, encoded)
631 except KeyError:
632 return False
Brad Bishop2f428582015-12-02 10:56:11 -0500633
Brad Bishop87b63c12016-03-18 14:47:51 -0400634 def invalidate_session(self, session):
635 try:
636 self.session_store.remove(session)
637 except ValueError:
638 pass
Brad Bishop2f428582015-12-02 10:56:11 -0500639
Brad Bishop87b63c12016-03-18 14:47:51 -0400640 def new_session(self):
641 sid = os.urandom(32)
642 if self.MAX_SESSIONS <= len(self.session_store):
643 self.session_store.pop()
644 self.session_store.insert(0, {'sid': sid})
Brad Bishop2f428582015-12-02 10:56:11 -0500645
Brad Bishop87b63c12016-03-18 14:47:51 -0400646 return self.session_store[0]
Brad Bishop2f428582015-12-02 10:56:11 -0500647
Brad Bishop87b63c12016-03-18 14:47:51 -0400648 def get_session(self, sid):
649 sids = [x['sid'] for x in self.session_store]
650 try:
651 return self.session_store[sids.index(sid)]
652 except ValueError:
653 return None
Brad Bishop2f428582015-12-02 10:56:11 -0500654
Brad Bishop87b63c12016-03-18 14:47:51 -0400655 def get_session_from_cookie(self):
656 return self.get_session(
657 request.get_cookie(
658 'sid', secret=self.hmac_key))
Brad Bishop2f428582015-12-02 10:56:11 -0500659
Brad Bishop87b63c12016-03-18 14:47:51 -0400660 def do_post(self, **kw):
661 if request.path == '/login':
662 return self.do_login(**kw)
663 else:
664 return self.do_logout(**kw)
Brad Bishop2f428582015-12-02 10:56:11 -0500665
Brad Bishop87b63c12016-03-18 14:47:51 -0400666 def do_logout(self, **kw):
667 session = self.get_session_from_cookie()
668 if session is not None:
669 user = session['user']
670 self.invalidate_session(session)
671 response.delete_cookie('sid')
672 return self.login_str % (user, 'out')
Brad Bishop2f428582015-12-02 10:56:11 -0500673
Brad Bishop87b63c12016-03-18 14:47:51 -0400674 return self.no_user_str
Brad Bishop2f428582015-12-02 10:56:11 -0500675
Brad Bishop87b63c12016-03-18 14:47:51 -0400676 def do_login(self, **kw):
677 session = self.get_session_from_cookie()
678 if session is not None:
679 return self.login_str % (session['user'], 'in')
Brad Bishop2f428582015-12-02 10:56:11 -0500680
Brad Bishop87b63c12016-03-18 14:47:51 -0400681 if len(request.parameter_list) != 2:
682 abort(400, self.bad_json_str % (request.json))
Brad Bishop2f428582015-12-02 10:56:11 -0500683
Brad Bishop87b63c12016-03-18 14:47:51 -0400684 if not self.authenticate(*request.parameter_list):
Brad Bishopdc3fbfa2016-09-08 09:51:38 -0400685 abort(401, self.bad_passwd_str)
Brad Bishop2f428582015-12-02 10:56:11 -0500686
Brad Bishop87b63c12016-03-18 14:47:51 -0400687 user = request.parameter_list[0]
688 session = self.new_session()
689 session['user'] = user
690 response.set_cookie(
691 'sid', session['sid'], secret=self.hmac_key,
692 secure=True,
693 httponly=True)
694 return self.login_str % (user, 'in')
Brad Bishop2f428582015-12-02 10:56:11 -0500695
Brad Bishop87b63c12016-03-18 14:47:51 -0400696 def find(self, **kw):
697 pass
Brad Bishop2f428582015-12-02 10:56:11 -0500698
Brad Bishop87b63c12016-03-18 14:47:51 -0400699 def setup(self, **kw):
700 pass
701
Brad Bishop2f428582015-12-02 10:56:11 -0500702
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500703class ImageUploadUtils:
704 ''' Provides common utils for image upload. '''
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500705
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500706 file_loc = '/tmp/images'
707 file_prefix = 'img'
708 file_suffix = ''
709
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500710 @classmethod
711 def do_upload(cls, filename=''):
712 if not os.path.exists(cls.file_loc):
Gunnar Millsfb515792017-11-09 15:52:17 -0600713 abort(500, "Error Directory not found")
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500714 if not filename:
715 handle, filename = tempfile.mkstemp(cls.file_suffix,
716 cls.file_prefix, cls.file_loc)
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500717 else:
718 filename = os.path.join(cls.file_loc, filename)
Gunnar Millsb66b18c2017-08-21 16:17:21 -0500719 handle = os.open(filename, os.O_WRONLY | os.O_CREAT)
Leonel Gonzalez0b62edf2017-06-08 15:10:03 -0500720 try:
721 file_contents = request.body.read()
722 request.body.close()
Gunnar Millsb66b18c2017-08-21 16:17:21 -0500723 os.write(handle, file_contents)
Leonel Gonzalez0b62edf2017-06-08 15:10:03 -0500724 except (IOError, ValueError), e:
725 abort(400, str(e))
726 except:
727 abort(400, "Unexpected Error")
Gunnar Millsb66b18c2017-08-21 16:17:21 -0500728 finally:
729 os.close(handle)
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500730
731
732class ImagePostHandler(RouteHandler):
733 ''' Handles the /upload/image route. '''
734
735 verbs = ['POST']
736 rules = ['/upload/image']
737 content_type = 'application/octet-stream'
738
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500739 def __init__(self, app, bus):
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500740 super(ImagePostHandler, self).__init__(
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500741 app, bus, self.verbs, self.rules, self.content_type)
742
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500743 def do_post(self, filename=''):
744 ImageUploadUtils.do_upload()
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500745
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500746 def find(self, **kw):
747 pass
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500748
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500749 def setup(self, **kw):
750 pass
751
752
Deepak Kodihalli639b5022017-10-13 06:40:26 -0500753class EventNotifier:
754 keyNames = {}
755 keyNames['event'] = 'event'
756 keyNames['path'] = 'path'
757 keyNames['intfMap'] = 'interfaces'
758 keyNames['propMap'] = 'properties'
759 keyNames['intf'] = 'interface'
760
761 def __init__(self, wsock, filters):
762 self.wsock = wsock
763 self.paths = filters.get("paths", [])
764 self.interfaces = filters.get("interfaces", [])
765 if not self.paths:
766 self.paths.append(None)
767 bus = dbus.SystemBus()
768 # Add a signal receiver for every path the client is interested in
769 for path in self.paths:
770 bus.add_signal_receiver(
771 self.interfaces_added_handler,
772 dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager',
773 signal_name='InterfacesAdded',
774 path=path)
775 bus.add_signal_receiver(
776 self.properties_changed_handler,
777 dbus_interface=dbus.PROPERTIES_IFACE,
778 signal_name='PropertiesChanged',
779 path=path,
780 path_keyword='path')
781 loop = gobject.MainLoop()
782 # gobject's mainloop.run() will block the entire process, so the gevent
783 # scheduler and hence greenlets won't execute. The while-loop below
784 # works around this limitation by using gevent's sleep, instead of
785 # calling loop.run()
786 gcontext = loop.get_context()
787 while loop is not None:
788 try:
789 if gcontext.pending():
790 gcontext.iteration()
791 else:
792 # gevent.sleep puts only the current greenlet to sleep,
793 # not the entire process.
794 gevent.sleep(5)
795 except WebSocketError:
796 break
797
798 def interfaces_added_handler(self, path, iprops, **kw):
799 ''' If the client is interested in these changes, respond to the
800 client. This handles d-bus interface additions.'''
801 if (not self.interfaces) or \
802 (not set(iprops).isdisjoint(self.interfaces)):
803 response = {}
804 response[self.keyNames['event']] = "InterfacesAdded"
805 response[self.keyNames['path']] = path
806 response[self.keyNames['intfMap']] = iprops
807 try:
808 self.wsock.send(json.dumps(response))
809 except WebSocketError:
810 return
811
812 def properties_changed_handler(self, interface, new, old, **kw):
813 ''' If the client is interested in these changes, respond to the
814 client. This handles d-bus property changes. '''
815 if (not self.interfaces) or (interface in self.interfaces):
816 path = str(kw['path'])
817 response = {}
818 response[self.keyNames['event']] = "PropertiesChanged"
819 response[self.keyNames['path']] = path
820 response[self.keyNames['intf']] = interface
821 response[self.keyNames['propMap']] = new
822 try:
823 self.wsock.send(json.dumps(response))
824 except WebSocketError:
825 return
826
827
Deepak Kodihallib209dd12017-10-11 01:19:17 -0500828class EventHandler(RouteHandler):
829 ''' Handles the /subscribe route, for clients to be able
830 to subscribe to BMC events. '''
831
832 verbs = ['GET']
833 rules = ['/subscribe']
834
835 def __init__(self, app, bus):
836 super(EventHandler, self).__init__(
837 app, bus, self.verbs, self.rules)
838
839 def find(self, **kw):
840 pass
841
842 def setup(self, **kw):
843 pass
844
845 def do_get(self):
846 wsock = request.environ.get('wsgi.websocket')
847 if not wsock:
848 abort(400, 'Expected WebSocket request.')
Deepak Kodihalli639b5022017-10-13 06:40:26 -0500849 filters = wsock.receive()
850 filters = json.loads(filters)
851 notifier = EventNotifier(wsock, filters)
Deepak Kodihallib209dd12017-10-11 01:19:17 -0500852
853
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500854class ImagePutHandler(RouteHandler):
855 ''' Handles the /upload/image/<filename> route. '''
856
857 verbs = ['PUT']
858 rules = ['/upload/image/<filename>']
859 content_type = 'application/octet-stream'
860
861 def __init__(self, app, bus):
862 super(ImagePutHandler, self).__init__(
863 app, bus, self.verbs, self.rules, self.content_type)
864
865 def do_put(self, filename=''):
866 ImageUploadUtils.do_upload(filename)
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500867
868 def find(self, **kw):
869 pass
870
871 def setup(self, **kw):
872 pass
873
874
Jayanth Othayoth9bc94992017-06-29 06:30:40 -0500875class DownloadDumpHandler(RouteHandler):
876 ''' Handles the /download/dump route. '''
877
878 verbs = 'GET'
879 rules = ['/download/dump/<dumpid>']
880 content_type = 'application/octet-stream'
Jayanth Othayoth18c3a242017-08-02 08:16:11 -0500881 dump_loc = '/var/lib/phosphor-debug-collector/dumps'
Brad Bishop944cd042017-07-10 16:42:41 -0400882 suppress_json_resp = True
Jayanth Othayoth9bc94992017-06-29 06:30:40 -0500883
884 def __init__(self, app, bus):
885 super(DownloadDumpHandler, self).__init__(
886 app, bus, self.verbs, self.rules, self.content_type)
887
888 def do_get(self, dumpid):
889 return self.do_download(dumpid)
890
891 def find(self, **kw):
892 pass
893
894 def setup(self, **kw):
895 pass
896
897 def do_download(self, dumpid):
898 dump_loc = os.path.join(self.dump_loc, dumpid)
899 if not os.path.exists(dump_loc):
900 abort(404, "Path not found")
901
902 files = os.listdir(dump_loc)
903 num_files = len(files)
904 if num_files == 0:
905 abort(404, "Dump not found")
906
907 return static_file(os.path.basename(files[0]), root=dump_loc,
908 download=True, mimetype=self.content_type)
909
910
Matt Spinlerd41643e2018-02-02 13:51:38 -0600911class WebHandler(RouteHandler):
912 ''' Handles the routes for the web UI files. '''
913
914 verbs = 'GET'
915
916 # Match only what we know are web files, so everything else
917 # can get routed to the REST handlers.
918 rules = ['//', '/<filename:re:.+\.js>', '/<filename:re:.+\.svg>',
919 '/<filename:re:.+\.css>', '/<filename:re:.+\.ttf>',
920 '/<filename:re:.+\.eot>', '/<filename:re:.+\.woff>',
921 '/<filename:re:.+\.woff2>', '/<filename:re:.+\.map>',
922 '/<filename:re:.+\.png>', '/<filename:re:.+\.html>',
923 '/<filename:re:.+\.ico>']
924
925 # The mimetypes module knows about most types, but not these
926 content_types = {
927 '.eot': 'application/vnd.ms-fontobject',
928 '.woff': 'application/x-font-woff',
929 '.woff2': 'application/x-font-woff2',
930 '.ttf': 'application/x-font-ttf',
931 '.map': 'application/json'
932 }
933
934 _require_auth = None
935 suppress_json_resp = True
936
937 def __init__(self, app, bus):
938 super(WebHandler, self).__init__(
939 app, bus, self.verbs, self.rules)
940
941 def get_type(self, filename):
942 ''' Returns the content type and encoding for a file '''
943
944 content_type, encoding = mimetypes.guess_type(filename)
945
946 # Try our own list if mimetypes didn't recognize it
947 if content_type is None:
948 if filename[-3:] == '.gz':
949 filename = filename[:-3]
950 extension = filename[filename.rfind('.'):]
951 content_type = self.content_types.get(extension, None)
952
953 return content_type, encoding
954
955 def do_get(self, filename='index.html'):
956
957 # If a gzipped version exists, use that instead.
958 # Possible future enhancement: if the client doesn't
959 # accept compressed files, unzip it ourselves before sending.
960 if not os.path.exists(os.path.join(www_base_path, filename)):
961 filename = filename + '.gz'
962
963 # Though bottle should protect us, ensure path is valid
964 realpath = os.path.realpath(filename)
965 if realpath[0] == '/':
966 realpath = realpath[1:]
967 if not os.path.exists(os.path.join(www_base_path, realpath)):
968 abort(404, "Path not found")
969
970 mimetype, encoding = self.get_type(filename)
971
972 # Couldn't find the type - let static_file() deal with it,
973 # though this should never happen.
974 if mimetype is None:
975 print("Can't figure out content-type for %s" % filename)
976 mimetype = 'auto'
977
978 # This call will set several header fields for us,
979 # including the charset if the type is text.
980 response = static_file(filename, www_base_path, mimetype)
981
982 # static_file() will only set the encoding if the
983 # mimetype was auto, so set it here.
984 if encoding is not None:
985 response.set_header('Content-Encoding', encoding)
986
987 return response
988
989 def find(self, **kw):
990 pass
991
992 def setup(self, **kw):
993 pass
994
995
Brad Bishop2f428582015-12-02 10:56:11 -0500996class AuthorizationPlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -0400997 ''' Invokes an optional list of authorization callbacks. '''
Brad Bishop2f428582015-12-02 10:56:11 -0500998
Brad Bishop87b63c12016-03-18 14:47:51 -0400999 name = 'authorization'
1000 api = 2
Brad Bishop2f428582015-12-02 10:56:11 -05001001
Brad Bishop87b63c12016-03-18 14:47:51 -04001002 class Compose:
1003 def __init__(self, validators, callback, session_mgr):
1004 self.validators = validators
1005 self.callback = callback
1006 self.session_mgr = session_mgr
Brad Bishop2f428582015-12-02 10:56:11 -05001007
Brad Bishop87b63c12016-03-18 14:47:51 -04001008 def __call__(self, *a, **kw):
1009 sid = request.get_cookie('sid', secret=self.session_mgr.hmac_key)
1010 session = self.session_mgr.get_session(sid)
Brad Bishopd4c1c552017-02-21 00:07:28 -05001011 if request.method != 'OPTIONS':
1012 for x in self.validators:
1013 x(session, *a, **kw)
Brad Bishop2f428582015-12-02 10:56:11 -05001014
Brad Bishop87b63c12016-03-18 14:47:51 -04001015 return self.callback(*a, **kw)
Brad Bishop2f428582015-12-02 10:56:11 -05001016
Brad Bishop87b63c12016-03-18 14:47:51 -04001017 def apply(self, callback, route):
1018 undecorated = route.get_undecorated_callback()
1019 if not isinstance(undecorated, RouteHandler):
1020 return callback
Brad Bishop2f428582015-12-02 10:56:11 -05001021
Brad Bishop87b63c12016-03-18 14:47:51 -04001022 auth_types = getattr(
1023 undecorated, '_require_auth', None)
1024 if not auth_types:
1025 return callback
Brad Bishop2f428582015-12-02 10:56:11 -05001026
Brad Bishop87b63c12016-03-18 14:47:51 -04001027 return self.Compose(
1028 auth_types, callback, undecorated.app.session_handler)
1029
Brad Bishop2f428582015-12-02 10:56:11 -05001030
Brad Bishopd0c404a2017-02-21 09:23:25 -05001031class CorsPlugin(object):
1032 ''' Add CORS headers. '''
1033
1034 name = 'cors'
1035 api = 2
1036
1037 @staticmethod
1038 def process_origin():
1039 origin = request.headers.get('Origin')
1040 if origin:
1041 response.add_header('Access-Control-Allow-Origin', origin)
1042 response.add_header(
1043 'Access-Control-Allow-Credentials', 'true')
1044
1045 @staticmethod
1046 def process_method_and_headers(verbs):
1047 method = request.headers.get('Access-Control-Request-Method')
1048 headers = request.headers.get('Access-Control-Request-Headers')
1049 if headers:
1050 headers = [x.lower() for x in headers.split(',')]
1051
1052 if method in verbs \
1053 and headers == ['content-type']:
1054 response.add_header('Access-Control-Allow-Methods', method)
1055 response.add_header(
1056 'Access-Control-Allow-Headers', 'Content-Type')
Ratan Gupta91b46f82018-01-14 12:52:23 +05301057 response.add_header('X-Frame-Options', 'deny')
1058 response.add_header('X-Content-Type-Options', 'nosniff')
1059 response.add_header('X-XSS-Protection', '1; mode=block')
1060 response.add_header(
1061 'Content-Security-Policy', "default-src 'self'")
1062 response.add_header(
1063 'Strict-Transport-Security',
1064 'max-age=31536000; includeSubDomains; preload')
Brad Bishopd0c404a2017-02-21 09:23:25 -05001065
1066 def __init__(self, app):
1067 app.install_error_callback(self.error_callback)
1068
1069 def apply(self, callback, route):
1070 undecorated = route.get_undecorated_callback()
1071 if not isinstance(undecorated, RouteHandler):
1072 return callback
1073
1074 if not getattr(undecorated, '_enable_cors', None):
1075 return callback
1076
1077 def wrap(*a, **kw):
1078 self.process_origin()
1079 self.process_method_and_headers(undecorated._verbs)
1080 return callback(*a, **kw)
1081
1082 return wrap
1083
1084 def error_callback(self, **kw):
1085 self.process_origin()
1086
1087
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001088class JsonApiRequestPlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -04001089 ''' Ensures request content satisfies the OpenBMC json api format. '''
1090 name = 'json_api_request'
1091 api = 2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001092
Brad Bishop87b63c12016-03-18 14:47:51 -04001093 error_str = "Expecting request format { 'data': <value> }, got '%s'"
1094 type_error_str = "Unsupported Content-Type: '%s'"
1095 json_type = "application/json"
1096 request_methods = ['PUT', 'POST', 'PATCH']
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001097
Brad Bishop87b63c12016-03-18 14:47:51 -04001098 @staticmethod
1099 def content_expected():
1100 return request.method in JsonApiRequestPlugin.request_methods
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001101
Brad Bishop87b63c12016-03-18 14:47:51 -04001102 def validate_request(self):
1103 if request.content_length > 0 and \
1104 request.content_type != self.json_type:
1105 abort(415, self.type_error_str % request.content_type)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001106
Brad Bishop87b63c12016-03-18 14:47:51 -04001107 try:
1108 request.parameter_list = request.json.get('data')
1109 except ValueError, e:
1110 abort(400, str(e))
1111 except (AttributeError, KeyError, TypeError):
1112 abort(400, self.error_str % request.json)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001113
Brad Bishop87b63c12016-03-18 14:47:51 -04001114 def apply(self, callback, route):
Deepak Kodihallifb6cd482017-04-10 07:27:09 -05001115 content_type = getattr(
1116 route.get_undecorated_callback(), '_content_type', None)
1117 if self.json_type != content_type:
1118 return callback
1119
Brad Bishop87b63c12016-03-18 14:47:51 -04001120 verbs = getattr(
1121 route.get_undecorated_callback(), '_verbs', None)
1122 if verbs is None:
1123 return callback
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001124
Brad Bishop87b63c12016-03-18 14:47:51 -04001125 if not set(self.request_methods).intersection(verbs):
1126 return callback
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001127
Brad Bishop87b63c12016-03-18 14:47:51 -04001128 def wrap(*a, **kw):
1129 if self.content_expected():
1130 self.validate_request()
1131 return callback(*a, **kw)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001132
Brad Bishop87b63c12016-03-18 14:47:51 -04001133 return wrap
1134
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001135
1136class JsonApiRequestTypePlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -04001137 ''' Ensures request content type satisfies the OpenBMC json api format. '''
1138 name = 'json_api_method_request'
1139 api = 2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001140
Brad Bishop87b63c12016-03-18 14:47:51 -04001141 error_str = "Expecting request format { 'data': %s }, got '%s'"
Deepak Kodihallifb6cd482017-04-10 07:27:09 -05001142 json_type = "application/json"
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001143
Brad Bishop87b63c12016-03-18 14:47:51 -04001144 def apply(self, callback, route):
Deepak Kodihallifb6cd482017-04-10 07:27:09 -05001145 content_type = getattr(
1146 route.get_undecorated_callback(), '_content_type', None)
1147 if self.json_type != content_type:
1148 return callback
1149
Brad Bishop87b63c12016-03-18 14:47:51 -04001150 request_type = getattr(
1151 route.get_undecorated_callback(), 'request_type', None)
1152 if request_type is None:
1153 return callback
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001154
Brad Bishop87b63c12016-03-18 14:47:51 -04001155 def validate_request():
1156 if not isinstance(request.parameter_list, request_type):
1157 abort(400, self.error_str % (str(request_type), request.json))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001158
Brad Bishop87b63c12016-03-18 14:47:51 -04001159 def wrap(*a, **kw):
1160 if JsonApiRequestPlugin.content_expected():
1161 validate_request()
1162 return callback(*a, **kw)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001163
Brad Bishop87b63c12016-03-18 14:47:51 -04001164 return wrap
1165
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001166
Brad Bishop080a48e2017-02-21 22:34:43 -05001167class JsonErrorsPlugin(JSONPlugin):
1168 ''' Extend the Bottle JSONPlugin such that it also encodes error
1169 responses. '''
1170
1171 def __init__(self, app, **kw):
1172 super(JsonErrorsPlugin, self).__init__(**kw)
1173 self.json_opts = {
1174 x: y for x, y in kw.iteritems()
1175 if x in ['indent', 'sort_keys']}
1176 app.install_error_callback(self.error_callback)
1177
1178 def error_callback(self, response_object, response_body, **kw):
1179 response_body['body'] = json.dumps(response_object, **self.json_opts)
1180 response.content_type = 'application/json'
1181
1182
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001183class JsonApiResponsePlugin(object):
Brad Bishop080a48e2017-02-21 22:34:43 -05001184 ''' Emits responses in the OpenBMC json api format. '''
Brad Bishop87b63c12016-03-18 14:47:51 -04001185 name = 'json_api_response'
1186 api = 2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001187
Brad Bishopd4c1c552017-02-21 00:07:28 -05001188 @staticmethod
1189 def has_body():
1190 return request.method not in ['OPTIONS']
1191
Brad Bishop080a48e2017-02-21 22:34:43 -05001192 def __init__(self, app):
1193 app.install_error_callback(self.error_callback)
1194
Brad Bishop87b63c12016-03-18 14:47:51 -04001195 def apply(self, callback, route):
Brad Bishop944cd042017-07-10 16:42:41 -04001196 skip = getattr(
1197 route.get_undecorated_callback(), 'suppress_json_resp', None)
1198 if skip:
Jayanth Othayoth1444fd82017-06-29 05:45:07 -05001199 return callback
1200
Brad Bishop87b63c12016-03-18 14:47:51 -04001201 def wrap(*a, **kw):
Brad Bishopd4c1c552017-02-21 00:07:28 -05001202 data = callback(*a, **kw)
1203 if self.has_body():
1204 resp = {'data': data}
1205 resp['status'] = 'ok'
1206 resp['message'] = response.status_line
1207 return resp
Brad Bishop87b63c12016-03-18 14:47:51 -04001208 return wrap
1209
Brad Bishop080a48e2017-02-21 22:34:43 -05001210 def error_callback(self, error, response_object, **kw):
Brad Bishop87b63c12016-03-18 14:47:51 -04001211 response_object['message'] = error.status_line
Brad Bishop9c2531e2017-03-07 10:22:40 -05001212 response_object['status'] = 'error'
Brad Bishop080a48e2017-02-21 22:34:43 -05001213 response_object.setdefault('data', {})['description'] = str(error.body)
Brad Bishop87b63c12016-03-18 14:47:51 -04001214 if error.status_code == 500:
1215 response_object['data']['exception'] = repr(error.exception)
1216 response_object['data']['traceback'] = error.traceback.splitlines()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001217
Brad Bishop87b63c12016-03-18 14:47:51 -04001218
Brad Bishop080a48e2017-02-21 22:34:43 -05001219class JsonpPlugin(object):
Brad Bishop80fe37a2016-03-29 10:54:54 -04001220 ''' Json javascript wrapper. '''
1221 name = 'jsonp'
1222 api = 2
1223
Brad Bishop080a48e2017-02-21 22:34:43 -05001224 def __init__(self, app, **kw):
1225 app.install_error_callback(self.error_callback)
Brad Bishop80fe37a2016-03-29 10:54:54 -04001226
1227 @staticmethod
1228 def to_jsonp(json):
1229 jwrapper = request.query.callback or None
1230 if(jwrapper):
1231 response.set_header('Content-Type', 'application/javascript')
1232 json = jwrapper + '(' + json + ');'
1233 return json
1234
1235 def apply(self, callback, route):
1236 def wrap(*a, **kw):
1237 return self.to_jsonp(callback(*a, **kw))
1238 return wrap
1239
Brad Bishop080a48e2017-02-21 22:34:43 -05001240 def error_callback(self, response_body, **kw):
1241 response_body['body'] = self.to_jsonp(response_body['body'])
Brad Bishop80fe37a2016-03-29 10:54:54 -04001242
1243
Deepak Kodihalli461367a2017-04-10 07:11:38 -05001244class ContentCheckerPlugin(object):
1245 ''' Ensures that a route is associated with the expected content-type
1246 header. '''
1247 name = 'content_checker'
1248 api = 2
1249
1250 class Checker:
1251 def __init__(self, type, callback):
1252 self.expected_type = type
1253 self.callback = callback
1254 self.error_str = "Expecting content type '%s', got '%s'"
1255
1256 def __call__(self, *a, **kw):
Deepak Kodihallidb1a21e2017-04-27 06:30:11 -05001257 if request.method in ['PUT', 'POST', 'PATCH'] and \
1258 self.expected_type and \
Deepak Kodihalli461367a2017-04-10 07:11:38 -05001259 self.expected_type != request.content_type:
1260 abort(415, self.error_str % (self.expected_type,
1261 request.content_type))
1262
1263 return self.callback(*a, **kw)
1264
1265 def apply(self, callback, route):
1266 content_type = getattr(
1267 route.get_undecorated_callback(), '_content_type', None)
1268
1269 return self.Checker(content_type, callback)
1270
1271
Brad Bishop2c6fc762016-08-29 15:53:25 -04001272class App(Bottle):
Deepak Kodihalli0fe213f2017-10-11 00:08:48 -05001273 def __init__(self, **kw):
Brad Bishop2c6fc762016-08-29 15:53:25 -04001274 super(App, self).__init__(autojson=False)
Deepak Kodihallib209dd12017-10-11 01:19:17 -05001275
1276 self.have_wsock = kw.get('have_wsock', False)
1277
Brad Bishop2ddfa002016-08-29 15:11:55 -04001278 self.bus = dbus.SystemBus()
1279 self.mapper = obmc.mapper.Mapper(self.bus)
Brad Bishop080a48e2017-02-21 22:34:43 -05001280 self.error_callbacks = []
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001281
Brad Bishop87b63c12016-03-18 14:47:51 -04001282 self.install_hooks()
1283 self.install_plugins()
1284 self.create_handlers()
1285 self.install_handlers()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001286
Brad Bishop87b63c12016-03-18 14:47:51 -04001287 def install_plugins(self):
1288 # install json api plugins
1289 json_kw = {'indent': 2, 'sort_keys': True}
Brad Bishop87b63c12016-03-18 14:47:51 -04001290 self.install(AuthorizationPlugin())
Brad Bishopd0c404a2017-02-21 09:23:25 -05001291 self.install(CorsPlugin(self))
Deepak Kodihalli461367a2017-04-10 07:11:38 -05001292 self.install(ContentCheckerPlugin())
Brad Bishop080a48e2017-02-21 22:34:43 -05001293 self.install(JsonpPlugin(self, **json_kw))
1294 self.install(JsonErrorsPlugin(self, **json_kw))
1295 self.install(JsonApiResponsePlugin(self))
Brad Bishop87b63c12016-03-18 14:47:51 -04001296 self.install(JsonApiRequestPlugin())
1297 self.install(JsonApiRequestTypePlugin())
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001298
Brad Bishop87b63c12016-03-18 14:47:51 -04001299 def install_hooks(self):
Brad Bishop080a48e2017-02-21 22:34:43 -05001300 self.error_handler_type = type(self.default_error_handler)
1301 self.original_error_handler = self.default_error_handler
1302 self.default_error_handler = self.error_handler_type(
1303 self.custom_error_handler, self, Bottle)
1304
Brad Bishop87b63c12016-03-18 14:47:51 -04001305 self.real_router_match = self.router.match
1306 self.router.match = self.custom_router_match
1307 self.add_hook('before_request', self.strip_extra_slashes)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001308
Brad Bishop87b63c12016-03-18 14:47:51 -04001309 def create_handlers(self):
1310 # create route handlers
1311 self.session_handler = SessionHandler(self, self.bus)
Matt Spinlerd41643e2018-02-02 13:51:38 -06001312 self.web_handler = WebHandler(self, self.bus)
Brad Bishop87b63c12016-03-18 14:47:51 -04001313 self.directory_handler = DirectoryHandler(self, self.bus)
1314 self.list_names_handler = ListNamesHandler(self, self.bus)
1315 self.list_handler = ListHandler(self, self.bus)
1316 self.method_handler = MethodHandler(self, self.bus)
1317 self.property_handler = PropertyHandler(self, self.bus)
1318 self.schema_handler = SchemaHandler(self, self.bus)
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -05001319 self.image_upload_post_handler = ImagePostHandler(self, self.bus)
1320 self.image_upload_put_handler = ImagePutHandler(self, self.bus)
Jayanth Othayoth9bc94992017-06-29 06:30:40 -05001321 self.download_dump_get_handler = DownloadDumpHandler(self, self.bus)
Deepak Kodihallib209dd12017-10-11 01:19:17 -05001322 if self.have_wsock:
1323 self.event_handler = EventHandler(self, self.bus)
Brad Bishop87b63c12016-03-18 14:47:51 -04001324 self.instance_handler = InstanceHandler(self, self.bus)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001325
Brad Bishop87b63c12016-03-18 14:47:51 -04001326 def install_handlers(self):
1327 self.session_handler.install()
Matt Spinlerd41643e2018-02-02 13:51:38 -06001328 self.web_handler.install()
Brad Bishop87b63c12016-03-18 14:47:51 -04001329 self.directory_handler.install()
1330 self.list_names_handler.install()
1331 self.list_handler.install()
1332 self.method_handler.install()
1333 self.property_handler.install()
1334 self.schema_handler.install()
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -05001335 self.image_upload_post_handler.install()
1336 self.image_upload_put_handler.install()
Jayanth Othayoth9bc94992017-06-29 06:30:40 -05001337 self.download_dump_get_handler.install()
Deepak Kodihallib209dd12017-10-11 01:19:17 -05001338 if self.have_wsock:
1339 self.event_handler.install()
Brad Bishop87b63c12016-03-18 14:47:51 -04001340 # this has to come last, since it matches everything
1341 self.instance_handler.install()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001342
Brad Bishop080a48e2017-02-21 22:34:43 -05001343 def install_error_callback(self, callback):
1344 self.error_callbacks.insert(0, callback)
1345
Brad Bishop87b63c12016-03-18 14:47:51 -04001346 def custom_router_match(self, environ):
1347 ''' The built-in Bottle algorithm for figuring out if a 404 or 405 is
1348 needed doesn't work for us since the instance rules match
1349 everything. This monkey-patch lets the route handler figure
1350 out which response is needed. This could be accomplished
1351 with a hook but that would require calling the router match
1352 function twice.
1353 '''
1354 route, args = self.real_router_match(environ)
1355 if isinstance(route.callback, RouteHandler):
1356 route.callback._setup(**args)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001357
Brad Bishop87b63c12016-03-18 14:47:51 -04001358 return route, args
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001359
Brad Bishop080a48e2017-02-21 22:34:43 -05001360 def custom_error_handler(self, res, error):
Gunnar Millsf01d0ba2017-10-25 20:37:24 -05001361 ''' Allow plugins to modify error responses too via this custom
Brad Bishop080a48e2017-02-21 22:34:43 -05001362 error handler. '''
1363
1364 response_object = {}
1365 response_body = {}
1366 for x in self.error_callbacks:
1367 x(error=error,
1368 response_object=response_object,
1369 response_body=response_body)
1370
1371 return response_body.get('body', "")
1372
Brad Bishop87b63c12016-03-18 14:47:51 -04001373 @staticmethod
1374 def strip_extra_slashes():
1375 path = request.environ['PATH_INFO']
1376 trailing = ("", "/")[path[-1] == '/']
1377 parts = filter(bool, path.split('/'))
1378 request.environ['PATH_INFO'] = '/' + '/'.join(parts) + trailing