blob: 8df9805c29b7ac0da6c979f9b0ea80208aeeba2b [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
Alexander Filippovd08a4562018-03-20 12:02:23 +030018import sys
Brad Bishopaa65f6e2015-10-27 16:28:51 -040019import dbus
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050020import dbus.exceptions
21import json
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050022from xml.etree import ElementTree
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050023from bottle import Bottle, abort, request, response, JSONPlugin, HTTPError
Jayanth Othayoth9bc94992017-06-29 06:30:40 -050024from bottle import static_file
Brad Bishopb103d2d2016-03-04 16:19:14 -050025import obmc.utils.misc
Brad Bishopb103d2d2016-03-04 16:19:14 -050026from obmc.dbuslib.introspection import IntrospectionNodeParser
27import obmc.mapper
Brad Bishop2f428582015-12-02 10:56:11 -050028import spwd
29import grp
30import crypt
Deepak Kodihalli1af301a2017-04-11 07:29:01 -050031import tempfile
Leonel Gonzalez0bdef952017-04-18 08:17:49 -050032import re
Matt Spinlerd41643e2018-02-02 13:51:38 -060033import mimetypes
Deepak Kodihalli639b5022017-10-13 06:40:26 -050034have_wsock = True
35try:
36 from geventwebsocket import WebSocketError
37except ImportError:
38 have_wsock = False
39if have_wsock:
40 from dbus.mainloop.glib import DBusGMainLoop
41 DBusGMainLoop(set_as_default=True)
CamVan Nguyen249d1322018-03-05 10:08:33 -060042 # TODO: openbmc/openbmc#2994 remove python 2 support
43 try: # python 2
44 import gobject
45 except ImportError: # python 3
46 from gi.repository import GObject as gobject
Deepak Kodihalli639b5022017-10-13 06:40:26 -050047 import gevent
Deepak Kodihalli5c518f62018-04-23 03:26:38 -050048 from gevent import socket
49 from gevent import Greenlet
Brad Bishopaa65f6e2015-10-27 16:28:51 -040050
Adriana Kobylakf92cf4d2017-12-13 11:46:50 -060051DBUS_UNKNOWN_INTERFACE = 'org.freedesktop.DBus.Error.UnknownInterface'
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050052DBUS_UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod'
Adriana Kobylaka8b05d12018-08-23 10:44:07 -050053DBUS_PROPERTY_READONLY = 'org.freedesktop.DBus.Error.PropertyReadOnly'
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050054DBUS_INVALID_ARGS = 'org.freedesktop.DBus.Error.InvalidArgs'
Brad Bishopd4578922015-12-02 11:10:36 -050055DBUS_TYPE_ERROR = 'org.freedesktop.DBus.Python.TypeError'
Deepak Kodihalli6075bb42017-04-04 05:49:17 -050056DELETE_IFACE = 'xyz.openbmc_project.Object.Delete'
Adriana Kobylak53693892018-03-12 13:05:50 -050057SOFTWARE_PATH = '/xyz/openbmc_project/software'
Jayashankar Padathbec10c22018-05-29 18:22:59 +053058WEBSOCKET_TIMEOUT = 45
Brad Bishop9ee57c42015-11-03 14:59:29 -050059
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050060_4034_msg = "The specified %s cannot be %s: '%s'"
Brad Bishopaa65f6e2015-10-27 16:28:51 -040061
Matt Spinlerd41643e2018-02-02 13:51:38 -060062www_base_path = '/usr/share/www/'
63
Brad Bishop87b63c12016-03-18 14:47:51 -040064
Brad Bishop2f428582015-12-02 10:56:11 -050065def valid_user(session, *a, **kw):
Brad Bishop87b63c12016-03-18 14:47:51 -040066 ''' Authorization plugin callback that checks
67 that the user is logged in. '''
68 if session is None:
Brad Bishopdc3fbfa2016-09-08 09:51:38 -040069 abort(401, 'Login required')
Brad Bishop87b63c12016-03-18 14:47:51 -040070
Brad Bishop2f428582015-12-02 10:56:11 -050071
Leonel Gonzalez0bdef952017-04-18 08:17:49 -050072def get_type_signature_by_introspection(bus, service, object_path,
73 property_name):
74 obj = bus.get_object(service, object_path)
75 iface = dbus.Interface(obj, 'org.freedesktop.DBus.Introspectable')
76 xml_string = iface.Introspect()
77 for child in ElementTree.fromstring(xml_string):
78 # Iterate over each interfaces's properties to find
79 # matching property_name, and return its signature string
80 if child.tag == 'interface':
81 for i in child.iter():
82 if ('name' in i.attrib) and \
83 (i.attrib['name'] == property_name):
84 type_signature = i.attrib['type']
85 return type_signature
86
87
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +053088def get_method_signature(bus, service, object_path, interface, method):
89 obj = bus.get_object(service, object_path)
90 iface = dbus.Interface(obj, 'org.freedesktop.DBus.Introspectable')
91 xml_string = iface.Introspect()
92 arglist = []
93
94 root = ElementTree.fromstring(xml_string)
95 for dbus_intf in root.findall('interface'):
96 if (dbus_intf.get('name') == interface):
97 for dbus_method in dbus_intf.findall('method'):
98 if(dbus_method.get('name') == method):
99 for arg in dbus_method.findall('arg'):
Ratan Gupta3c6d49a2019-01-16 13:58:34 +0530100 if (arg.get('direction') == 'in'):
101 arglist.append(arg.get('type'))
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530102 return arglist
103
104
Leonel Gonzalez0bdef952017-04-18 08:17:49 -0500105def split_struct_signature(signature):
106 struct_regex = r'(b|y|n|i|x|q|u|t|d|s|a\(.+?\)|\(.+?\))|a\{.+?\}+?'
107 struct_matches = re.findall(struct_regex, signature)
108 return struct_matches
109
110
111def convert_type(signature, value):
112 # Basic Types
113 converted_value = None
114 converted_container = None
CamVan Nguyen249d1322018-03-05 10:08:33 -0600115 # TODO: openbmc/openbmc#2994 remove python 2 support
116 try: # python 2
117 basic_types = {'b': bool, 'y': dbus.Byte, 'n': dbus.Int16, 'i': int,
118 'x': long, 'q': dbus.UInt16, 'u': dbus.UInt32,
119 't': dbus.UInt64, 'd': float, 's': str}
120 except NameError: # python 3
121 basic_types = {'b': bool, 'y': dbus.Byte, 'n': dbus.Int16, 'i': int,
122 'x': int, 'q': dbus.UInt16, 'u': dbus.UInt32,
123 't': dbus.UInt64, 'd': float, 's': str}
Leonel Gonzalez0bdef952017-04-18 08:17:49 -0500124 array_matches = re.match(r'a\((\S+)\)', signature)
125 struct_matches = re.match(r'\((\S+)\)', signature)
126 dictionary_matches = re.match(r'a{(\S+)}', signature)
127 if signature in basic_types:
128 converted_value = basic_types[signature](value)
129 return converted_value
130 # Array
131 if array_matches:
132 element_type = array_matches.group(1)
133 converted_container = list()
134 # Test if value is a list
135 # to avoid iterating over each character in a string.
136 # Iterate over each item and convert type
137 if isinstance(value, list):
138 for i in value:
139 converted_element = convert_type(element_type, i)
140 converted_container.append(converted_element)
141 # Convert non-sequence to expected type, and append to list
142 else:
143 converted_element = convert_type(element_type, value)
144 converted_container.append(converted_element)
145 return converted_container
146 # Struct
147 if struct_matches:
148 element_types = struct_matches.group(1)
149 split_element_types = split_struct_signature(element_types)
150 converted_container = list()
151 # Test if value is a list
152 if isinstance(value, list):
153 for index, val in enumerate(value):
154 converted_element = convert_type(split_element_types[index],
155 value[index])
156 converted_container.append(converted_element)
157 else:
158 converted_element = convert_type(element_types, value)
159 converted_container.append(converted_element)
160 return tuple(converted_container)
161 # Dictionary
162 if dictionary_matches:
163 element_types = dictionary_matches.group(1)
164 split_element_types = split_struct_signature(element_types)
165 converted_container = dict()
166 # Convert each element of dict
CamVan Nguyen249d1322018-03-05 10:08:33 -0600167 for key, val in value.items():
Leonel Gonzalez0bdef952017-04-18 08:17:49 -0500168 converted_key = convert_type(split_element_types[0], key)
169 converted_val = convert_type(split_element_types[1], val)
170 converted_container[converted_key] = converted_val
171 return converted_container
172
173
Jayashankar Padathbec10c22018-05-29 18:22:59 +0530174def send_ws_ping(wsock, timeout) :
175 # Most webservers close websockets after 60 seconds of
176 # inactivity. Make sure to send a ping before that.
177 payload = "ping"
178 # the ping payload can be anything, the receiver has to just
179 # return the same back.
180 while True:
181 gevent.sleep(timeout)
182 try:
183 if wsock:
184 wsock.send_frame(payload, wsock.OPCODE_PING)
185 except Exception as e:
186 wsock.close()
187 return
188
189
Brad Bishop2f428582015-12-02 10:56:11 -0500190class UserInGroup:
Brad Bishop87b63c12016-03-18 14:47:51 -0400191 ''' Authorization plugin callback that checks that the user is logged in
192 and a member of a group. '''
193 def __init__(self, group):
194 self.group = group
Brad Bishop2f428582015-12-02 10:56:11 -0500195
Brad Bishop87b63c12016-03-18 14:47:51 -0400196 def __call__(self, session, *a, **kw):
197 valid_user(session, *a, **kw)
198 res = False
Brad Bishop2f428582015-12-02 10:56:11 -0500199
Brad Bishop87b63c12016-03-18 14:47:51 -0400200 try:
201 res = session['user'] in grp.getgrnam(self.group)[3]
202 except KeyError:
203 pass
Brad Bishop2f428582015-12-02 10:56:11 -0500204
Brad Bishop87b63c12016-03-18 14:47:51 -0400205 if not res:
206 abort(403, 'Insufficient access')
207
Brad Bishop2f428582015-12-02 10:56:11 -0500208
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500209class RouteHandler(object):
Brad Bishop6d190602016-04-15 13:09:39 -0400210 _require_auth = obmc.utils.misc.makelist(valid_user)
Brad Bishopd0c404a2017-02-21 09:23:25 -0500211 _enable_cors = True
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400212
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500213 def __init__(self, app, bus, verbs, rules, content_type=''):
Brad Bishop87b63c12016-03-18 14:47:51 -0400214 self.app = app
215 self.bus = bus
Brad Bishopb103d2d2016-03-04 16:19:14 -0500216 self.mapper = obmc.mapper.Mapper(bus)
Brad Bishop6d190602016-04-15 13:09:39 -0400217 self._verbs = obmc.utils.misc.makelist(verbs)
Brad Bishop87b63c12016-03-18 14:47:51 -0400218 self._rules = rules
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500219 self._content_type = content_type
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400220
Brad Bishop88c76a42017-02-21 00:02:02 -0500221 if 'GET' in self._verbs:
222 self._verbs = list(set(self._verbs + ['HEAD']))
Brad Bishopd4c1c552017-02-21 00:07:28 -0500223 if 'OPTIONS' not in self._verbs:
224 self._verbs.append('OPTIONS')
Brad Bishop88c76a42017-02-21 00:02:02 -0500225
Brad Bishop87b63c12016-03-18 14:47:51 -0400226 def _setup(self, **kw):
227 request.route_data = {}
Brad Bishopd4c1c552017-02-21 00:07:28 -0500228
Brad Bishop87b63c12016-03-18 14:47:51 -0400229 if request.method in self._verbs:
Brad Bishopd4c1c552017-02-21 00:07:28 -0500230 if request.method != 'OPTIONS':
231 return self.setup(**kw)
Brad Bishop88c76a42017-02-21 00:02:02 -0500232
Brad Bishopd4c1c552017-02-21 00:07:28 -0500233 # Javascript implementations will not send credentials
234 # with an OPTIONS request. Don't help malicious clients
235 # by checking the path here and returning a 404 if the
236 # path doesn't exist.
237 return None
Brad Bishop88c76a42017-02-21 00:02:02 -0500238
Brad Bishopd4c1c552017-02-21 00:07:28 -0500239 # Return 405
Brad Bishop88c76a42017-02-21 00:02:02 -0500240 raise HTTPError(
241 405, "Method not allowed.", Allow=','.join(self._verbs))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400242
Brad Bishop87b63c12016-03-18 14:47:51 -0400243 def __call__(self, **kw):
244 return getattr(self, 'do_' + request.method.lower())(**kw)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400245
Brad Bishop88c76a42017-02-21 00:02:02 -0500246 def do_head(self, **kw):
247 return self.do_get(**kw)
248
Brad Bishopd4c1c552017-02-21 00:07:28 -0500249 def do_options(self, **kw):
250 for v in self._verbs:
251 response.set_header(
252 'Allow',
253 ','.join(self._verbs))
254 return None
255
Brad Bishop87b63c12016-03-18 14:47:51 -0400256 def install(self):
257 self.app.route(
258 self._rules, callback=self,
Brad Bishopd4c1c552017-02-21 00:07:28 -0500259 method=['OPTIONS', 'GET', 'PUT', 'PATCH', 'POST', 'DELETE'])
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400260
Brad Bishop87b63c12016-03-18 14:47:51 -0400261 @staticmethod
262 def try_mapper_call(f, callback=None, **kw):
263 try:
264 return f(**kw)
CamVan Nguyen249d1322018-03-05 10:08:33 -0600265 except dbus.exceptions.DBusException as e:
Brad Bishopfce77562016-11-28 15:44:18 -0500266 if e.get_dbus_name() == \
267 'org.freedesktop.DBus.Error.ObjectPathInUse':
268 abort(503, str(e))
Brad Bishopb103d2d2016-03-04 16:19:14 -0500269 if e.get_dbus_name() != obmc.mapper.MAPPER_NOT_FOUND:
Brad Bishop87b63c12016-03-18 14:47:51 -0400270 raise
271 if callback is None:
272 def callback(e, **kw):
273 abort(404, str(e))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400274
Brad Bishop87b63c12016-03-18 14:47:51 -0400275 callback(e, **kw)
276
277 @staticmethod
278 def try_properties_interface(f, *a):
279 try:
280 return f(*a)
CamVan Nguyen249d1322018-03-05 10:08:33 -0600281 except dbus.exceptions.DBusException as e:
Adriana Kobylakf92cf4d2017-12-13 11:46:50 -0600282 if DBUS_UNKNOWN_INTERFACE in e.get_dbus_name():
Brad Bishopf4e74982016-04-01 14:53:05 -0400283 # interface doesn't have any properties
284 return None
Brad Bishop87b63c12016-03-18 14:47:51 -0400285 if DBUS_UNKNOWN_METHOD == e.get_dbus_name():
286 # properties interface not implemented at all
287 return None
288 raise
289
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400290
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500291class DirectoryHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400292 verbs = 'GET'
293 rules = '<path:path>/'
Deepak Kodihalli6e1ca532018-09-04 03:51:04 -0500294 suppress_logging = True
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400295
Brad Bishop87b63c12016-03-18 14:47:51 -0400296 def __init__(self, app, bus):
297 super(DirectoryHandler, self).__init__(
Brad Bishopc431e1a2017-07-10 16:44:51 -0400298 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400299
Brad Bishop87b63c12016-03-18 14:47:51 -0400300 def find(self, path='/'):
301 return self.try_mapper_call(
302 self.mapper.get_subtree_paths, path=path, depth=1)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400303
Brad Bishop87b63c12016-03-18 14:47:51 -0400304 def setup(self, path='/'):
305 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400306
Brad Bishop87b63c12016-03-18 14:47:51 -0400307 def do_get(self, path='/'):
308 return request.route_data['map']
309
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400310
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500311class ListNamesHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400312 verbs = 'GET'
313 rules = ['/list', '<path:path>/list']
Deepak Kodihalli6e1ca532018-09-04 03:51:04 -0500314 suppress_logging = True
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400315
Brad Bishop87b63c12016-03-18 14:47:51 -0400316 def __init__(self, app, bus):
317 super(ListNamesHandler, self).__init__(
Brad Bishopc431e1a2017-07-10 16:44:51 -0400318 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400319
Brad Bishop87b63c12016-03-18 14:47:51 -0400320 def find(self, path='/'):
CamVan Nguyen249d1322018-03-05 10:08:33 -0600321 return list(self.try_mapper_call(
322 self.mapper.get_subtree, path=path).keys())
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400323
Brad Bishop87b63c12016-03-18 14:47:51 -0400324 def setup(self, path='/'):
325 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400326
Brad Bishop87b63c12016-03-18 14:47:51 -0400327 def do_get(self, path='/'):
328 return request.route_data['map']
329
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400330
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500331class ListHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400332 verbs = 'GET'
333 rules = ['/enumerate', '<path:path>/enumerate']
Deepak Kodihalli6e1ca532018-09-04 03:51:04 -0500334 suppress_logging = True
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400335
Brad Bishop87b63c12016-03-18 14:47:51 -0400336 def __init__(self, app, bus):
337 super(ListHandler, self).__init__(
Brad Bishopc431e1a2017-07-10 16:44:51 -0400338 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400339
Brad Bishop87b63c12016-03-18 14:47:51 -0400340 def find(self, path='/'):
341 return self.try_mapper_call(
342 self.mapper.get_subtree, path=path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400343
Brad Bishop87b63c12016-03-18 14:47:51 -0400344 def setup(self, path='/'):
345 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400346
Brad Bishop87b63c12016-03-18 14:47:51 -0400347 def do_get(self, path='/'):
Brad Bishop71527b42016-04-01 14:51:14 -0400348 return {x: y for x, y in self.mapper.enumerate_subtree(
349 path,
350 mapper_data=request.route_data['map']).dataitems()}
Brad Bishop87b63c12016-03-18 14:47:51 -0400351
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400352
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500353class MethodHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400354 verbs = 'POST'
355 rules = '<path:path>/action/<method>'
356 request_type = list
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500357 content_type = 'application/json'
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400358
Brad Bishop87b63c12016-03-18 14:47:51 -0400359 def __init__(self, app, bus):
360 super(MethodHandler, self).__init__(
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500361 app, bus, self.verbs, self.rules, self.content_type)
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530362 self.service = ''
363 self.interface = ''
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400364
Brad Bishop87b63c12016-03-18 14:47:51 -0400365 def find(self, path, method):
Saqib Khan3a00b1f2017-11-04 15:56:21 -0500366 method_list = []
Gunnar Mills313aadb2018-04-08 14:50:09 -0500367 buses = self.try_mapper_call(
Brad Bishop87b63c12016-03-18 14:47:51 -0400368 self.mapper.get_object, path=path)
Gunnar Mills313aadb2018-04-08 14:50:09 -0500369 for items in buses.items():
Brad Bishop87b63c12016-03-18 14:47:51 -0400370 m = self.find_method_on_bus(path, method, *items)
371 if m:
Saqib Khan3a00b1f2017-11-04 15:56:21 -0500372 method_list.append(m)
Nagaraju Goruganti765c2c82017-11-13 06:17:13 -0600373 if method_list:
374 return method_list
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400375
Brad Bishop87b63c12016-03-18 14:47:51 -0400376 abort(404, _4034_msg % ('method', 'found', method))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400377
Brad Bishop87b63c12016-03-18 14:47:51 -0400378 def setup(self, path, method):
Saqib Khan3a00b1f2017-11-04 15:56:21 -0500379 request.route_data['map'] = self.find(path, method)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400380
Marri Devender Raobc0c6732017-11-20 00:15:47 -0600381 def do_post(self, path, method, retry=True):
Brad Bishop87b63c12016-03-18 14:47:51 -0400382 try:
Nagaraju Goruganti765c2c82017-11-13 06:17:13 -0600383 args = []
384 if request.parameter_list:
385 args = request.parameter_list
386 # To see if the return type is capable of being merged
387 if len(request.route_data['map']) > 1:
388 results = None
389 for item in request.route_data['map']:
390 tmp = item(*args)
391 if not results:
392 if tmp is not None:
393 results = type(tmp)()
394 if isinstance(results, dict):
395 results = results.update(tmp)
396 elif isinstance(results, list):
397 results = results + tmp
398 elif isinstance(results, type(None)):
399 results = None
400 else:
401 abort(501, 'Don\'t know how to merge method call '
402 'results of {}'.format(type(tmp)))
403 return results
404 # There is only one method
405 return request.route_data['map'][0](*args)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400406
CamVan Nguyen249d1322018-03-05 10:08:33 -0600407 except dbus.exceptions.DBusException as e:
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530408 paramlist = []
Brad Bishopb7fca9b2018-01-23 12:16:50 -0500409 if e.get_dbus_name() == DBUS_INVALID_ARGS and retry:
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530410
411 signature_list = get_method_signature(self.bus, self.service,
412 path, self.interface,
413 method)
414 if not signature_list:
415 abort(400, "Failed to get method signature: %s" % str(e))
416 if len(signature_list) != len(request.parameter_list):
417 abort(400, "Invalid number of args")
418 converted_value = None
419 try:
420 for index, expected_type in enumerate(signature_list):
421 value = request.parameter_list[index]
422 converted_value = convert_type(expected_type, value)
423 paramlist.append(converted_value)
424 request.parameter_list = paramlist
Marri Devender Raobc0c6732017-11-20 00:15:47 -0600425 self.do_post(path, method, False)
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530426 return
427 except Exception as ex:
Nagaraju Gorugantiab404fa2017-12-14 10:24:40 -0600428 abort(400, "Bad Request/Invalid Args given")
Brad Bishop87b63c12016-03-18 14:47:51 -0400429 abort(400, str(e))
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530430
Brad Bishop87b63c12016-03-18 14:47:51 -0400431 if e.get_dbus_name() == DBUS_TYPE_ERROR:
432 abort(400, str(e))
433 raise
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400434
Brad Bishop87b63c12016-03-18 14:47:51 -0400435 @staticmethod
436 def find_method_in_interface(method, obj, interface, methods):
437 if methods is None:
438 return None
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400439
CamVan Nguyen249d1322018-03-05 10:08:33 -0600440 method = obmc.utils.misc.find_case_insensitive(method, list(methods.keys()))
Brad Bishop87b63c12016-03-18 14:47:51 -0400441 if method is not None:
442 iface = dbus.Interface(obj, interface)
443 return iface.get_dbus_method(method)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400444
Brad Bishop87b63c12016-03-18 14:47:51 -0400445 def find_method_on_bus(self, path, method, bus, interfaces):
446 obj = self.bus.get_object(bus, path, introspect=False)
447 iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
448 data = iface.Introspect()
449 parser = IntrospectionNodeParser(
450 ElementTree.fromstring(data),
Brad Bishopaeb995d2018-04-04 22:28:42 -0400451 intf_match=lambda x: x in interfaces)
CamVan Nguyen249d1322018-03-05 10:08:33 -0600452 for x, y in parser.get_interfaces().items():
Brad Bishop87b63c12016-03-18 14:47:51 -0400453 m = self.find_method_in_interface(
454 method, obj, x, y.get('method'))
455 if m:
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530456 self.service = bus
457 self.interface = x
Brad Bishop87b63c12016-03-18 14:47:51 -0400458 return m
459
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400460
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500461class PropertyHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400462 verbs = ['PUT', 'GET']
463 rules = '<path:path>/attr/<prop>'
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500464 content_type = 'application/json'
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400465
Brad Bishop87b63c12016-03-18 14:47:51 -0400466 def __init__(self, app, bus):
467 super(PropertyHandler, self).__init__(
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500468 app, bus, self.verbs, self.rules, self.content_type)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400469
Brad Bishop87b63c12016-03-18 14:47:51 -0400470 def find(self, path, prop):
471 self.app.instance_handler.setup(path)
472 obj = self.app.instance_handler.do_get(path)
Brad Bishop56ad87f2017-02-21 23:33:29 -0500473 real_name = obmc.utils.misc.find_case_insensitive(
CamVan Nguyen249d1322018-03-05 10:08:33 -0600474 prop, list(obj.keys()))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400475
Brad Bishop56ad87f2017-02-21 23:33:29 -0500476 if not real_name:
477 if request.method == 'PUT':
478 abort(403, _4034_msg % ('property', 'created', prop))
479 else:
480 abort(404, _4034_msg % ('property', 'found', prop))
481 return real_name, {path: obj}
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500482
Brad Bishop87b63c12016-03-18 14:47:51 -0400483 def setup(self, path, prop):
Brad Bishop56ad87f2017-02-21 23:33:29 -0500484 name, obj = self.find(path, prop)
485 request.route_data['obj'] = obj
486 request.route_data['name'] = name
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500487
Brad Bishop87b63c12016-03-18 14:47:51 -0400488 def do_get(self, path, prop):
Brad Bishop56ad87f2017-02-21 23:33:29 -0500489 name = request.route_data['name']
490 return request.route_data['obj'][path][name]
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500491
Marri Devender Raobc0c6732017-11-20 00:15:47 -0600492 def do_put(self, path, prop, value=None, retry=True):
Brad Bishop87b63c12016-03-18 14:47:51 -0400493 if value is None:
494 value = request.parameter_list
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500495
Brad Bishop87b63c12016-03-18 14:47:51 -0400496 prop, iface, properties_iface = self.get_host_interface(
497 path, prop, request.route_data['map'][path])
498 try:
499 properties_iface.Set(iface, prop, value)
CamVan Nguyen249d1322018-03-05 10:08:33 -0600500 except ValueError as e:
Brad Bishop87b63c12016-03-18 14:47:51 -0400501 abort(400, str(e))
CamVan Nguyen249d1322018-03-05 10:08:33 -0600502 except dbus.exceptions.DBusException as e:
Adriana Kobylaka8b05d12018-08-23 10:44:07 -0500503 if e.get_dbus_name() == DBUS_PROPERTY_READONLY:
504 abort(403, str(e))
Brad Bishopb7fca9b2018-01-23 12:16:50 -0500505 if e.get_dbus_name() == DBUS_INVALID_ARGS and retry:
Leonel Gonzalez0bdef952017-04-18 08:17:49 -0500506 bus_name = properties_iface.bus_name
507 expected_type = get_type_signature_by_introspection(self.bus,
508 bus_name,
509 path,
510 prop)
511 if not expected_type:
512 abort(403, "Failed to get expected type: %s" % str(e))
513 converted_value = None
514 try:
515 converted_value = convert_type(expected_type, value)
Leonel Gonzalez0bdef952017-04-18 08:17:49 -0500516 except Exception as ex:
517 abort(403, "Failed to convert %s to type %s" %
518 (value, expected_type))
Lei YU1eea5c32018-07-12 15:32:37 +0800519 try:
520 self.do_put(path, prop, converted_value, False)
521 return
522 except Exception as ex:
523 abort(403, str(ex))
524
Brad Bishop87b63c12016-03-18 14:47:51 -0400525 abort(403, str(e))
526 raise
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500527
Brad Bishop87b63c12016-03-18 14:47:51 -0400528 def get_host_interface(self, path, prop, bus_info):
CamVan Nguyen249d1322018-03-05 10:08:33 -0600529 for bus, interfaces in bus_info.items():
Brad Bishop87b63c12016-03-18 14:47:51 -0400530 obj = self.bus.get_object(bus, path, introspect=True)
531 properties_iface = dbus.Interface(
532 obj, dbus_interface=dbus.PROPERTIES_IFACE)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500533
Adriana Kobylak44573ab2019-01-14 12:54:43 -0600534 try:
535 info = self.get_host_interface_on_bus(
536 path, prop, properties_iface, bus, interfaces)
537 except Exception:
538 continue
Brad Bishop87b63c12016-03-18 14:47:51 -0400539 if info is not None:
540 prop, iface = info
541 return prop, iface, properties_iface
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500542
Brad Bishop87b63c12016-03-18 14:47:51 -0400543 def get_host_interface_on_bus(self, path, prop, iface, bus, interfaces):
544 for i in interfaces:
545 properties = self.try_properties_interface(iface.GetAll, i)
Brad Bishop69cb6d12017-02-21 12:01:52 -0500546 if not properties:
Brad Bishop87b63c12016-03-18 14:47:51 -0400547 continue
Leonel Gonzalez409f6712017-05-24 09:51:55 -0500548 match = obmc.utils.misc.find_case_insensitive(
CamVan Nguyen249d1322018-03-05 10:08:33 -0600549 prop, list(properties.keys()))
Leonel Gonzalez409f6712017-05-24 09:51:55 -0500550 if match is None:
Brad Bishop87b63c12016-03-18 14:47:51 -0400551 continue
Leonel Gonzalez409f6712017-05-24 09:51:55 -0500552 prop = match
Brad Bishop87b63c12016-03-18 14:47:51 -0400553 return prop, i
554
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500555
Brad Bishop2503bd62015-12-16 17:56:12 -0500556class SchemaHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400557 verbs = ['GET']
558 rules = '<path:path>/schema'
Deepak Kodihalli6e1ca532018-09-04 03:51:04 -0500559 suppress_logging = True
Brad Bishop2503bd62015-12-16 17:56:12 -0500560
Brad Bishop87b63c12016-03-18 14:47:51 -0400561 def __init__(self, app, bus):
562 super(SchemaHandler, self).__init__(
Brad Bishop529029b2017-07-10 16:46:01 -0400563 app, bus, self.verbs, self.rules)
Brad Bishop2503bd62015-12-16 17:56:12 -0500564
Brad Bishop87b63c12016-03-18 14:47:51 -0400565 def find(self, path):
566 return self.try_mapper_call(
567 self.mapper.get_object,
568 path=path)
Brad Bishop2503bd62015-12-16 17:56:12 -0500569
Brad Bishop87b63c12016-03-18 14:47:51 -0400570 def setup(self, path):
571 request.route_data['map'] = self.find(path)
Brad Bishop2503bd62015-12-16 17:56:12 -0500572
Brad Bishop87b63c12016-03-18 14:47:51 -0400573 def do_get(self, path):
574 schema = {}
CamVan Nguyen249d1322018-03-05 10:08:33 -0600575 for x in request.route_data['map'].keys():
Brad Bishop87b63c12016-03-18 14:47:51 -0400576 obj = self.bus.get_object(x, path, introspect=False)
577 iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
578 data = iface.Introspect()
579 parser = IntrospectionNodeParser(
580 ElementTree.fromstring(data))
CamVan Nguyen249d1322018-03-05 10:08:33 -0600581 for x, y in parser.get_interfaces().items():
Brad Bishop87b63c12016-03-18 14:47:51 -0400582 schema[x] = y
Brad Bishop2503bd62015-12-16 17:56:12 -0500583
Brad Bishop87b63c12016-03-18 14:47:51 -0400584 return schema
585
Brad Bishop2503bd62015-12-16 17:56:12 -0500586
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500587class InstanceHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400588 verbs = ['GET', 'PUT', 'DELETE']
589 rules = '<path:path>'
590 request_type = dict
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500591
Brad Bishop87b63c12016-03-18 14:47:51 -0400592 def __init__(self, app, bus):
593 super(InstanceHandler, self).__init__(
Brad Bishop529029b2017-07-10 16:46:01 -0400594 app, bus, self.verbs, self.rules)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500595
Brad Bishop87b63c12016-03-18 14:47:51 -0400596 def find(self, path, callback=None):
597 return {path: self.try_mapper_call(
598 self.mapper.get_object,
599 callback,
600 path=path)}
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500601
Brad Bishop87b63c12016-03-18 14:47:51 -0400602 def setup(self, path):
603 callback = None
604 if request.method == 'PUT':
605 def callback(e, **kw):
606 abort(403, _4034_msg % ('resource', 'created', path))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500607
Brad Bishop87b63c12016-03-18 14:47:51 -0400608 if request.route_data.get('map') is None:
609 request.route_data['map'] = self.find(path, callback)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500610
Brad Bishop87b63c12016-03-18 14:47:51 -0400611 def do_get(self, path):
Brad Bishop71527b42016-04-01 14:51:14 -0400612 return self.mapper.enumerate_object(
613 path,
614 mapper_data=request.route_data['map'])
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500615
Brad Bishop87b63c12016-03-18 14:47:51 -0400616 def do_put(self, path):
617 # make sure all properties exist in the request
618 obj = set(self.do_get(path).keys())
619 req = set(request.parameter_list.keys())
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500620
Brad Bishop87b63c12016-03-18 14:47:51 -0400621 diff = list(obj.difference(req))
622 if diff:
623 abort(403, _4034_msg % (
624 'resource', 'removed', '%s/attr/%s' % (path, diff[0])))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500625
Brad Bishop87b63c12016-03-18 14:47:51 -0400626 diff = list(req.difference(obj))
627 if diff:
628 abort(403, _4034_msg % (
629 'resource', 'created', '%s/attr/%s' % (path, diff[0])))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500630
CamVan Nguyen249d1322018-03-05 10:08:33 -0600631 for p, v in request.parameter_list.items():
Brad Bishop87b63c12016-03-18 14:47:51 -0400632 self.app.property_handler.do_put(
633 path, p, v)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500634
Brad Bishop87b63c12016-03-18 14:47:51 -0400635 def do_delete(self, path):
Matt Spinlerb1f6a2c2018-05-14 12:25:21 -0500636 deleted = False
637 for bus, interfaces in request.route_data['map'][path].items():
638 if self.bus_has_delete(interfaces):
639 self.delete_on_bus(path, bus)
640 deleted = True
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500641
Matt Spinlerb1f6a2c2018-05-14 12:25:21 -0500642 #It's OK if some objects didn't have a Delete, but not all
643 if not deleted:
644 abort(403, _4034_msg % ('resource', 'removed', path))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500645
Matt Spinlerb1f6a2c2018-05-14 12:25:21 -0500646 def bus_has_delete(self, interfaces):
647 return DELETE_IFACE in interfaces
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500648
Brad Bishop87b63c12016-03-18 14:47:51 -0400649 def delete_on_bus(self, path, bus):
650 obj = self.bus.get_object(bus, path, introspect=False)
651 delete_iface = dbus.Interface(
652 obj, dbus_interface=DELETE_IFACE)
653 delete_iface.Delete()
654
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500655
Brad Bishop2f428582015-12-02 10:56:11 -0500656class SessionHandler(MethodHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400657 ''' Handles the /login and /logout routes, manages
658 server side session store and session cookies. '''
Brad Bishop2f428582015-12-02 10:56:11 -0500659
Brad Bishop87b63c12016-03-18 14:47:51 -0400660 rules = ['/login', '/logout']
661 login_str = "User '%s' logged %s"
662 bad_passwd_str = "Invalid username or password"
663 no_user_str = "No user logged in"
664 bad_json_str = "Expecting request format { 'data': " \
665 "[<username>, <password>] }, got '%s'"
Alexander Filippovd08a4562018-03-20 12:02:23 +0300666 bmc_not_ready_str = "BMC is not ready (booting)"
Brad Bishop87b63c12016-03-18 14:47:51 -0400667 _require_auth = None
668 MAX_SESSIONS = 16
Alexander Filippovd08a4562018-03-20 12:02:23 +0300669 BMCSTATE_IFACE = 'xyz.openbmc_project.State.BMC'
670 BMCSTATE_PATH = '/xyz/openbmc_project/state/bmc0'
671 BMCSTATE_PROPERTY = 'CurrentBMCState'
672 BMCSTATE_READY = 'xyz.openbmc_project.State.BMC.BMCState.Ready'
Deepak Kodihalli6e1ca532018-09-04 03:51:04 -0500673 suppress_json_logging = True
Brad Bishop2f428582015-12-02 10:56:11 -0500674
Brad Bishop87b63c12016-03-18 14:47:51 -0400675 def __init__(self, app, bus):
676 super(SessionHandler, self).__init__(
677 app, bus)
678 self.hmac_key = os.urandom(128)
679 self.session_store = []
Brad Bishop2f428582015-12-02 10:56:11 -0500680
Brad Bishop87b63c12016-03-18 14:47:51 -0400681 @staticmethod
682 def authenticate(username, clear):
683 try:
684 encoded = spwd.getspnam(username)[1]
685 return encoded == crypt.crypt(clear, encoded)
686 except KeyError:
687 return False
Brad Bishop2f428582015-12-02 10:56:11 -0500688
Brad Bishop87b63c12016-03-18 14:47:51 -0400689 def invalidate_session(self, session):
690 try:
691 self.session_store.remove(session)
692 except ValueError:
693 pass
Brad Bishop2f428582015-12-02 10:56:11 -0500694
Brad Bishop87b63c12016-03-18 14:47:51 -0400695 def new_session(self):
696 sid = os.urandom(32)
697 if self.MAX_SESSIONS <= len(self.session_store):
698 self.session_store.pop()
699 self.session_store.insert(0, {'sid': sid})
Brad Bishop2f428582015-12-02 10:56:11 -0500700
Brad Bishop87b63c12016-03-18 14:47:51 -0400701 return self.session_store[0]
Brad Bishop2f428582015-12-02 10:56:11 -0500702
Brad Bishop87b63c12016-03-18 14:47:51 -0400703 def get_session(self, sid):
704 sids = [x['sid'] for x in self.session_store]
705 try:
706 return self.session_store[sids.index(sid)]
707 except ValueError:
708 return None
Brad Bishop2f428582015-12-02 10:56:11 -0500709
Brad Bishop87b63c12016-03-18 14:47:51 -0400710 def get_session_from_cookie(self):
711 return self.get_session(
712 request.get_cookie(
713 'sid', secret=self.hmac_key))
Brad Bishop2f428582015-12-02 10:56:11 -0500714
Brad Bishop87b63c12016-03-18 14:47:51 -0400715 def do_post(self, **kw):
716 if request.path == '/login':
717 return self.do_login(**kw)
718 else:
719 return self.do_logout(**kw)
Brad Bishop2f428582015-12-02 10:56:11 -0500720
Brad Bishop87b63c12016-03-18 14:47:51 -0400721 def do_logout(self, **kw):
722 session = self.get_session_from_cookie()
723 if session is not None:
724 user = session['user']
725 self.invalidate_session(session)
726 response.delete_cookie('sid')
727 return self.login_str % (user, 'out')
Brad Bishop2f428582015-12-02 10:56:11 -0500728
Brad Bishop87b63c12016-03-18 14:47:51 -0400729 return self.no_user_str
Brad Bishop2f428582015-12-02 10:56:11 -0500730
Brad Bishop87b63c12016-03-18 14:47:51 -0400731 def do_login(self, **kw):
Brad Bishop87b63c12016-03-18 14:47:51 -0400732 if len(request.parameter_list) != 2:
733 abort(400, self.bad_json_str % (request.json))
Brad Bishop2f428582015-12-02 10:56:11 -0500734
Brad Bishop87b63c12016-03-18 14:47:51 -0400735 if not self.authenticate(*request.parameter_list):
Brad Bishopdc3fbfa2016-09-08 09:51:38 -0400736 abort(401, self.bad_passwd_str)
Brad Bishop2f428582015-12-02 10:56:11 -0500737
Alexander Filippovd08a4562018-03-20 12:02:23 +0300738 force = False
739 try:
740 force = request.json.get('force')
741 except (ValueError, AttributeError, KeyError, TypeError):
742 force = False
743
744 if not force and not self.is_bmc_ready():
745 abort(503, self.bmc_not_ready_str)
746
Brad Bishop87b63c12016-03-18 14:47:51 -0400747 user = request.parameter_list[0]
748 session = self.new_session()
749 session['user'] = user
750 response.set_cookie(
751 'sid', session['sid'], secret=self.hmac_key,
752 secure=True,
753 httponly=True)
754 return self.login_str % (user, 'in')
Brad Bishop2f428582015-12-02 10:56:11 -0500755
Alexander Filippovd08a4562018-03-20 12:02:23 +0300756 def is_bmc_ready(self):
757 if not self.app.with_bmc_check:
758 return True
759
760 try:
761 obj = self.bus.get_object(self.BMCSTATE_IFACE, self.BMCSTATE_PATH)
762 iface = dbus.Interface(obj, dbus.PROPERTIES_IFACE)
763 state = iface.Get(self.BMCSTATE_IFACE, self.BMCSTATE_PROPERTY)
764 if state == self.BMCSTATE_READY:
765 return True
766
767 except dbus.exceptions.DBusException:
768 pass
769
770 return False
771
Brad Bishop87b63c12016-03-18 14:47:51 -0400772 def find(self, **kw):
773 pass
Brad Bishop2f428582015-12-02 10:56:11 -0500774
Brad Bishop87b63c12016-03-18 14:47:51 -0400775 def setup(self, **kw):
776 pass
777
Brad Bishop2f428582015-12-02 10:56:11 -0500778
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500779class ImageUploadUtils:
780 ''' Provides common utils for image upload. '''
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500781
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500782 file_loc = '/tmp/images'
783 file_prefix = 'img'
784 file_suffix = ''
Adriana Kobylak53693892018-03-12 13:05:50 -0500785 signal = None
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500786
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500787 @classmethod
788 def do_upload(cls, filename=''):
Adriana Kobylak53693892018-03-12 13:05:50 -0500789 def cleanup():
790 os.close(handle)
791 if cls.signal:
792 cls.signal.remove()
793 cls.signal = None
794
795 def signal_callback(path, a, **kw):
796 # Just interested on the first Version interface created which is
797 # triggered when the file is uploaded. This helps avoid getting the
798 # wrong information for multiple upload requests in a row.
799 if "xyz.openbmc_project.Software.Version" in a and \
800 "xyz.openbmc_project.Software.Activation" not in a:
801 paths.append(path)
802
803 while cls.signal:
804 # Serialize uploads by waiting for the signal to be cleared.
805 # This makes it easier to ensure that the version information
806 # is the right one instead of the data from another upload request.
807 gevent.sleep(1)
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500808 if not os.path.exists(cls.file_loc):
Gunnar Millsfb515792017-11-09 15:52:17 -0600809 abort(500, "Error Directory not found")
Adriana Kobylak53693892018-03-12 13:05:50 -0500810 paths = []
811 bus = dbus.SystemBus()
812 cls.signal = bus.add_signal_receiver(
813 signal_callback,
814 dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager',
815 signal_name='InterfacesAdded',
816 path=SOFTWARE_PATH)
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500817 if not filename:
818 handle, filename = tempfile.mkstemp(cls.file_suffix,
819 cls.file_prefix, cls.file_loc)
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500820 else:
821 filename = os.path.join(cls.file_loc, filename)
Gunnar Millsb66b18c2017-08-21 16:17:21 -0500822 handle = os.open(filename, os.O_WRONLY | os.O_CREAT)
Leonel Gonzalez0b62edf2017-06-08 15:10:03 -0500823 try:
824 file_contents = request.body.read()
825 request.body.close()
Gunnar Millsb66b18c2017-08-21 16:17:21 -0500826 os.write(handle, file_contents)
Adriana Kobylak53693892018-03-12 13:05:50 -0500827 # Close file after writing, the image manager process watches for
828 # the close event to know the upload is complete.
Gunnar Millsb66b18c2017-08-21 16:17:21 -0500829 os.close(handle)
Adriana Kobylak53693892018-03-12 13:05:50 -0500830 except (IOError, ValueError) as e:
831 cleanup()
832 abort(400, str(e))
833 except Exception:
834 cleanup()
835 abort(400, "Unexpected Error")
836 loop = gobject.MainLoop()
837 gcontext = loop.get_context()
838 count = 0
839 version_id = ''
840 while loop is not None:
841 try:
842 if gcontext.pending():
843 gcontext.iteration()
844 if not paths:
845 gevent.sleep(1)
846 else:
847 version_id = os.path.basename(paths.pop())
848 break
849 count += 1
850 if count == 10:
851 break
852 except Exception:
853 break
854 cls.signal.remove()
855 cls.signal = None
Adriana Kobylak97fe4352018-04-10 10:44:11 -0500856 if version_id:
857 return version_id
858 else:
859 abort(400, "Version already exists or failed to be extracted")
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500860
861
862class ImagePostHandler(RouteHandler):
863 ''' Handles the /upload/image route. '''
864
865 verbs = ['POST']
866 rules = ['/upload/image']
867 content_type = 'application/octet-stream'
868
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500869 def __init__(self, app, bus):
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500870 super(ImagePostHandler, self).__init__(
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500871 app, bus, self.verbs, self.rules, self.content_type)
872
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500873 def do_post(self, filename=''):
Adriana Kobylak53693892018-03-12 13:05:50 -0500874 return ImageUploadUtils.do_upload()
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500875
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500876 def find(self, **kw):
877 pass
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500878
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500879 def setup(self, **kw):
880 pass
881
882
Dhruvaraj Subhashchandrandee2ef52018-09-05 05:36:31 -0500883class CertificateHandler:
Dhruvaraj Subhashchandrandee2ef52018-09-05 05:36:31 -0500884 file_suffix = '.pem'
885 file_prefix = 'cert_'
Dhruvaraj Subhashchandrandee2ef52018-09-05 05:36:31 -0500886 CERT_PATH = '/xyz/openbmc_project/certs'
887 CERT_IFACE = 'xyz.openbmc_project.Certs.Install'
888
Deepak Kodihallia324acd2018-09-30 06:57:57 -0500889 def __init__(self, route_handler, cert_type, service):
Dhruvaraj Subhashchandrandee2ef52018-09-05 05:36:31 -0500890 if not service:
891 abort(500, "Missing service")
892 if not cert_type:
893 abort(500, "Missing certificate type")
Deepak Kodihallia324acd2018-09-30 06:57:57 -0500894 bus = dbus.SystemBus()
895 certPath = self.CERT_PATH + "/" + cert_type + "/" + service
896 intfs = route_handler.try_mapper_call(
897 route_handler.mapper.get_object, path=certPath)
898 for busName,intf in intfs.items():
899 if self.CERT_IFACE in intf:
900 self.obj = bus.get_object(busName, certPath)
901 return
902 abort(404, "Path not found")
903
904 def do_upload(self):
905 def cleanup():
906 if os.path.exists(temp.name):
907 os.remove(temp.name)
Dhruvaraj Subhashchandrandee2ef52018-09-05 05:36:31 -0500908
909 with tempfile.NamedTemporaryFile(
Deepak Kodihallia324acd2018-09-30 06:57:57 -0500910 suffix=self.file_suffix,
911 prefix=self.file_prefix,
Dhruvaraj Subhashchandrandee2ef52018-09-05 05:36:31 -0500912 delete=False) as temp:
913 try:
914 file_contents = request.body.read()
915 request.body.close()
916 temp.write(file_contents)
917 except (IOError, ValueError) as e:
918 cleanup()
919 abort(500, str(e))
920 except Exception:
921 cleanup()
922 abort(500, "Unexpected Error")
923
924 try:
Deepak Kodihallia324acd2018-09-30 06:57:57 -0500925 iface = dbus.Interface(self.obj, self.CERT_IFACE)
926 iface.Install(temp.name)
Deepak Kodihallic043cdd2018-10-02 06:27:57 -0500927 except Exception as e:
Dhruvaraj Subhashchandrandee2ef52018-09-05 05:36:31 -0500928 cleanup()
Deepak Kodihalli844bb4e2018-10-03 04:59:26 -0500929 abort(400, str(e))
Deepak Kodihallia324acd2018-09-30 06:57:57 -0500930 cleanup()
931
932 def do_delete(self):
933 delete_iface = dbus.Interface(
934 self.obj, dbus_interface=DELETE_IFACE)
935 delete_iface.Delete()
Dhruvaraj Subhashchandrandee2ef52018-09-05 05:36:31 -0500936
937
938class CertificatePutHandler(RouteHandler):
939 ''' Handles the /xyz/openbmc_project/certs/<cert_type>/<service> route. '''
940
Deepak Kodihallia324acd2018-09-30 06:57:57 -0500941 verbs = ['PUT', 'DELETE']
Dhruvaraj Subhashchandrandee2ef52018-09-05 05:36:31 -0500942 rules = ['/xyz/openbmc_project/certs/<cert_type>/<service>']
943 content_type = 'application/octet-stream'
944
945 def __init__(self, app, bus):
946 super(CertificatePutHandler, self).__init__(
947 app, bus, self.verbs, self.rules, self.content_type)
948
949 def do_put(self, cert_type, service):
Deepak Kodihallia324acd2018-09-30 06:57:57 -0500950 return CertificateHandler(self, cert_type, service).do_upload()
951
952 def do_delete(self, cert_type, service):
953 return CertificateHandler(self, cert_type, service).do_delete()
Dhruvaraj Subhashchandrandee2ef52018-09-05 05:36:31 -0500954
955 def find(self, **kw):
956 pass
957
958 def setup(self, **kw):
959 pass
960
961
Deepak Kodihalli639b5022017-10-13 06:40:26 -0500962class EventNotifier:
963 keyNames = {}
964 keyNames['event'] = 'event'
965 keyNames['path'] = 'path'
966 keyNames['intfMap'] = 'interfaces'
967 keyNames['propMap'] = 'properties'
968 keyNames['intf'] = 'interface'
969
970 def __init__(self, wsock, filters):
971 self.wsock = wsock
972 self.paths = filters.get("paths", [])
973 self.interfaces = filters.get("interfaces", [])
Andrew Geissler0f7019d2018-10-10 15:00:17 -0500974 self.signals = []
975 self.socket_error = False
Deepak Kodihalli639b5022017-10-13 06:40:26 -0500976 if not self.paths:
977 self.paths.append(None)
978 bus = dbus.SystemBus()
979 # Add a signal receiver for every path the client is interested in
980 for path in self.paths:
Andrew Geissler0f7019d2018-10-10 15:00:17 -0500981 add_sig = bus.add_signal_receiver(
Deepak Kodihalli639b5022017-10-13 06:40:26 -0500982 self.interfaces_added_handler,
983 dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager',
984 signal_name='InterfacesAdded',
985 path=path)
Andrew Geissler0f7019d2018-10-10 15:00:17 -0500986 chg_sig = bus.add_signal_receiver(
Deepak Kodihalli639b5022017-10-13 06:40:26 -0500987 self.properties_changed_handler,
988 dbus_interface=dbus.PROPERTIES_IFACE,
989 signal_name='PropertiesChanged',
990 path=path,
991 path_keyword='path')
Andrew Geissler0f7019d2018-10-10 15:00:17 -0500992 self.signals.append(add_sig)
993 self.signals.append(chg_sig)
Deepak Kodihalli639b5022017-10-13 06:40:26 -0500994 loop = gobject.MainLoop()
995 # gobject's mainloop.run() will block the entire process, so the gevent
996 # scheduler and hence greenlets won't execute. The while-loop below
997 # works around this limitation by using gevent's sleep, instead of
998 # calling loop.run()
999 gcontext = loop.get_context()
1000 while loop is not None:
1001 try:
Andrew Geissler0f7019d2018-10-10 15:00:17 -05001002 if self.socket_error:
1003 for signal in self.signals:
1004 signal.remove()
1005 loop.quit()
1006 break;
Deepak Kodihalli639b5022017-10-13 06:40:26 -05001007 if gcontext.pending():
1008 gcontext.iteration()
1009 else:
1010 # gevent.sleep puts only the current greenlet to sleep,
1011 # not the entire process.
1012 gevent.sleep(5)
1013 except WebSocketError:
1014 break
1015
1016 def interfaces_added_handler(self, path, iprops, **kw):
1017 ''' If the client is interested in these changes, respond to the
1018 client. This handles d-bus interface additions.'''
1019 if (not self.interfaces) or \
1020 (not set(iprops).isdisjoint(self.interfaces)):
1021 response = {}
1022 response[self.keyNames['event']] = "InterfacesAdded"
1023 response[self.keyNames['path']] = path
1024 response[self.keyNames['intfMap']] = iprops
1025 try:
1026 self.wsock.send(json.dumps(response))
Andrew Geissler0f7019d2018-10-10 15:00:17 -05001027 except:
1028 self.socket_error = True
Deepak Kodihalli639b5022017-10-13 06:40:26 -05001029 return
1030
1031 def properties_changed_handler(self, interface, new, old, **kw):
1032 ''' If the client is interested in these changes, respond to the
1033 client. This handles d-bus property changes. '''
1034 if (not self.interfaces) or (interface in self.interfaces):
1035 path = str(kw['path'])
1036 response = {}
1037 response[self.keyNames['event']] = "PropertiesChanged"
1038 response[self.keyNames['path']] = path
1039 response[self.keyNames['intf']] = interface
1040 response[self.keyNames['propMap']] = new
1041 try:
1042 self.wsock.send(json.dumps(response))
Andrew Geissler0f7019d2018-10-10 15:00:17 -05001043 except:
1044 self.socket_error = True
Deepak Kodihalli639b5022017-10-13 06:40:26 -05001045 return
1046
1047
Deepak Kodihallib209dd12017-10-11 01:19:17 -05001048class EventHandler(RouteHandler):
1049 ''' Handles the /subscribe route, for clients to be able
1050 to subscribe to BMC events. '''
1051
1052 verbs = ['GET']
1053 rules = ['/subscribe']
Deepak Kodihalli6e1ca532018-09-04 03:51:04 -05001054 suppress_logging = True
Deepak Kodihallib209dd12017-10-11 01:19:17 -05001055
1056 def __init__(self, app, bus):
1057 super(EventHandler, self).__init__(
1058 app, bus, self.verbs, self.rules)
1059
1060 def find(self, **kw):
1061 pass
1062
1063 def setup(self, **kw):
1064 pass
1065
1066 def do_get(self):
1067 wsock = request.environ.get('wsgi.websocket')
1068 if not wsock:
1069 abort(400, 'Expected WebSocket request.')
Jayashankar Padathbec10c22018-05-29 18:22:59 +05301070 ping_sender = Greenlet.spawn(send_ws_ping, wsock, WEBSOCKET_TIMEOUT)
Deepak Kodihalli639b5022017-10-13 06:40:26 -05001071 filters = wsock.receive()
1072 filters = json.loads(filters)
1073 notifier = EventNotifier(wsock, filters)
Deepak Kodihallib209dd12017-10-11 01:19:17 -05001074
Deepak Kodihalli5c518f62018-04-23 03:26:38 -05001075class HostConsoleHandler(RouteHandler):
1076 ''' Handles the /console route, for clients to be able
1077 read/write the host serial console. The way this is
1078 done is by exposing a websocket that's mirrored to an
1079 abstract UNIX domain socket, which is the source for
1080 the console data. '''
1081
1082 verbs = ['GET']
1083 # Naming the route console0, because the numbering will help
1084 # on multi-bmc/multi-host systems.
1085 rules = ['/console0']
Deepak Kodihalli6e1ca532018-09-04 03:51:04 -05001086 suppress_logging = True
Deepak Kodihalli5c518f62018-04-23 03:26:38 -05001087
1088 def __init__(self, app, bus):
1089 super(HostConsoleHandler, self).__init__(
1090 app, bus, self.verbs, self.rules)
1091
1092 def find(self, **kw):
1093 pass
1094
1095 def setup(self, **kw):
1096 pass
1097
1098 def read_wsock(self, wsock, sock):
1099 while True:
1100 try:
1101 incoming = wsock.receive()
1102 if incoming:
1103 # Read websocket, write to UNIX socket
1104 sock.send(incoming)
1105 except Exception as e:
1106 sock.close()
1107 return
1108
1109 def read_sock(self, sock, wsock):
1110 max_sock_read_len = 4096
1111 while True:
1112 try:
1113 outgoing = sock.recv(max_sock_read_len)
1114 if outgoing:
1115 # Read UNIX socket, write to websocket
1116 wsock.send(outgoing)
1117 except Exception as e:
1118 wsock.close()
1119 return
1120
Deepak Kodihalli5c518f62018-04-23 03:26:38 -05001121 def do_get(self):
1122 wsock = request.environ.get('wsgi.websocket')
1123 if not wsock:
1124 abort(400, 'Expected WebSocket based request.')
1125
Vernon Mauerydbc46912018-12-19 10:33:46 -08001126 # An abstract Unix socket path must be less than or equal to 108 bytes
1127 # and does not need to be nul-terminated or padded out to 108 bytes
Deepak Kodihalli5c518f62018-04-23 03:26:38 -05001128 socket_name = "\0obmc-console"
Vernon Mauerydbc46912018-12-19 10:33:46 -08001129 socket_path = socket_name
Deepak Kodihalli5c518f62018-04-23 03:26:38 -05001130 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
1131
1132 try:
1133 sock.connect(socket_path)
1134 except Exception as e:
1135 abort(500, str(e))
1136
1137 wsock_reader = Greenlet.spawn(self.read_wsock, wsock, sock)
1138 sock_reader = Greenlet.spawn(self.read_sock, sock, wsock)
Jayashankar Padathbec10c22018-05-29 18:22:59 +05301139 ping_sender = Greenlet.spawn(send_ws_ping, wsock, WEBSOCKET_TIMEOUT)
Deepak Kodihalli5c518f62018-04-23 03:26:38 -05001140 gevent.joinall([wsock_reader, sock_reader, ping_sender])
1141
1142
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -05001143class ImagePutHandler(RouteHandler):
1144 ''' Handles the /upload/image/<filename> route. '''
1145
1146 verbs = ['PUT']
1147 rules = ['/upload/image/<filename>']
1148 content_type = 'application/octet-stream'
1149
1150 def __init__(self, app, bus):
1151 super(ImagePutHandler, self).__init__(
1152 app, bus, self.verbs, self.rules, self.content_type)
1153
1154 def do_put(self, filename=''):
Adriana Kobylak53693892018-03-12 13:05:50 -05001155 return ImageUploadUtils.do_upload(filename)
Deepak Kodihalli1af301a2017-04-11 07:29:01 -05001156
1157 def find(self, **kw):
1158 pass
1159
1160 def setup(self, **kw):
1161 pass
1162
1163
Jayanth Othayoth9bc94992017-06-29 06:30:40 -05001164class DownloadDumpHandler(RouteHandler):
1165 ''' Handles the /download/dump route. '''
1166
1167 verbs = 'GET'
1168 rules = ['/download/dump/<dumpid>']
1169 content_type = 'application/octet-stream'
Jayanth Othayoth18c3a242017-08-02 08:16:11 -05001170 dump_loc = '/var/lib/phosphor-debug-collector/dumps'
Brad Bishop944cd042017-07-10 16:42:41 -04001171 suppress_json_resp = True
Deepak Kodihalli6e1ca532018-09-04 03:51:04 -05001172 suppress_logging = True
Jayanth Othayoth9bc94992017-06-29 06:30:40 -05001173
1174 def __init__(self, app, bus):
1175 super(DownloadDumpHandler, self).__init__(
1176 app, bus, self.verbs, self.rules, self.content_type)
1177
1178 def do_get(self, dumpid):
1179 return self.do_download(dumpid)
1180
1181 def find(self, **kw):
1182 pass
1183
1184 def setup(self, **kw):
1185 pass
1186
1187 def do_download(self, dumpid):
1188 dump_loc = os.path.join(self.dump_loc, dumpid)
1189 if not os.path.exists(dump_loc):
1190 abort(404, "Path not found")
1191
1192 files = os.listdir(dump_loc)
1193 num_files = len(files)
1194 if num_files == 0:
1195 abort(404, "Dump not found")
1196
1197 return static_file(os.path.basename(files[0]), root=dump_loc,
1198 download=True, mimetype=self.content_type)
1199
1200
Matt Spinlerd41643e2018-02-02 13:51:38 -06001201class WebHandler(RouteHandler):
1202 ''' Handles the routes for the web UI files. '''
1203
1204 verbs = 'GET'
1205
1206 # Match only what we know are web files, so everything else
1207 # can get routed to the REST handlers.
1208 rules = ['//', '/<filename:re:.+\.js>', '/<filename:re:.+\.svg>',
1209 '/<filename:re:.+\.css>', '/<filename:re:.+\.ttf>',
1210 '/<filename:re:.+\.eot>', '/<filename:re:.+\.woff>',
1211 '/<filename:re:.+\.woff2>', '/<filename:re:.+\.map>',
1212 '/<filename:re:.+\.png>', '/<filename:re:.+\.html>',
1213 '/<filename:re:.+\.ico>']
1214
1215 # The mimetypes module knows about most types, but not these
1216 content_types = {
1217 '.eot': 'application/vnd.ms-fontobject',
1218 '.woff': 'application/x-font-woff',
1219 '.woff2': 'application/x-font-woff2',
1220 '.ttf': 'application/x-font-ttf',
1221 '.map': 'application/json'
1222 }
1223
1224 _require_auth = None
1225 suppress_json_resp = True
Deepak Kodihalli6e1ca532018-09-04 03:51:04 -05001226 suppress_logging = True
Matt Spinlerd41643e2018-02-02 13:51:38 -06001227
1228 def __init__(self, app, bus):
1229 super(WebHandler, self).__init__(
1230 app, bus, self.verbs, self.rules)
1231
1232 def get_type(self, filename):
1233 ''' Returns the content type and encoding for a file '''
1234
1235 content_type, encoding = mimetypes.guess_type(filename)
1236
1237 # Try our own list if mimetypes didn't recognize it
1238 if content_type is None:
1239 if filename[-3:] == '.gz':
1240 filename = filename[:-3]
1241 extension = filename[filename.rfind('.'):]
1242 content_type = self.content_types.get(extension, None)
1243
1244 return content_type, encoding
1245
1246 def do_get(self, filename='index.html'):
1247
1248 # If a gzipped version exists, use that instead.
1249 # Possible future enhancement: if the client doesn't
1250 # accept compressed files, unzip it ourselves before sending.
1251 if not os.path.exists(os.path.join(www_base_path, filename)):
1252 filename = filename + '.gz'
1253
1254 # Though bottle should protect us, ensure path is valid
1255 realpath = os.path.realpath(filename)
1256 if realpath[0] == '/':
1257 realpath = realpath[1:]
1258 if not os.path.exists(os.path.join(www_base_path, realpath)):
1259 abort(404, "Path not found")
1260
1261 mimetype, encoding = self.get_type(filename)
1262
1263 # Couldn't find the type - let static_file() deal with it,
1264 # though this should never happen.
1265 if mimetype is None:
1266 print("Can't figure out content-type for %s" % filename)
1267 mimetype = 'auto'
1268
1269 # This call will set several header fields for us,
1270 # including the charset if the type is text.
1271 response = static_file(filename, www_base_path, mimetype)
1272
1273 # static_file() will only set the encoding if the
1274 # mimetype was auto, so set it here.
1275 if encoding is not None:
1276 response.set_header('Content-Encoding', encoding)
1277
1278 return response
1279
1280 def find(self, **kw):
1281 pass
1282
1283 def setup(self, **kw):
1284 pass
1285
1286
Brad Bishop2f428582015-12-02 10:56:11 -05001287class AuthorizationPlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -04001288 ''' Invokes an optional list of authorization callbacks. '''
Brad Bishop2f428582015-12-02 10:56:11 -05001289
Brad Bishop87b63c12016-03-18 14:47:51 -04001290 name = 'authorization'
1291 api = 2
Brad Bishop2f428582015-12-02 10:56:11 -05001292
Brad Bishop87b63c12016-03-18 14:47:51 -04001293 class Compose:
1294 def __init__(self, validators, callback, session_mgr):
1295 self.validators = validators
1296 self.callback = callback
1297 self.session_mgr = session_mgr
Brad Bishop2f428582015-12-02 10:56:11 -05001298
Brad Bishop87b63c12016-03-18 14:47:51 -04001299 def __call__(self, *a, **kw):
1300 sid = request.get_cookie('sid', secret=self.session_mgr.hmac_key)
1301 session = self.session_mgr.get_session(sid)
Brad Bishopd4c1c552017-02-21 00:07:28 -05001302 if request.method != 'OPTIONS':
1303 for x in self.validators:
1304 x(session, *a, **kw)
Brad Bishop2f428582015-12-02 10:56:11 -05001305
Brad Bishop87b63c12016-03-18 14:47:51 -04001306 return self.callback(*a, **kw)
Brad Bishop2f428582015-12-02 10:56:11 -05001307
Brad Bishop87b63c12016-03-18 14:47:51 -04001308 def apply(self, callback, route):
1309 undecorated = route.get_undecorated_callback()
1310 if not isinstance(undecorated, RouteHandler):
1311 return callback
Brad Bishop2f428582015-12-02 10:56:11 -05001312
Brad Bishop87b63c12016-03-18 14:47:51 -04001313 auth_types = getattr(
1314 undecorated, '_require_auth', None)
1315 if not auth_types:
1316 return callback
Brad Bishop2f428582015-12-02 10:56:11 -05001317
Brad Bishop87b63c12016-03-18 14:47:51 -04001318 return self.Compose(
1319 auth_types, callback, undecorated.app.session_handler)
1320
Brad Bishop2f428582015-12-02 10:56:11 -05001321
Brad Bishopd0c404a2017-02-21 09:23:25 -05001322class CorsPlugin(object):
1323 ''' Add CORS headers. '''
1324
1325 name = 'cors'
1326 api = 2
1327
1328 @staticmethod
1329 def process_origin():
1330 origin = request.headers.get('Origin')
1331 if origin:
1332 response.add_header('Access-Control-Allow-Origin', origin)
1333 response.add_header(
1334 'Access-Control-Allow-Credentials', 'true')
1335
1336 @staticmethod
1337 def process_method_and_headers(verbs):
1338 method = request.headers.get('Access-Control-Request-Method')
1339 headers = request.headers.get('Access-Control-Request-Headers')
1340 if headers:
1341 headers = [x.lower() for x in headers.split(',')]
1342
1343 if method in verbs \
1344 and headers == ['content-type']:
1345 response.add_header('Access-Control-Allow-Methods', method)
1346 response.add_header(
1347 'Access-Control-Allow-Headers', 'Content-Type')
Ratan Gupta91b46f82018-01-14 12:52:23 +05301348 response.add_header('X-Frame-Options', 'deny')
1349 response.add_header('X-Content-Type-Options', 'nosniff')
1350 response.add_header('X-XSS-Protection', '1; mode=block')
1351 response.add_header(
1352 'Content-Security-Policy', "default-src 'self'")
1353 response.add_header(
1354 'Strict-Transport-Security',
1355 'max-age=31536000; includeSubDomains; preload')
Brad Bishopd0c404a2017-02-21 09:23:25 -05001356
1357 def __init__(self, app):
1358 app.install_error_callback(self.error_callback)
1359
1360 def apply(self, callback, route):
1361 undecorated = route.get_undecorated_callback()
1362 if not isinstance(undecorated, RouteHandler):
1363 return callback
1364
1365 if not getattr(undecorated, '_enable_cors', None):
1366 return callback
1367
1368 def wrap(*a, **kw):
1369 self.process_origin()
1370 self.process_method_and_headers(undecorated._verbs)
1371 return callback(*a, **kw)
1372
1373 return wrap
1374
1375 def error_callback(self, **kw):
1376 self.process_origin()
1377
1378
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001379class JsonApiRequestPlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -04001380 ''' Ensures request content satisfies the OpenBMC json api format. '''
1381 name = 'json_api_request'
1382 api = 2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001383
Brad Bishop87b63c12016-03-18 14:47:51 -04001384 error_str = "Expecting request format { 'data': <value> }, got '%s'"
1385 type_error_str = "Unsupported Content-Type: '%s'"
1386 json_type = "application/json"
1387 request_methods = ['PUT', 'POST', 'PATCH']
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001388
Brad Bishop87b63c12016-03-18 14:47:51 -04001389 @staticmethod
1390 def content_expected():
1391 return request.method in JsonApiRequestPlugin.request_methods
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001392
Brad Bishop87b63c12016-03-18 14:47:51 -04001393 def validate_request(self):
1394 if request.content_length > 0 and \
1395 request.content_type != self.json_type:
1396 abort(415, self.type_error_str % request.content_type)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001397
Brad Bishop87b63c12016-03-18 14:47:51 -04001398 try:
1399 request.parameter_list = request.json.get('data')
CamVan Nguyen249d1322018-03-05 10:08:33 -06001400 except ValueError as e:
Brad Bishop87b63c12016-03-18 14:47:51 -04001401 abort(400, str(e))
1402 except (AttributeError, KeyError, TypeError):
1403 abort(400, self.error_str % request.json)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001404
Brad Bishop87b63c12016-03-18 14:47:51 -04001405 def apply(self, callback, route):
Deepak Kodihallifb6cd482017-04-10 07:27:09 -05001406 content_type = getattr(
1407 route.get_undecorated_callback(), '_content_type', None)
1408 if self.json_type != content_type:
1409 return callback
1410
Brad Bishop87b63c12016-03-18 14:47:51 -04001411 verbs = getattr(
1412 route.get_undecorated_callback(), '_verbs', None)
1413 if verbs is None:
1414 return callback
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001415
Brad Bishop87b63c12016-03-18 14:47:51 -04001416 if not set(self.request_methods).intersection(verbs):
1417 return callback
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001418
Brad Bishop87b63c12016-03-18 14:47:51 -04001419 def wrap(*a, **kw):
1420 if self.content_expected():
1421 self.validate_request()
1422 return callback(*a, **kw)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001423
Brad Bishop87b63c12016-03-18 14:47:51 -04001424 return wrap
1425
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001426
1427class JsonApiRequestTypePlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -04001428 ''' Ensures request content type satisfies the OpenBMC json api format. '''
1429 name = 'json_api_method_request'
1430 api = 2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001431
Brad Bishop87b63c12016-03-18 14:47:51 -04001432 error_str = "Expecting request format { 'data': %s }, got '%s'"
Deepak Kodihallifb6cd482017-04-10 07:27:09 -05001433 json_type = "application/json"
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001434
Brad Bishop87b63c12016-03-18 14:47:51 -04001435 def apply(self, callback, route):
Deepak Kodihallifb6cd482017-04-10 07:27:09 -05001436 content_type = getattr(
1437 route.get_undecorated_callback(), '_content_type', None)
1438 if self.json_type != content_type:
1439 return callback
1440
Brad Bishop87b63c12016-03-18 14:47:51 -04001441 request_type = getattr(
1442 route.get_undecorated_callback(), 'request_type', None)
1443 if request_type is None:
1444 return callback
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001445
Brad Bishop87b63c12016-03-18 14:47:51 -04001446 def validate_request():
1447 if not isinstance(request.parameter_list, request_type):
1448 abort(400, self.error_str % (str(request_type), request.json))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001449
Brad Bishop87b63c12016-03-18 14:47:51 -04001450 def wrap(*a, **kw):
1451 if JsonApiRequestPlugin.content_expected():
1452 validate_request()
1453 return callback(*a, **kw)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001454
Brad Bishop87b63c12016-03-18 14:47:51 -04001455 return wrap
1456
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001457
Brad Bishop080a48e2017-02-21 22:34:43 -05001458class JsonErrorsPlugin(JSONPlugin):
1459 ''' Extend the Bottle JSONPlugin such that it also encodes error
1460 responses. '''
1461
1462 def __init__(self, app, **kw):
1463 super(JsonErrorsPlugin, self).__init__(**kw)
1464 self.json_opts = {
CamVan Nguyen249d1322018-03-05 10:08:33 -06001465 x: y for x, y in kw.items()
Brad Bishop080a48e2017-02-21 22:34:43 -05001466 if x in ['indent', 'sort_keys']}
1467 app.install_error_callback(self.error_callback)
1468
1469 def error_callback(self, response_object, response_body, **kw):
1470 response_body['body'] = json.dumps(response_object, **self.json_opts)
1471 response.content_type = 'application/json'
1472
1473
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001474class JsonApiResponsePlugin(object):
Brad Bishop080a48e2017-02-21 22:34:43 -05001475 ''' Emits responses in the OpenBMC json api format. '''
Brad Bishop87b63c12016-03-18 14:47:51 -04001476 name = 'json_api_response'
1477 api = 2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001478
Brad Bishopd4c1c552017-02-21 00:07:28 -05001479 @staticmethod
1480 def has_body():
1481 return request.method not in ['OPTIONS']
1482
Brad Bishop080a48e2017-02-21 22:34:43 -05001483 def __init__(self, app):
1484 app.install_error_callback(self.error_callback)
1485
Matt Spinler6691e7c2018-06-25 14:11:58 -05001486 @staticmethod
1487 def dbus_boolean_to_bool(data):
1488 ''' Convert all dbus.Booleans to true/false instead of 1/0 as
1489 the JSON encoder thinks they're ints. Note that unlike
1490 dicts and lists, tuples (from a dbus.Struct) are immutable
1491 so they need special handling. '''
1492
1493 def walkdict(data):
1494 for key, value in data.items():
1495 if isinstance(value, dbus.Boolean):
1496 data[key] = bool(value)
1497 elif isinstance(value, tuple):
1498 data[key] = walktuple(value)
1499 else:
1500 JsonApiResponsePlugin.dbus_boolean_to_bool(value)
1501
1502 def walklist(data):
1503 for i in range(len(data)):
1504 if isinstance(data[i], dbus.Boolean):
1505 data[i] = bool(data[i])
1506 elif isinstance(data[i], tuple):
1507 data[i] = walktuple(data[i])
1508 else:
1509 JsonApiResponsePlugin.dbus_boolean_to_bool(data[i])
1510
1511 def walktuple(data):
1512 new = []
1513 for item in data:
1514 if isinstance(item, dbus.Boolean):
1515 item = bool(item)
1516 else:
1517 JsonApiResponsePlugin.dbus_boolean_to_bool(item)
1518 new.append(item)
1519 return tuple(new)
1520
1521 if isinstance(data, dict):
1522 walkdict(data)
1523 elif isinstance(data, list):
1524 walklist(data)
1525
Brad Bishop87b63c12016-03-18 14:47:51 -04001526 def apply(self, callback, route):
Brad Bishop944cd042017-07-10 16:42:41 -04001527 skip = getattr(
1528 route.get_undecorated_callback(), 'suppress_json_resp', None)
1529 if skip:
Jayanth Othayoth1444fd82017-06-29 05:45:07 -05001530 return callback
1531
Brad Bishop87b63c12016-03-18 14:47:51 -04001532 def wrap(*a, **kw):
Brad Bishopd4c1c552017-02-21 00:07:28 -05001533 data = callback(*a, **kw)
Matt Spinler6691e7c2018-06-25 14:11:58 -05001534 JsonApiResponsePlugin.dbus_boolean_to_bool(data)
Brad Bishopd4c1c552017-02-21 00:07:28 -05001535 if self.has_body():
1536 resp = {'data': data}
1537 resp['status'] = 'ok'
1538 resp['message'] = response.status_line
1539 return resp
Brad Bishop87b63c12016-03-18 14:47:51 -04001540 return wrap
1541
Brad Bishop080a48e2017-02-21 22:34:43 -05001542 def error_callback(self, error, response_object, **kw):
Brad Bishop87b63c12016-03-18 14:47:51 -04001543 response_object['message'] = error.status_line
Brad Bishop9c2531e2017-03-07 10:22:40 -05001544 response_object['status'] = 'error'
Brad Bishop080a48e2017-02-21 22:34:43 -05001545 response_object.setdefault('data', {})['description'] = str(error.body)
Brad Bishop87b63c12016-03-18 14:47:51 -04001546 if error.status_code == 500:
1547 response_object['data']['exception'] = repr(error.exception)
1548 response_object['data']['traceback'] = error.traceback.splitlines()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001549
Brad Bishop87b63c12016-03-18 14:47:51 -04001550
Brad Bishop080a48e2017-02-21 22:34:43 -05001551class JsonpPlugin(object):
Brad Bishop80fe37a2016-03-29 10:54:54 -04001552 ''' Json javascript wrapper. '''
1553 name = 'jsonp'
1554 api = 2
1555
Brad Bishop080a48e2017-02-21 22:34:43 -05001556 def __init__(self, app, **kw):
1557 app.install_error_callback(self.error_callback)
Brad Bishop80fe37a2016-03-29 10:54:54 -04001558
1559 @staticmethod
1560 def to_jsonp(json):
1561 jwrapper = request.query.callback or None
1562 if(jwrapper):
1563 response.set_header('Content-Type', 'application/javascript')
1564 json = jwrapper + '(' + json + ');'
1565 return json
1566
1567 def apply(self, callback, route):
1568 def wrap(*a, **kw):
1569 return self.to_jsonp(callback(*a, **kw))
1570 return wrap
1571
Brad Bishop080a48e2017-02-21 22:34:43 -05001572 def error_callback(self, response_body, **kw):
1573 response_body['body'] = self.to_jsonp(response_body['body'])
Brad Bishop80fe37a2016-03-29 10:54:54 -04001574
1575
Deepak Kodihalli461367a2017-04-10 07:11:38 -05001576class ContentCheckerPlugin(object):
1577 ''' Ensures that a route is associated with the expected content-type
1578 header. '''
1579 name = 'content_checker'
1580 api = 2
1581
1582 class Checker:
1583 def __init__(self, type, callback):
1584 self.expected_type = type
1585 self.callback = callback
1586 self.error_str = "Expecting content type '%s', got '%s'"
1587
1588 def __call__(self, *a, **kw):
Deepak Kodihallidb1a21e2017-04-27 06:30:11 -05001589 if request.method in ['PUT', 'POST', 'PATCH'] and \
1590 self.expected_type and \
Deepak Kodihalli461367a2017-04-10 07:11:38 -05001591 self.expected_type != request.content_type:
1592 abort(415, self.error_str % (self.expected_type,
1593 request.content_type))
1594
1595 return self.callback(*a, **kw)
1596
1597 def apply(self, callback, route):
1598 content_type = getattr(
1599 route.get_undecorated_callback(), '_content_type', None)
1600
1601 return self.Checker(content_type, callback)
1602
1603
Deepak Kodihalli6e1ca532018-09-04 03:51:04 -05001604class LoggingPlugin(object):
1605 ''' Wraps a request in order to emit a log after the request is handled. '''
1606 name = 'loggingp'
1607 api = 2
1608
1609 class Logger:
1610 def __init__(self, suppress_json_logging, callback, app):
1611 self.suppress_json_logging = suppress_json_logging
1612 self.callback = callback
1613 self.app = app
Deepak Kodihalli95803682018-09-07 03:13:59 -05001614 self.logging_enabled = None
1615 self.bus = dbus.SystemBus()
1616 self.dbus_path = '/xyz/openbmc_project/logging/rest_api_logs'
Deepak Kodihalli4b412ac2018-10-15 12:45:18 -05001617 self.no_json = [
1618 '/xyz/openbmc_project/user/ldap/action/CreateConfig'
1619 ]
Deepak Kodihalli95803682018-09-07 03:13:59 -05001620 self.bus.add_signal_receiver(
1621 self.properties_changed_handler,
1622 dbus_interface=dbus.PROPERTIES_IFACE,
1623 signal_name='PropertiesChanged',
1624 path=self.dbus_path)
1625 Greenlet.spawn(self.dbus_loop)
Deepak Kodihalli6e1ca532018-09-04 03:51:04 -05001626
1627 def __call__(self, *a, **kw):
1628 resp = self.callback(*a, **kw)
Deepak Kodihalli95803682018-09-07 03:13:59 -05001629 if not self.enabled():
Deepak Kodihalli4aa10002018-09-13 11:48:45 -05001630 return resp
Deepak Kodihalli6e1ca532018-09-04 03:51:04 -05001631 if request.method == 'GET':
Deepak Kodihalli4aa10002018-09-13 11:48:45 -05001632 return resp
Deepak Kodihalli6e1ca532018-09-04 03:51:04 -05001633 json = request.json
1634 if self.suppress_json_logging:
1635 json = None
Deepak Kodihalli4b412ac2018-10-15 12:45:18 -05001636 elif any(substring in request.url for substring in self.no_json):
1637 json = None
Deepak Kodihalli6e1ca532018-09-04 03:51:04 -05001638 session = self.app.session_handler.get_session_from_cookie()
1639 user = None
1640 if "/login" in request.url:
1641 user = request.parameter_list[0]
1642 elif session is not None:
1643 user = session['user']
1644 print("{remote} user:{user} {method} {url} json:{json} {status}" \
1645 .format(
1646 user=user,
1647 remote=request.remote_addr,
1648 method=request.method,
1649 url=request.url,
1650 json=json,
1651 status=response.status))
Deepak Kodihalli4aa10002018-09-13 11:48:45 -05001652 return resp
Deepak Kodihalli6e1ca532018-09-04 03:51:04 -05001653
Deepak Kodihalli95803682018-09-07 03:13:59 -05001654 def enabled(self):
1655 if self.logging_enabled is None:
1656 try:
1657 obj = self.bus.get_object(
1658 'xyz.openbmc_project.Settings',
1659 self.dbus_path)
1660 iface = dbus.Interface(obj, dbus.PROPERTIES_IFACE)
1661 logging_enabled = iface.Get(
1662 'xyz.openbmc_project.Object.Enable',
1663 'Enabled')
1664 self.logging_enabled = logging_enabled
1665 except dbus.exceptions.DBusException:
1666 self.logging_enabled = False
1667 return self.logging_enabled
1668
1669 def dbus_loop(self):
1670 loop = gobject.MainLoop()
1671 gcontext = loop.get_context()
1672 while loop is not None:
1673 try:
1674 if gcontext.pending():
1675 gcontext.iteration()
1676 else:
1677 gevent.sleep(5)
1678 except Exception as e:
1679 break
1680
1681 def properties_changed_handler(self, interface, new, old, **kw):
1682 self.logging_enabled = new.values()[0]
1683
Deepak Kodihalli6e1ca532018-09-04 03:51:04 -05001684 def apply(self, callback, route):
1685 cb = route.get_undecorated_callback()
1686 skip = getattr(
1687 cb, 'suppress_logging', None)
1688 if skip:
1689 return callback
1690
1691 suppress_json_logging = getattr(
1692 cb, 'suppress_json_logging', None)
1693 return self.Logger(suppress_json_logging, callback, cb.app)
1694
1695
Brad Bishop2c6fc762016-08-29 15:53:25 -04001696class App(Bottle):
Deepak Kodihalli0fe213f2017-10-11 00:08:48 -05001697 def __init__(self, **kw):
Brad Bishop2c6fc762016-08-29 15:53:25 -04001698 super(App, self).__init__(autojson=False)
Deepak Kodihallib209dd12017-10-11 01:19:17 -05001699
1700 self.have_wsock = kw.get('have_wsock', False)
Alexander Filippovd08a4562018-03-20 12:02:23 +03001701 self.with_bmc_check = '--with-bmc-check' in sys.argv
Deepak Kodihallib209dd12017-10-11 01:19:17 -05001702
Brad Bishop2ddfa002016-08-29 15:11:55 -04001703 self.bus = dbus.SystemBus()
1704 self.mapper = obmc.mapper.Mapper(self.bus)
Brad Bishop080a48e2017-02-21 22:34:43 -05001705 self.error_callbacks = []
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001706
Brad Bishop87b63c12016-03-18 14:47:51 -04001707 self.install_hooks()
1708 self.install_plugins()
1709 self.create_handlers()
1710 self.install_handlers()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001711
Brad Bishop87b63c12016-03-18 14:47:51 -04001712 def install_plugins(self):
1713 # install json api plugins
1714 json_kw = {'indent': 2, 'sort_keys': True}
Brad Bishop87b63c12016-03-18 14:47:51 -04001715 self.install(AuthorizationPlugin())
Brad Bishopd0c404a2017-02-21 09:23:25 -05001716 self.install(CorsPlugin(self))
Deepak Kodihalli461367a2017-04-10 07:11:38 -05001717 self.install(ContentCheckerPlugin())
Brad Bishop080a48e2017-02-21 22:34:43 -05001718 self.install(JsonpPlugin(self, **json_kw))
1719 self.install(JsonErrorsPlugin(self, **json_kw))
1720 self.install(JsonApiResponsePlugin(self))
Brad Bishop87b63c12016-03-18 14:47:51 -04001721 self.install(JsonApiRequestPlugin())
1722 self.install(JsonApiRequestTypePlugin())
Deepak Kodihalli6e1ca532018-09-04 03:51:04 -05001723 self.install(LoggingPlugin())
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001724
Brad Bishop87b63c12016-03-18 14:47:51 -04001725 def install_hooks(self):
Brad Bishop080a48e2017-02-21 22:34:43 -05001726 self.error_handler_type = type(self.default_error_handler)
1727 self.original_error_handler = self.default_error_handler
1728 self.default_error_handler = self.error_handler_type(
1729 self.custom_error_handler, self, Bottle)
1730
Brad Bishop87b63c12016-03-18 14:47:51 -04001731 self.real_router_match = self.router.match
1732 self.router.match = self.custom_router_match
1733 self.add_hook('before_request', self.strip_extra_slashes)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001734
Brad Bishop87b63c12016-03-18 14:47:51 -04001735 def create_handlers(self):
1736 # create route handlers
1737 self.session_handler = SessionHandler(self, self.bus)
Matt Spinlerd41643e2018-02-02 13:51:38 -06001738 self.web_handler = WebHandler(self, self.bus)
Brad Bishop87b63c12016-03-18 14:47:51 -04001739 self.directory_handler = DirectoryHandler(self, self.bus)
1740 self.list_names_handler = ListNamesHandler(self, self.bus)
1741 self.list_handler = ListHandler(self, self.bus)
1742 self.method_handler = MethodHandler(self, self.bus)
1743 self.property_handler = PropertyHandler(self, self.bus)
1744 self.schema_handler = SchemaHandler(self, self.bus)
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -05001745 self.image_upload_post_handler = ImagePostHandler(self, self.bus)
1746 self.image_upload_put_handler = ImagePutHandler(self, self.bus)
Jayanth Othayoth9bc94992017-06-29 06:30:40 -05001747 self.download_dump_get_handler = DownloadDumpHandler(self, self.bus)
Dhruvaraj Subhashchandrandee2ef52018-09-05 05:36:31 -05001748 self.certificate_put_handler = CertificatePutHandler(self, self.bus)
Deepak Kodihallib209dd12017-10-11 01:19:17 -05001749 if self.have_wsock:
1750 self.event_handler = EventHandler(self, self.bus)
Deepak Kodihalli5c518f62018-04-23 03:26:38 -05001751 self.host_console_handler = HostConsoleHandler(self, self.bus)
Brad Bishop87b63c12016-03-18 14:47:51 -04001752 self.instance_handler = InstanceHandler(self, self.bus)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001753
Brad Bishop87b63c12016-03-18 14:47:51 -04001754 def install_handlers(self):
1755 self.session_handler.install()
Matt Spinlerd41643e2018-02-02 13:51:38 -06001756 self.web_handler.install()
Brad Bishop87b63c12016-03-18 14:47:51 -04001757 self.directory_handler.install()
1758 self.list_names_handler.install()
1759 self.list_handler.install()
1760 self.method_handler.install()
1761 self.property_handler.install()
1762 self.schema_handler.install()
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -05001763 self.image_upload_post_handler.install()
1764 self.image_upload_put_handler.install()
Jayanth Othayoth9bc94992017-06-29 06:30:40 -05001765 self.download_dump_get_handler.install()
Dhruvaraj Subhashchandrandee2ef52018-09-05 05:36:31 -05001766 self.certificate_put_handler.install()
Deepak Kodihallib209dd12017-10-11 01:19:17 -05001767 if self.have_wsock:
1768 self.event_handler.install()
Deepak Kodihalli5c518f62018-04-23 03:26:38 -05001769 self.host_console_handler.install()
Brad Bishop87b63c12016-03-18 14:47:51 -04001770 # this has to come last, since it matches everything
1771 self.instance_handler.install()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001772
Brad Bishop080a48e2017-02-21 22:34:43 -05001773 def install_error_callback(self, callback):
1774 self.error_callbacks.insert(0, callback)
1775
Brad Bishop87b63c12016-03-18 14:47:51 -04001776 def custom_router_match(self, environ):
1777 ''' The built-in Bottle algorithm for figuring out if a 404 or 405 is
1778 needed doesn't work for us since the instance rules match
1779 everything. This monkey-patch lets the route handler figure
1780 out which response is needed. This could be accomplished
1781 with a hook but that would require calling the router match
1782 function twice.
1783 '''
1784 route, args = self.real_router_match(environ)
1785 if isinstance(route.callback, RouteHandler):
1786 route.callback._setup(**args)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001787
Brad Bishop87b63c12016-03-18 14:47:51 -04001788 return route, args
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001789
Brad Bishop080a48e2017-02-21 22:34:43 -05001790 def custom_error_handler(self, res, error):
Gunnar Millsf01d0ba2017-10-25 20:37:24 -05001791 ''' Allow plugins to modify error responses too via this custom
Brad Bishop080a48e2017-02-21 22:34:43 -05001792 error handler. '''
1793
1794 response_object = {}
1795 response_body = {}
1796 for x in self.error_callbacks:
1797 x(error=error,
1798 response_object=response_object,
1799 response_body=response_body)
1800
1801 return response_body.get('body', "")
1802
Brad Bishop87b63c12016-03-18 14:47:51 -04001803 @staticmethod
1804 def strip_extra_slashes():
1805 path = request.environ['PATH_INFO']
1806 trailing = ("", "/")[path[-1] == '/']
CamVan Nguyen249d1322018-03-05 10:08:33 -06001807 parts = list(filter(bool, path.split('/')))
Brad Bishop87b63c12016-03-18 14:47:51 -04001808 request.environ['PATH_INFO'] = '/' + '/'.join(parts) + trailing