12c6fc760SBrad Bishop# Contributors Listed Below - COPYRIGHT 2016 22c6fc760SBrad Bishop# [+] International Business Machines Corp. 32c6fc760SBrad Bishop# 42c6fc760SBrad Bishop# 52c6fc760SBrad Bishop# Licensed under the Apache License, Version 2.0 (the "License"); 62c6fc760SBrad Bishop# you may not use this file except in compliance with the License. 72c6fc760SBrad Bishop# You may obtain a copy of the License at 82c6fc760SBrad Bishop# 92c6fc760SBrad Bishop# http://www.apache.org/licenses/LICENSE-2.0 102c6fc760SBrad Bishop# 112c6fc760SBrad Bishop# Unless required by applicable law or agreed to in writing, software 122c6fc760SBrad Bishop# distributed under the License is distributed on an "AS IS" BASIS, 132c6fc760SBrad Bishop# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 142c6fc760SBrad Bishop# implied. See the License for the specific language governing 152c6fc760SBrad Bishop# permissions and limitations under the License. 162c6fc760SBrad Bishop 172c6fc760SBrad Bishopimport os 18d08a4569SAlexander Filippovimport sys 192c6fc760SBrad Bishopimport dbus 202c6fc760SBrad Bishopimport dbus.exceptions 212c6fc760SBrad Bishopimport json 222c6fc760SBrad Bishopfrom xml.etree import ElementTree 232c6fc760SBrad Bishopfrom bottle import Bottle, abort, request, response, JSONPlugin, HTTPError 249bc94997SJayanth Othayothfrom bottle import static_file 252c6fc760SBrad Bishopimport obmc.utils.misc 262c6fc760SBrad Bishopfrom obmc.dbuslib.introspection import IntrospectionNodeParser 272c6fc760SBrad Bishopimport obmc.mapper 282c6fc760SBrad Bishopimport spwd 292c6fc760SBrad Bishopimport grp 302c6fc760SBrad Bishopimport crypt 311af301a0SDeepak Kodihalliimport tempfile 320bdef95dSLeonel Gonzalezimport re 33d41643ecSMatt Spinlerimport mimetypes 34639b502eSDeepak Kodihallihave_wsock = True 35639b502eSDeepak Kodihallitry: 36639b502eSDeepak Kodihalli from geventwebsocket import WebSocketError 37639b502eSDeepak Kodihalliexcept ImportError: 38639b502eSDeepak Kodihalli have_wsock = False 39639b502eSDeepak Kodihalliif have_wsock: 40639b502eSDeepak Kodihalli from dbus.mainloop.glib import DBusGMainLoop 41639b502eSDeepak Kodihalli DBusGMainLoop(set_as_default=True) 42249d1327SCamVan Nguyen # TODO: openbmc/openbmc#2994 remove python 2 support 43249d1327SCamVan Nguyen try: # python 2 44639b502eSDeepak Kodihalli import gobject 45249d1327SCamVan Nguyen except ImportError: # python 3 46249d1327SCamVan Nguyen from gi.repository import GObject as gobject 47639b502eSDeepak Kodihalli import gevent 485c518f63SDeepak Kodihalli from gevent import socket 495c518f63SDeepak Kodihalli from gevent import Greenlet 502c6fc760SBrad Bishop 51f92cf4dbSAdriana KobylakDBUS_UNKNOWN_INTERFACE = 'org.freedesktop.DBus.Error.UnknownInterface' 522c6fc760SBrad BishopDBUS_UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod' 53a8b05d16SAdriana KobylakDBUS_PROPERTY_READONLY = 'org.freedesktop.DBus.Error.PropertyReadOnly' 542c6fc760SBrad BishopDBUS_INVALID_ARGS = 'org.freedesktop.DBus.Error.InvalidArgs' 552c6fc760SBrad BishopDBUS_TYPE_ERROR = 'org.freedesktop.DBus.Python.TypeError' 566075bb48SDeepak KodihalliDELETE_IFACE = 'xyz.openbmc_project.Object.Delete' 5753693891SAdriana KobylakSOFTWARE_PATH = '/xyz/openbmc_project/software' 58bec10c20SJayashankar PadathWEBSOCKET_TIMEOUT = 45 592c6fc760SBrad Bishop 602c6fc760SBrad Bishop_4034_msg = "The specified %s cannot be %s: '%s'" 612c6fc760SBrad Bishop 62d41643ecSMatt Spinlerwww_base_path = '/usr/share/www/' 63d41643ecSMatt Spinler 642c6fc760SBrad Bishop 652c6fc760SBrad Bishopdef valid_user(session, *a, **kw): 662c6fc760SBrad Bishop ''' Authorization plugin callback that checks 672c6fc760SBrad Bishop that the user is logged in. ''' 682c6fc760SBrad Bishop if session is None: 69dc3fbfa7SBrad Bishop abort(401, 'Login required') 702c6fc760SBrad Bishop 712c6fc760SBrad Bishop 720bdef95dSLeonel Gonzalezdef get_type_signature_by_introspection(bus, service, object_path, 730bdef95dSLeonel Gonzalez property_name): 740bdef95dSLeonel Gonzalez obj = bus.get_object(service, object_path) 750bdef95dSLeonel Gonzalez iface = dbus.Interface(obj, 'org.freedesktop.DBus.Introspectable') 760bdef95dSLeonel Gonzalez xml_string = iface.Introspect() 770bdef95dSLeonel Gonzalez for child in ElementTree.fromstring(xml_string): 780bdef95dSLeonel Gonzalez # Iterate over each interfaces's properties to find 790bdef95dSLeonel Gonzalez # matching property_name, and return its signature string 800bdef95dSLeonel Gonzalez if child.tag == 'interface': 810bdef95dSLeonel Gonzalez for i in child.iter(): 820bdef95dSLeonel Gonzalez if ('name' in i.attrib) and \ 830bdef95dSLeonel Gonzalez (i.attrib['name'] == property_name): 840bdef95dSLeonel Gonzalez type_signature = i.attrib['type'] 850bdef95dSLeonel Gonzalez return type_signature 860bdef95dSLeonel Gonzalez 870bdef95dSLeonel Gonzalez 88a6a8a4c1SRatan Guptadef get_method_signature(bus, service, object_path, interface, method): 89a6a8a4c1SRatan Gupta obj = bus.get_object(service, object_path) 90a6a8a4c1SRatan Gupta iface = dbus.Interface(obj, 'org.freedesktop.DBus.Introspectable') 91a6a8a4c1SRatan Gupta xml_string = iface.Introspect() 92a6a8a4c1SRatan Gupta arglist = [] 93a6a8a4c1SRatan Gupta 94a6a8a4c1SRatan Gupta root = ElementTree.fromstring(xml_string) 95a6a8a4c1SRatan Gupta for dbus_intf in root.findall('interface'): 96a6a8a4c1SRatan Gupta if (dbus_intf.get('name') == interface): 97a6a8a4c1SRatan Gupta for dbus_method in dbus_intf.findall('method'): 98a6a8a4c1SRatan Gupta if(dbus_method.get('name') == method): 99a6a8a4c1SRatan Gupta for arg in dbus_method.findall('arg'): 100*3c6d49a6SRatan Gupta if (arg.get('direction') == 'in'): 101a6a8a4c1SRatan Gupta arglist.append(arg.get('type')) 102a6a8a4c1SRatan Gupta return arglist 103a6a8a4c1SRatan Gupta 104a6a8a4c1SRatan Gupta 1050bdef95dSLeonel Gonzalezdef split_struct_signature(signature): 1060bdef95dSLeonel Gonzalez struct_regex = r'(b|y|n|i|x|q|u|t|d|s|a\(.+?\)|\(.+?\))|a\{.+?\}+?' 1070bdef95dSLeonel Gonzalez struct_matches = re.findall(struct_regex, signature) 1080bdef95dSLeonel Gonzalez return struct_matches 1090bdef95dSLeonel Gonzalez 1100bdef95dSLeonel Gonzalez 1110bdef95dSLeonel Gonzalezdef convert_type(signature, value): 1120bdef95dSLeonel Gonzalez # Basic Types 1130bdef95dSLeonel Gonzalez converted_value = None 1140bdef95dSLeonel Gonzalez converted_container = None 115249d1327SCamVan Nguyen # TODO: openbmc/openbmc#2994 remove python 2 support 116249d1327SCamVan Nguyen try: # python 2 1170bdef95dSLeonel Gonzalez basic_types = {'b': bool, 'y': dbus.Byte, 'n': dbus.Int16, 'i': int, 1180bdef95dSLeonel Gonzalez 'x': long, 'q': dbus.UInt16, 'u': dbus.UInt32, 1190bdef95dSLeonel Gonzalez 't': dbus.UInt64, 'd': float, 's': str} 120249d1327SCamVan Nguyen except NameError: # python 3 121249d1327SCamVan Nguyen basic_types = {'b': bool, 'y': dbus.Byte, 'n': dbus.Int16, 'i': int, 122249d1327SCamVan Nguyen 'x': int, 'q': dbus.UInt16, 'u': dbus.UInt32, 123249d1327SCamVan Nguyen 't': dbus.UInt64, 'd': float, 's': str} 1240bdef95dSLeonel Gonzalez array_matches = re.match(r'a\((\S+)\)', signature) 1250bdef95dSLeonel Gonzalez struct_matches = re.match(r'\((\S+)\)', signature) 1260bdef95dSLeonel Gonzalez dictionary_matches = re.match(r'a{(\S+)}', signature) 1270bdef95dSLeonel Gonzalez if signature in basic_types: 1280bdef95dSLeonel Gonzalez converted_value = basic_types[signature](value) 1290bdef95dSLeonel Gonzalez return converted_value 1300bdef95dSLeonel Gonzalez # Array 1310bdef95dSLeonel Gonzalez if array_matches: 1320bdef95dSLeonel Gonzalez element_type = array_matches.group(1) 1330bdef95dSLeonel Gonzalez converted_container = list() 1340bdef95dSLeonel Gonzalez # Test if value is a list 1350bdef95dSLeonel Gonzalez # to avoid iterating over each character in a string. 1360bdef95dSLeonel Gonzalez # Iterate over each item and convert type 1370bdef95dSLeonel Gonzalez if isinstance(value, list): 1380bdef95dSLeonel Gonzalez for i in value: 1390bdef95dSLeonel Gonzalez converted_element = convert_type(element_type, i) 1400bdef95dSLeonel Gonzalez converted_container.append(converted_element) 1410bdef95dSLeonel Gonzalez # Convert non-sequence to expected type, and append to list 1420bdef95dSLeonel Gonzalez else: 1430bdef95dSLeonel Gonzalez converted_element = convert_type(element_type, value) 1440bdef95dSLeonel Gonzalez converted_container.append(converted_element) 1450bdef95dSLeonel Gonzalez return converted_container 1460bdef95dSLeonel Gonzalez # Struct 1470bdef95dSLeonel Gonzalez if struct_matches: 1480bdef95dSLeonel Gonzalez element_types = struct_matches.group(1) 1490bdef95dSLeonel Gonzalez split_element_types = split_struct_signature(element_types) 1500bdef95dSLeonel Gonzalez converted_container = list() 1510bdef95dSLeonel Gonzalez # Test if value is a list 1520bdef95dSLeonel Gonzalez if isinstance(value, list): 1530bdef95dSLeonel Gonzalez for index, val in enumerate(value): 1540bdef95dSLeonel Gonzalez converted_element = convert_type(split_element_types[index], 1550bdef95dSLeonel Gonzalez value[index]) 1560bdef95dSLeonel Gonzalez converted_container.append(converted_element) 1570bdef95dSLeonel Gonzalez else: 1580bdef95dSLeonel Gonzalez converted_element = convert_type(element_types, value) 1590bdef95dSLeonel Gonzalez converted_container.append(converted_element) 1600bdef95dSLeonel Gonzalez return tuple(converted_container) 1610bdef95dSLeonel Gonzalez # Dictionary 1620bdef95dSLeonel Gonzalez if dictionary_matches: 1630bdef95dSLeonel Gonzalez element_types = dictionary_matches.group(1) 1640bdef95dSLeonel Gonzalez split_element_types = split_struct_signature(element_types) 1650bdef95dSLeonel Gonzalez converted_container = dict() 1660bdef95dSLeonel Gonzalez # Convert each element of dict 167249d1327SCamVan Nguyen for key, val in value.items(): 1680bdef95dSLeonel Gonzalez converted_key = convert_type(split_element_types[0], key) 1690bdef95dSLeonel Gonzalez converted_val = convert_type(split_element_types[1], val) 1700bdef95dSLeonel Gonzalez converted_container[converted_key] = converted_val 1710bdef95dSLeonel Gonzalez return converted_container 1720bdef95dSLeonel Gonzalez 1730bdef95dSLeonel Gonzalez 174bec10c20SJayashankar Padathdef send_ws_ping(wsock, timeout) : 175bec10c20SJayashankar Padath # Most webservers close websockets after 60 seconds of 176bec10c20SJayashankar Padath # inactivity. Make sure to send a ping before that. 177bec10c20SJayashankar Padath payload = "ping" 178bec10c20SJayashankar Padath # the ping payload can be anything, the receiver has to just 179bec10c20SJayashankar Padath # return the same back. 180bec10c20SJayashankar Padath while True: 181bec10c20SJayashankar Padath gevent.sleep(timeout) 182bec10c20SJayashankar Padath try: 183bec10c20SJayashankar Padath if wsock: 184bec10c20SJayashankar Padath wsock.send_frame(payload, wsock.OPCODE_PING) 185bec10c20SJayashankar Padath except Exception as e: 186bec10c20SJayashankar Padath wsock.close() 187bec10c20SJayashankar Padath return 188bec10c20SJayashankar Padath 189bec10c20SJayashankar Padath 1902c6fc760SBrad Bishopclass UserInGroup: 1912c6fc760SBrad Bishop ''' Authorization plugin callback that checks that the user is logged in 1922c6fc760SBrad Bishop and a member of a group. ''' 1932c6fc760SBrad Bishop def __init__(self, group): 1942c6fc760SBrad Bishop self.group = group 1952c6fc760SBrad Bishop 1962c6fc760SBrad Bishop def __call__(self, session, *a, **kw): 1972c6fc760SBrad Bishop valid_user(session, *a, **kw) 1982c6fc760SBrad Bishop res = False 1992c6fc760SBrad Bishop 2002c6fc760SBrad Bishop try: 2012c6fc760SBrad Bishop res = session['user'] in grp.getgrnam(self.group)[3] 2022c6fc760SBrad Bishop except KeyError: 2032c6fc760SBrad Bishop pass 2042c6fc760SBrad Bishop 2052c6fc760SBrad Bishop if not res: 2062c6fc760SBrad Bishop abort(403, 'Insufficient access') 2072c6fc760SBrad Bishop 2082c6fc760SBrad Bishop 2092c6fc760SBrad Bishopclass RouteHandler(object): 2102c6fc760SBrad Bishop _require_auth = obmc.utils.misc.makelist(valid_user) 211d0c404a6SBrad Bishop _enable_cors = True 2122c6fc760SBrad Bishop 21383afbaffSDeepak Kodihalli def __init__(self, app, bus, verbs, rules, content_type=''): 2142c6fc760SBrad Bishop self.app = app 2152c6fc760SBrad Bishop self.bus = bus 2162c6fc760SBrad Bishop self.mapper = obmc.mapper.Mapper(bus) 2172c6fc760SBrad Bishop self._verbs = obmc.utils.misc.makelist(verbs) 2182c6fc760SBrad Bishop self._rules = rules 21983afbaffSDeepak Kodihalli self._content_type = content_type 2202c6fc760SBrad Bishop 22188c76a4dSBrad Bishop if 'GET' in self._verbs: 22288c76a4dSBrad Bishop self._verbs = list(set(self._verbs + ['HEAD'])) 223d4c1c55cSBrad Bishop if 'OPTIONS' not in self._verbs: 224d4c1c55cSBrad Bishop self._verbs.append('OPTIONS') 22588c76a4dSBrad Bishop 2262c6fc760SBrad Bishop def _setup(self, **kw): 2272c6fc760SBrad Bishop request.route_data = {} 228d4c1c55cSBrad Bishop 2292c6fc760SBrad Bishop if request.method in self._verbs: 230d4c1c55cSBrad Bishop if request.method != 'OPTIONS': 2312c6fc760SBrad Bishop return self.setup(**kw) 23288c76a4dSBrad Bishop 233d4c1c55cSBrad Bishop # Javascript implementations will not send credentials 234d4c1c55cSBrad Bishop # with an OPTIONS request. Don't help malicious clients 235d4c1c55cSBrad Bishop # by checking the path here and returning a 404 if the 236d4c1c55cSBrad Bishop # path doesn't exist. 237d4c1c55cSBrad Bishop return None 23888c76a4dSBrad Bishop 239d4c1c55cSBrad Bishop # Return 405 2402c6fc760SBrad Bishop raise HTTPError( 2412c6fc760SBrad Bishop 405, "Method not allowed.", Allow=','.join(self._verbs)) 2422c6fc760SBrad Bishop 2432c6fc760SBrad Bishop def __call__(self, **kw): 2442c6fc760SBrad Bishop return getattr(self, 'do_' + request.method.lower())(**kw) 2452c6fc760SBrad Bishop 24688c76a4dSBrad Bishop def do_head(self, **kw): 24788c76a4dSBrad Bishop return self.do_get(**kw) 24888c76a4dSBrad Bishop 249d4c1c55cSBrad Bishop def do_options(self, **kw): 250d4c1c55cSBrad Bishop for v in self._verbs: 251d4c1c55cSBrad Bishop response.set_header( 252d4c1c55cSBrad Bishop 'Allow', 253d4c1c55cSBrad Bishop ','.join(self._verbs)) 254d4c1c55cSBrad Bishop return None 255d4c1c55cSBrad Bishop 2562c6fc760SBrad Bishop def install(self): 2572c6fc760SBrad Bishop self.app.route( 2582c6fc760SBrad Bishop self._rules, callback=self, 259d4c1c55cSBrad Bishop method=['OPTIONS', 'GET', 'PUT', 'PATCH', 'POST', 'DELETE']) 2602c6fc760SBrad Bishop 2612c6fc760SBrad Bishop @staticmethod 2622c6fc760SBrad Bishop def try_mapper_call(f, callback=None, **kw): 2632c6fc760SBrad Bishop try: 2642c6fc760SBrad Bishop return f(**kw) 265249d1327SCamVan Nguyen except dbus.exceptions.DBusException as e: 266fce7756cSBrad Bishop if e.get_dbus_name() == \ 267fce7756cSBrad Bishop 'org.freedesktop.DBus.Error.ObjectPathInUse': 268fce7756cSBrad Bishop abort(503, str(e)) 2692c6fc760SBrad Bishop if e.get_dbus_name() != obmc.mapper.MAPPER_NOT_FOUND: 2702c6fc760SBrad Bishop raise 2712c6fc760SBrad Bishop if callback is None: 2722c6fc760SBrad Bishop def callback(e, **kw): 2732c6fc760SBrad Bishop abort(404, str(e)) 2742c6fc760SBrad Bishop 2752c6fc760SBrad Bishop callback(e, **kw) 2762c6fc760SBrad Bishop 2772c6fc760SBrad Bishop @staticmethod 2782c6fc760SBrad Bishop def try_properties_interface(f, *a): 2792c6fc760SBrad Bishop try: 2802c6fc760SBrad Bishop return f(*a) 281249d1327SCamVan Nguyen except dbus.exceptions.DBusException as e: 282f92cf4dbSAdriana Kobylak if DBUS_UNKNOWN_INTERFACE in e.get_dbus_name(): 2832c6fc760SBrad Bishop # interface doesn't have any properties 2842c6fc760SBrad Bishop return None 2852c6fc760SBrad Bishop if DBUS_UNKNOWN_METHOD == e.get_dbus_name(): 2862c6fc760SBrad Bishop # properties interface not implemented at all 2872c6fc760SBrad Bishop return None 2882c6fc760SBrad Bishop raise 2892c6fc760SBrad Bishop 2902c6fc760SBrad Bishop 2912c6fc760SBrad Bishopclass DirectoryHandler(RouteHandler): 2922c6fc760SBrad Bishop verbs = 'GET' 2932c6fc760SBrad Bishop rules = '<path:path>/' 2946e1ca530SDeepak Kodihalli suppress_logging = True 2952c6fc760SBrad Bishop 2962c6fc760SBrad Bishop def __init__(self, app, bus): 2972c6fc760SBrad Bishop super(DirectoryHandler, self).__init__( 298c431e1afSBrad Bishop app, bus, self.verbs, self.rules) 2992c6fc760SBrad Bishop 3002c6fc760SBrad Bishop def find(self, path='/'): 3012c6fc760SBrad Bishop return self.try_mapper_call( 3022c6fc760SBrad Bishop self.mapper.get_subtree_paths, path=path, depth=1) 3032c6fc760SBrad Bishop 3042c6fc760SBrad Bishop def setup(self, path='/'): 3052c6fc760SBrad Bishop request.route_data['map'] = self.find(path) 3062c6fc760SBrad Bishop 3072c6fc760SBrad Bishop def do_get(self, path='/'): 3082c6fc760SBrad Bishop return request.route_data['map'] 3092c6fc760SBrad Bishop 3102c6fc760SBrad Bishop 3112c6fc760SBrad Bishopclass ListNamesHandler(RouteHandler): 3122c6fc760SBrad Bishop verbs = 'GET' 3132c6fc760SBrad Bishop rules = ['/list', '<path:path>/list'] 3146e1ca530SDeepak Kodihalli suppress_logging = True 3152c6fc760SBrad Bishop 3162c6fc760SBrad Bishop def __init__(self, app, bus): 3172c6fc760SBrad Bishop super(ListNamesHandler, self).__init__( 318c431e1afSBrad Bishop app, bus, self.verbs, self.rules) 3192c6fc760SBrad Bishop 3202c6fc760SBrad Bishop def find(self, path='/'): 321249d1327SCamVan Nguyen return list(self.try_mapper_call( 322249d1327SCamVan Nguyen self.mapper.get_subtree, path=path).keys()) 3232c6fc760SBrad Bishop 3242c6fc760SBrad Bishop def setup(self, path='/'): 3252c6fc760SBrad Bishop request.route_data['map'] = self.find(path) 3262c6fc760SBrad Bishop 3272c6fc760SBrad Bishop def do_get(self, path='/'): 3282c6fc760SBrad Bishop return request.route_data['map'] 3292c6fc760SBrad Bishop 3302c6fc760SBrad Bishop 3312c6fc760SBrad Bishopclass ListHandler(RouteHandler): 3322c6fc760SBrad Bishop verbs = 'GET' 3332c6fc760SBrad Bishop rules = ['/enumerate', '<path:path>/enumerate'] 3346e1ca530SDeepak Kodihalli suppress_logging = True 3352c6fc760SBrad Bishop 3362c6fc760SBrad Bishop def __init__(self, app, bus): 3372c6fc760SBrad Bishop super(ListHandler, self).__init__( 338c431e1afSBrad Bishop app, bus, self.verbs, self.rules) 3392c6fc760SBrad Bishop 3402c6fc760SBrad Bishop def find(self, path='/'): 3412c6fc760SBrad Bishop return self.try_mapper_call( 3422c6fc760SBrad Bishop self.mapper.get_subtree, path=path) 3432c6fc760SBrad Bishop 3442c6fc760SBrad Bishop def setup(self, path='/'): 3452c6fc760SBrad Bishop request.route_data['map'] = self.find(path) 3462c6fc760SBrad Bishop 3472c6fc760SBrad Bishop def do_get(self, path='/'): 3482c6fc760SBrad Bishop return {x: y for x, y in self.mapper.enumerate_subtree( 3492c6fc760SBrad Bishop path, 3502c6fc760SBrad Bishop mapper_data=request.route_data['map']).dataitems()} 3512c6fc760SBrad Bishop 3522c6fc760SBrad Bishop 3532c6fc760SBrad Bishopclass MethodHandler(RouteHandler): 3542c6fc760SBrad Bishop verbs = 'POST' 3552c6fc760SBrad Bishop rules = '<path:path>/action/<method>' 3562c6fc760SBrad Bishop request_type = list 35783afbaffSDeepak Kodihalli content_type = 'application/json' 3582c6fc760SBrad Bishop 3592c6fc760SBrad Bishop def __init__(self, app, bus): 3602c6fc760SBrad Bishop super(MethodHandler, self).__init__( 36183afbaffSDeepak Kodihalli app, bus, self.verbs, self.rules, self.content_type) 362a6a8a4c1SRatan Gupta self.service = '' 363a6a8a4c1SRatan Gupta self.interface = '' 3642c6fc760SBrad Bishop 3652c6fc760SBrad Bishop def find(self, path, method): 3663a00b1fdSSaqib Khan method_list = [] 367313aadb3SGunnar Mills buses = self.try_mapper_call( 3682c6fc760SBrad Bishop self.mapper.get_object, path=path) 369313aadb3SGunnar Mills for items in buses.items(): 3702c6fc760SBrad Bishop m = self.find_method_on_bus(path, method, *items) 3712c6fc760SBrad Bishop if m: 3723a00b1fdSSaqib Khan method_list.append(m) 373765c2c8bSNagaraju Goruganti if method_list: 3743a00b1fdSSaqib Khan return method_list 3752c6fc760SBrad Bishop 3762c6fc760SBrad Bishop abort(404, _4034_msg % ('method', 'found', method)) 3772c6fc760SBrad Bishop 3782c6fc760SBrad Bishop def setup(self, path, method): 3793a00b1fdSSaqib Khan request.route_data['map'] = self.find(path, method) 3802c6fc760SBrad Bishop 381bc0c6738SMarri Devender Rao def do_post(self, path, method, retry=True): 3822c6fc760SBrad Bishop try: 383765c2c8bSNagaraju Goruganti args = [] 3842c6fc760SBrad Bishop if request.parameter_list: 385765c2c8bSNagaraju Goruganti args = request.parameter_list 386765c2c8bSNagaraju Goruganti # To see if the return type is capable of being merged 387765c2c8bSNagaraju Goruganti if len(request.route_data['map']) > 1: 388765c2c8bSNagaraju Goruganti results = None 389765c2c8bSNagaraju Goruganti for item in request.route_data['map']: 390765c2c8bSNagaraju Goruganti tmp = item(*args) 391765c2c8bSNagaraju Goruganti if not results: 392765c2c8bSNagaraju Goruganti if tmp is not None: 393765c2c8bSNagaraju Goruganti results = type(tmp)() 394765c2c8bSNagaraju Goruganti if isinstance(results, dict): 395765c2c8bSNagaraju Goruganti results = results.update(tmp) 396765c2c8bSNagaraju Goruganti elif isinstance(results, list): 397765c2c8bSNagaraju Goruganti results = results + tmp 398765c2c8bSNagaraju Goruganti elif isinstance(results, type(None)): 399765c2c8bSNagaraju Goruganti results = None 4002c6fc760SBrad Bishop else: 401765c2c8bSNagaraju Goruganti abort(501, 'Don\'t know how to merge method call ' 402765c2c8bSNagaraju Goruganti 'results of {}'.format(type(tmp))) 403765c2c8bSNagaraju Goruganti return results 404765c2c8bSNagaraju Goruganti # There is only one method 405765c2c8bSNagaraju Goruganti return request.route_data['map'][0](*args) 4062c6fc760SBrad Bishop 407249d1327SCamVan Nguyen except dbus.exceptions.DBusException as e: 408a6a8a4c1SRatan Gupta paramlist = [] 409b7fca9bcSBrad Bishop if e.get_dbus_name() == DBUS_INVALID_ARGS and retry: 410a6a8a4c1SRatan Gupta 411a6a8a4c1SRatan Gupta signature_list = get_method_signature(self.bus, self.service, 412a6a8a4c1SRatan Gupta path, self.interface, 413a6a8a4c1SRatan Gupta method) 414a6a8a4c1SRatan Gupta if not signature_list: 415a6a8a4c1SRatan Gupta abort(400, "Failed to get method signature: %s" % str(e)) 416a6a8a4c1SRatan Gupta if len(signature_list) != len(request.parameter_list): 417a6a8a4c1SRatan Gupta abort(400, "Invalid number of args") 418a6a8a4c1SRatan Gupta converted_value = None 419a6a8a4c1SRatan Gupta try: 420a6a8a4c1SRatan Gupta for index, expected_type in enumerate(signature_list): 421a6a8a4c1SRatan Gupta value = request.parameter_list[index] 422a6a8a4c1SRatan Gupta converted_value = convert_type(expected_type, value) 423a6a8a4c1SRatan Gupta paramlist.append(converted_value) 424a6a8a4c1SRatan Gupta request.parameter_list = paramlist 425bc0c6738SMarri Devender Rao self.do_post(path, method, False) 426a6a8a4c1SRatan Gupta return 427a6a8a4c1SRatan Gupta except Exception as ex: 428ab404fa2SNagaraju Goruganti abort(400, "Bad Request/Invalid Args given") 4292c6fc760SBrad Bishop abort(400, str(e)) 430a6a8a4c1SRatan Gupta 4312c6fc760SBrad Bishop if e.get_dbus_name() == DBUS_TYPE_ERROR: 4322c6fc760SBrad Bishop abort(400, str(e)) 4332c6fc760SBrad Bishop raise 4342c6fc760SBrad Bishop 4352c6fc760SBrad Bishop @staticmethod 4362c6fc760SBrad Bishop def find_method_in_interface(method, obj, interface, methods): 4372c6fc760SBrad Bishop if methods is None: 4382c6fc760SBrad Bishop return None 4392c6fc760SBrad Bishop 440249d1327SCamVan Nguyen method = obmc.utils.misc.find_case_insensitive(method, list(methods.keys())) 4412c6fc760SBrad Bishop if method is not None: 4422c6fc760SBrad Bishop iface = dbus.Interface(obj, interface) 4432c6fc760SBrad Bishop return iface.get_dbus_method(method) 4442c6fc760SBrad Bishop 4452c6fc760SBrad Bishop def find_method_on_bus(self, path, method, bus, interfaces): 4462c6fc760SBrad Bishop obj = self.bus.get_object(bus, path, introspect=False) 4472c6fc760SBrad Bishop iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE) 4482c6fc760SBrad Bishop data = iface.Introspect() 4492c6fc760SBrad Bishop parser = IntrospectionNodeParser( 4502c6fc760SBrad Bishop ElementTree.fromstring(data), 451aeb995daSBrad Bishop intf_match=lambda x: x in interfaces) 452249d1327SCamVan Nguyen for x, y in parser.get_interfaces().items(): 4532c6fc760SBrad Bishop m = self.find_method_in_interface( 4542c6fc760SBrad Bishop method, obj, x, y.get('method')) 4552c6fc760SBrad Bishop if m: 456a6a8a4c1SRatan Gupta self.service = bus 457a6a8a4c1SRatan Gupta self.interface = x 4582c6fc760SBrad Bishop return m 4592c6fc760SBrad Bishop 4602c6fc760SBrad Bishop 4612c6fc760SBrad Bishopclass PropertyHandler(RouteHandler): 4622c6fc760SBrad Bishop verbs = ['PUT', 'GET'] 4632c6fc760SBrad Bishop rules = '<path:path>/attr/<prop>' 46483afbaffSDeepak Kodihalli content_type = 'application/json' 4652c6fc760SBrad Bishop 4662c6fc760SBrad Bishop def __init__(self, app, bus): 4672c6fc760SBrad Bishop super(PropertyHandler, self).__init__( 46883afbaffSDeepak Kodihalli app, bus, self.verbs, self.rules, self.content_type) 4692c6fc760SBrad Bishop 4702c6fc760SBrad Bishop def find(self, path, prop): 4712c6fc760SBrad Bishop self.app.instance_handler.setup(path) 4722c6fc760SBrad Bishop obj = self.app.instance_handler.do_get(path) 47356ad87f3SBrad Bishop real_name = obmc.utils.misc.find_case_insensitive( 474249d1327SCamVan Nguyen prop, list(obj.keys())) 4752c6fc760SBrad Bishop 47656ad87f3SBrad Bishop if not real_name: 47756ad87f3SBrad Bishop if request.method == 'PUT': 47856ad87f3SBrad Bishop abort(403, _4034_msg % ('property', 'created', prop)) 47956ad87f3SBrad Bishop else: 48056ad87f3SBrad Bishop abort(404, _4034_msg % ('property', 'found', prop)) 48156ad87f3SBrad Bishop return real_name, {path: obj} 4822c6fc760SBrad Bishop 4832c6fc760SBrad Bishop def setup(self, path, prop): 48456ad87f3SBrad Bishop name, obj = self.find(path, prop) 48556ad87f3SBrad Bishop request.route_data['obj'] = obj 48656ad87f3SBrad Bishop request.route_data['name'] = name 4872c6fc760SBrad Bishop 4882c6fc760SBrad Bishop def do_get(self, path, prop): 48956ad87f3SBrad Bishop name = request.route_data['name'] 49056ad87f3SBrad Bishop return request.route_data['obj'][path][name] 4912c6fc760SBrad Bishop 492bc0c6738SMarri Devender Rao def do_put(self, path, prop, value=None, retry=True): 4932c6fc760SBrad Bishop if value is None: 4942c6fc760SBrad Bishop value = request.parameter_list 4952c6fc760SBrad Bishop 4962c6fc760SBrad Bishop prop, iface, properties_iface = self.get_host_interface( 4972c6fc760SBrad Bishop path, prop, request.route_data['map'][path]) 4982c6fc760SBrad Bishop try: 4992c6fc760SBrad Bishop properties_iface.Set(iface, prop, value) 500249d1327SCamVan Nguyen except ValueError as e: 5012c6fc760SBrad Bishop abort(400, str(e)) 502249d1327SCamVan Nguyen except dbus.exceptions.DBusException as e: 503a8b05d16SAdriana Kobylak if e.get_dbus_name() == DBUS_PROPERTY_READONLY: 504a8b05d16SAdriana Kobylak abort(403, str(e)) 505b7fca9bcSBrad Bishop if e.get_dbus_name() == DBUS_INVALID_ARGS and retry: 5060bdef95dSLeonel Gonzalez bus_name = properties_iface.bus_name 5070bdef95dSLeonel Gonzalez expected_type = get_type_signature_by_introspection(self.bus, 5080bdef95dSLeonel Gonzalez bus_name, 5090bdef95dSLeonel Gonzalez path, 5100bdef95dSLeonel Gonzalez prop) 5110bdef95dSLeonel Gonzalez if not expected_type: 5120bdef95dSLeonel Gonzalez abort(403, "Failed to get expected type: %s" % str(e)) 5130bdef95dSLeonel Gonzalez converted_value = None 5140bdef95dSLeonel Gonzalez try: 5150bdef95dSLeonel Gonzalez converted_value = convert_type(expected_type, value) 5160bdef95dSLeonel Gonzalez except Exception as ex: 5170bdef95dSLeonel Gonzalez abort(403, "Failed to convert %s to type %s" % 5180bdef95dSLeonel Gonzalez (value, expected_type)) 5191eea5c3bSLei YU try: 5201eea5c3bSLei YU self.do_put(path, prop, converted_value, False) 5211eea5c3bSLei YU return 5221eea5c3bSLei YU except Exception as ex: 5231eea5c3bSLei YU abort(403, str(ex)) 5241eea5c3bSLei YU 5252c6fc760SBrad Bishop abort(403, str(e)) 5262c6fc760SBrad Bishop raise 5272c6fc760SBrad Bishop 5282c6fc760SBrad Bishop def get_host_interface(self, path, prop, bus_info): 529249d1327SCamVan Nguyen for bus, interfaces in bus_info.items(): 5302c6fc760SBrad Bishop obj = self.bus.get_object(bus, path, introspect=True) 5312c6fc760SBrad Bishop properties_iface = dbus.Interface( 5322c6fc760SBrad Bishop obj, dbus_interface=dbus.PROPERTIES_IFACE) 5332c6fc760SBrad Bishop 53444573ab2SAdriana Kobylak try: 5352c6fc760SBrad Bishop info = self.get_host_interface_on_bus( 5362c6fc760SBrad Bishop path, prop, properties_iface, bus, interfaces) 53744573ab2SAdriana Kobylak except Exception: 53844573ab2SAdriana Kobylak continue 5392c6fc760SBrad Bishop if info is not None: 5402c6fc760SBrad Bishop prop, iface = info 5412c6fc760SBrad Bishop return prop, iface, properties_iface 5422c6fc760SBrad Bishop 5432c6fc760SBrad Bishop def get_host_interface_on_bus(self, path, prop, iface, bus, interfaces): 5442c6fc760SBrad Bishop for i in interfaces: 5452c6fc760SBrad Bishop properties = self.try_properties_interface(iface.GetAll, i) 54669cb6d18SBrad Bishop if not properties: 5472c6fc760SBrad Bishop continue 548409f671bSLeonel Gonzalez match = obmc.utils.misc.find_case_insensitive( 549249d1327SCamVan Nguyen prop, list(properties.keys())) 550409f671bSLeonel Gonzalez if match is None: 5512c6fc760SBrad Bishop continue 552409f671bSLeonel Gonzalez prop = match 5532c6fc760SBrad Bishop return prop, i 5542c6fc760SBrad Bishop 5552c6fc760SBrad Bishop 5562c6fc760SBrad Bishopclass SchemaHandler(RouteHandler): 5572c6fc760SBrad Bishop verbs = ['GET'] 5582c6fc760SBrad Bishop rules = '<path:path>/schema' 5596e1ca530SDeepak Kodihalli suppress_logging = True 5602c6fc760SBrad Bishop 5612c6fc760SBrad Bishop def __init__(self, app, bus): 5622c6fc760SBrad Bishop super(SchemaHandler, self).__init__( 563529029b5SBrad Bishop app, bus, self.verbs, self.rules) 5642c6fc760SBrad Bishop 5652c6fc760SBrad Bishop def find(self, path): 5662c6fc760SBrad Bishop return self.try_mapper_call( 5672c6fc760SBrad Bishop self.mapper.get_object, 5682c6fc760SBrad Bishop path=path) 5692c6fc760SBrad Bishop 5702c6fc760SBrad Bishop def setup(self, path): 5712c6fc760SBrad Bishop request.route_data['map'] = self.find(path) 5722c6fc760SBrad Bishop 5732c6fc760SBrad Bishop def do_get(self, path): 5742c6fc760SBrad Bishop schema = {} 575249d1327SCamVan Nguyen for x in request.route_data['map'].keys(): 5762c6fc760SBrad Bishop obj = self.bus.get_object(x, path, introspect=False) 5772c6fc760SBrad Bishop iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE) 5782c6fc760SBrad Bishop data = iface.Introspect() 5792c6fc760SBrad Bishop parser = IntrospectionNodeParser( 5802c6fc760SBrad Bishop ElementTree.fromstring(data)) 581249d1327SCamVan Nguyen for x, y in parser.get_interfaces().items(): 5822c6fc760SBrad Bishop schema[x] = y 5832c6fc760SBrad Bishop 5842c6fc760SBrad Bishop return schema 5852c6fc760SBrad Bishop 5862c6fc760SBrad Bishop 5872c6fc760SBrad Bishopclass InstanceHandler(RouteHandler): 5882c6fc760SBrad Bishop verbs = ['GET', 'PUT', 'DELETE'] 5892c6fc760SBrad Bishop rules = '<path:path>' 5902c6fc760SBrad Bishop request_type = dict 5912c6fc760SBrad Bishop 5922c6fc760SBrad Bishop def __init__(self, app, bus): 5932c6fc760SBrad Bishop super(InstanceHandler, self).__init__( 594529029b5SBrad Bishop app, bus, self.verbs, self.rules) 5952c6fc760SBrad Bishop 5962c6fc760SBrad Bishop def find(self, path, callback=None): 5972c6fc760SBrad Bishop return {path: self.try_mapper_call( 5982c6fc760SBrad Bishop self.mapper.get_object, 5992c6fc760SBrad Bishop callback, 6002c6fc760SBrad Bishop path=path)} 6012c6fc760SBrad Bishop 6022c6fc760SBrad Bishop def setup(self, path): 6032c6fc760SBrad Bishop callback = None 6042c6fc760SBrad Bishop if request.method == 'PUT': 6052c6fc760SBrad Bishop def callback(e, **kw): 6062c6fc760SBrad Bishop abort(403, _4034_msg % ('resource', 'created', path)) 6072c6fc760SBrad Bishop 6082c6fc760SBrad Bishop if request.route_data.get('map') is None: 6092c6fc760SBrad Bishop request.route_data['map'] = self.find(path, callback) 6102c6fc760SBrad Bishop 6112c6fc760SBrad Bishop def do_get(self, path): 6122c6fc760SBrad Bishop return self.mapper.enumerate_object( 6132c6fc760SBrad Bishop path, 6142c6fc760SBrad Bishop mapper_data=request.route_data['map']) 6152c6fc760SBrad Bishop 6162c6fc760SBrad Bishop def do_put(self, path): 6172c6fc760SBrad Bishop # make sure all properties exist in the request 6182c6fc760SBrad Bishop obj = set(self.do_get(path).keys()) 6192c6fc760SBrad Bishop req = set(request.parameter_list.keys()) 6202c6fc760SBrad Bishop 6212c6fc760SBrad Bishop diff = list(obj.difference(req)) 6222c6fc760SBrad Bishop if diff: 6232c6fc760SBrad Bishop abort(403, _4034_msg % ( 6242c6fc760SBrad Bishop 'resource', 'removed', '%s/attr/%s' % (path, diff[0]))) 6252c6fc760SBrad Bishop 6262c6fc760SBrad Bishop diff = list(req.difference(obj)) 6272c6fc760SBrad Bishop if diff: 6282c6fc760SBrad Bishop abort(403, _4034_msg % ( 6292c6fc760SBrad Bishop 'resource', 'created', '%s/attr/%s' % (path, diff[0]))) 6302c6fc760SBrad Bishop 631249d1327SCamVan Nguyen for p, v in request.parameter_list.items(): 6322c6fc760SBrad Bishop self.app.property_handler.do_put( 6332c6fc760SBrad Bishop path, p, v) 6342c6fc760SBrad Bishop 6352c6fc760SBrad Bishop def do_delete(self, path): 636b1f6a2cdSMatt Spinler deleted = False 637b1f6a2cdSMatt Spinler for bus, interfaces in request.route_data['map'][path].items(): 638b1f6a2cdSMatt Spinler if self.bus_has_delete(interfaces): 639b1f6a2cdSMatt Spinler self.delete_on_bus(path, bus) 640b1f6a2cdSMatt Spinler deleted = True 641b1f6a2cdSMatt Spinler 642b1f6a2cdSMatt Spinler #It's OK if some objects didn't have a Delete, but not all 643b1f6a2cdSMatt Spinler if not deleted: 6442c6fc760SBrad Bishop abort(403, _4034_msg % ('resource', 'removed', path)) 6452c6fc760SBrad Bishop 646b1f6a2cdSMatt Spinler def bus_has_delete(self, interfaces): 647b1f6a2cdSMatt Spinler return DELETE_IFACE in interfaces 6482c6fc760SBrad Bishop 6492c6fc760SBrad Bishop def delete_on_bus(self, path, bus): 6502c6fc760SBrad Bishop obj = self.bus.get_object(bus, path, introspect=False) 6512c6fc760SBrad Bishop delete_iface = dbus.Interface( 6522c6fc760SBrad Bishop obj, dbus_interface=DELETE_IFACE) 6532c6fc760SBrad Bishop delete_iface.Delete() 6542c6fc760SBrad Bishop 6552c6fc760SBrad Bishop 6562c6fc760SBrad Bishopclass SessionHandler(MethodHandler): 6572c6fc760SBrad Bishop ''' Handles the /login and /logout routes, manages 6582c6fc760SBrad Bishop server side session store and session cookies. ''' 6592c6fc760SBrad Bishop 6602c6fc760SBrad Bishop rules = ['/login', '/logout'] 6612c6fc760SBrad Bishop login_str = "User '%s' logged %s" 6622c6fc760SBrad Bishop bad_passwd_str = "Invalid username or password" 6632c6fc760SBrad Bishop no_user_str = "No user logged in" 6642c6fc760SBrad Bishop bad_json_str = "Expecting request format { 'data': " \ 6652c6fc760SBrad Bishop "[<username>, <password>] }, got '%s'" 666d08a4569SAlexander Filippov bmc_not_ready_str = "BMC is not ready (booting)" 6672c6fc760SBrad Bishop _require_auth = None 6682c6fc760SBrad Bishop MAX_SESSIONS = 16 669d08a4569SAlexander Filippov BMCSTATE_IFACE = 'xyz.openbmc_project.State.BMC' 670d08a4569SAlexander Filippov BMCSTATE_PATH = '/xyz/openbmc_project/state/bmc0' 671d08a4569SAlexander Filippov BMCSTATE_PROPERTY = 'CurrentBMCState' 672d08a4569SAlexander Filippov BMCSTATE_READY = 'xyz.openbmc_project.State.BMC.BMCState.Ready' 6736e1ca530SDeepak Kodihalli suppress_json_logging = True 6742c6fc760SBrad Bishop 6752c6fc760SBrad Bishop def __init__(self, app, bus): 6762c6fc760SBrad Bishop super(SessionHandler, self).__init__( 6772c6fc760SBrad Bishop app, bus) 6782c6fc760SBrad Bishop self.hmac_key = os.urandom(128) 6792c6fc760SBrad Bishop self.session_store = [] 6802c6fc760SBrad Bishop 6812c6fc760SBrad Bishop @staticmethod 6822c6fc760SBrad Bishop def authenticate(username, clear): 6832c6fc760SBrad Bishop try: 6842c6fc760SBrad Bishop encoded = spwd.getspnam(username)[1] 6852c6fc760SBrad Bishop return encoded == crypt.crypt(clear, encoded) 6862c6fc760SBrad Bishop except KeyError: 6872c6fc760SBrad Bishop return False 6882c6fc760SBrad Bishop 6892c6fc760SBrad Bishop def invalidate_session(self, session): 6902c6fc760SBrad Bishop try: 6912c6fc760SBrad Bishop self.session_store.remove(session) 6922c6fc760SBrad Bishop except ValueError: 6932c6fc760SBrad Bishop pass 6942c6fc760SBrad Bishop 6952c6fc760SBrad Bishop def new_session(self): 6962c6fc760SBrad Bishop sid = os.urandom(32) 6972c6fc760SBrad Bishop if self.MAX_SESSIONS <= len(self.session_store): 6982c6fc760SBrad Bishop self.session_store.pop() 6992c6fc760SBrad Bishop self.session_store.insert(0, {'sid': sid}) 7002c6fc760SBrad Bishop 7012c6fc760SBrad Bishop return self.session_store[0] 7022c6fc760SBrad Bishop 7032c6fc760SBrad Bishop def get_session(self, sid): 7042c6fc760SBrad Bishop sids = [x['sid'] for x in self.session_store] 7052c6fc760SBrad Bishop try: 7062c6fc760SBrad Bishop return self.session_store[sids.index(sid)] 7072c6fc760SBrad Bishop except ValueError: 7082c6fc760SBrad Bishop return None 7092c6fc760SBrad Bishop 7102c6fc760SBrad Bishop def get_session_from_cookie(self): 7112c6fc760SBrad Bishop return self.get_session( 7122c6fc760SBrad Bishop request.get_cookie( 7132c6fc760SBrad Bishop 'sid', secret=self.hmac_key)) 7142c6fc760SBrad Bishop 7152c6fc760SBrad Bishop def do_post(self, **kw): 7162c6fc760SBrad Bishop if request.path == '/login': 7172c6fc760SBrad Bishop return self.do_login(**kw) 7182c6fc760SBrad Bishop else: 7192c6fc760SBrad Bishop return self.do_logout(**kw) 7202c6fc760SBrad Bishop 7212c6fc760SBrad Bishop def do_logout(self, **kw): 7222c6fc760SBrad Bishop session = self.get_session_from_cookie() 7232c6fc760SBrad Bishop if session is not None: 7242c6fc760SBrad Bishop user = session['user'] 7252c6fc760SBrad Bishop self.invalidate_session(session) 7262c6fc760SBrad Bishop response.delete_cookie('sid') 7272c6fc760SBrad Bishop return self.login_str % (user, 'out') 7282c6fc760SBrad Bishop 7292c6fc760SBrad Bishop return self.no_user_str 7302c6fc760SBrad Bishop 7312c6fc760SBrad Bishop def do_login(self, **kw): 7322c6fc760SBrad Bishop if len(request.parameter_list) != 2: 7332c6fc760SBrad Bishop abort(400, self.bad_json_str % (request.json)) 7342c6fc760SBrad Bishop 7352c6fc760SBrad Bishop if not self.authenticate(*request.parameter_list): 736dc3fbfa7SBrad Bishop abort(401, self.bad_passwd_str) 7372c6fc760SBrad Bishop 738d08a4569SAlexander Filippov force = False 739d08a4569SAlexander Filippov try: 740d08a4569SAlexander Filippov force = request.json.get('force') 741d08a4569SAlexander Filippov except (ValueError, AttributeError, KeyError, TypeError): 742d08a4569SAlexander Filippov force = False 743d08a4569SAlexander Filippov 744d08a4569SAlexander Filippov if not force and not self.is_bmc_ready(): 745d08a4569SAlexander Filippov abort(503, self.bmc_not_ready_str) 746d08a4569SAlexander Filippov 7472c6fc760SBrad Bishop user = request.parameter_list[0] 7482c6fc760SBrad Bishop session = self.new_session() 7492c6fc760SBrad Bishop session['user'] = user 7502c6fc760SBrad Bishop response.set_cookie( 7512c6fc760SBrad Bishop 'sid', session['sid'], secret=self.hmac_key, 7522c6fc760SBrad Bishop secure=True, 7532c6fc760SBrad Bishop httponly=True) 7542c6fc760SBrad Bishop return self.login_str % (user, 'in') 7552c6fc760SBrad Bishop 756d08a4569SAlexander Filippov def is_bmc_ready(self): 757d08a4569SAlexander Filippov if not self.app.with_bmc_check: 758d08a4569SAlexander Filippov return True 759d08a4569SAlexander Filippov 760d08a4569SAlexander Filippov try: 761d08a4569SAlexander Filippov obj = self.bus.get_object(self.BMCSTATE_IFACE, self.BMCSTATE_PATH) 762d08a4569SAlexander Filippov iface = dbus.Interface(obj, dbus.PROPERTIES_IFACE) 763d08a4569SAlexander Filippov state = iface.Get(self.BMCSTATE_IFACE, self.BMCSTATE_PROPERTY) 764d08a4569SAlexander Filippov if state == self.BMCSTATE_READY: 765d08a4569SAlexander Filippov return True 766d08a4569SAlexander Filippov 767d08a4569SAlexander Filippov except dbus.exceptions.DBusException: 768d08a4569SAlexander Filippov pass 769d08a4569SAlexander Filippov 770d08a4569SAlexander Filippov return False 771d08a4569SAlexander Filippov 7722c6fc760SBrad Bishop def find(self, **kw): 7732c6fc760SBrad Bishop pass 7742c6fc760SBrad Bishop 7752c6fc760SBrad Bishop def setup(self, **kw): 7762c6fc760SBrad Bishop pass 7772c6fc760SBrad Bishop 7782c6fc760SBrad Bishop 7797ec0a4f4SDeepak Kodihalliclass ImageUploadUtils: 7807ec0a4f4SDeepak Kodihalli ''' Provides common utils for image upload. ''' 7811af301a0SDeepak Kodihalli 7821af301a0SDeepak Kodihalli file_loc = '/tmp/images' 7831af301a0SDeepak Kodihalli file_prefix = 'img' 7841af301a0SDeepak Kodihalli file_suffix = '' 78553693891SAdriana Kobylak signal = None 7861af301a0SDeepak Kodihalli 7877ec0a4f4SDeepak Kodihalli @classmethod 7887ec0a4f4SDeepak Kodihalli def do_upload(cls, filename=''): 78953693891SAdriana Kobylak def cleanup(): 79053693891SAdriana Kobylak os.close(handle) 79153693891SAdriana Kobylak if cls.signal: 79253693891SAdriana Kobylak cls.signal.remove() 79353693891SAdriana Kobylak cls.signal = None 79453693891SAdriana Kobylak 79553693891SAdriana Kobylak def signal_callback(path, a, **kw): 79653693891SAdriana Kobylak # Just interested on the first Version interface created which is 79753693891SAdriana Kobylak # triggered when the file is uploaded. This helps avoid getting the 79853693891SAdriana Kobylak # wrong information for multiple upload requests in a row. 79953693891SAdriana Kobylak if "xyz.openbmc_project.Software.Version" in a and \ 80053693891SAdriana Kobylak "xyz.openbmc_project.Software.Activation" not in a: 80153693891SAdriana Kobylak paths.append(path) 80253693891SAdriana Kobylak 80353693891SAdriana Kobylak while cls.signal: 80453693891SAdriana Kobylak # Serialize uploads by waiting for the signal to be cleared. 80553693891SAdriana Kobylak # This makes it easier to ensure that the version information 80653693891SAdriana Kobylak # is the right one instead of the data from another upload request. 80753693891SAdriana Kobylak gevent.sleep(1) 8087ec0a4f4SDeepak Kodihalli if not os.path.exists(cls.file_loc): 809fb515796SGunnar Mills abort(500, "Error Directory not found") 81053693891SAdriana Kobylak paths = [] 81153693891SAdriana Kobylak bus = dbus.SystemBus() 81253693891SAdriana Kobylak cls.signal = bus.add_signal_receiver( 81353693891SAdriana Kobylak signal_callback, 81453693891SAdriana Kobylak dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager', 81553693891SAdriana Kobylak signal_name='InterfacesAdded', 81653693891SAdriana Kobylak path=SOFTWARE_PATH) 8177ec0a4f4SDeepak Kodihalli if not filename: 8187ec0a4f4SDeepak Kodihalli handle, filename = tempfile.mkstemp(cls.file_suffix, 8197ec0a4f4SDeepak Kodihalli cls.file_prefix, cls.file_loc) 8207ec0a4f4SDeepak Kodihalli else: 8217ec0a4f4SDeepak Kodihalli filename = os.path.join(cls.file_loc, filename) 822b66b18cbSGunnar Mills handle = os.open(filename, os.O_WRONLY | os.O_CREAT) 8230b62edfbSLeonel Gonzalez try: 8240b62edfbSLeonel Gonzalez file_contents = request.body.read() 8250b62edfbSLeonel Gonzalez request.body.close() 826b66b18cbSGunnar Mills os.write(handle, file_contents) 82753693891SAdriana Kobylak # Close file after writing, the image manager process watches for 82853693891SAdriana Kobylak # the close event to know the upload is complete. 829b66b18cbSGunnar Mills os.close(handle) 83053693891SAdriana Kobylak except (IOError, ValueError) as e: 83153693891SAdriana Kobylak cleanup() 83253693891SAdriana Kobylak abort(400, str(e)) 83353693891SAdriana Kobylak except Exception: 83453693891SAdriana Kobylak cleanup() 83553693891SAdriana Kobylak abort(400, "Unexpected Error") 83653693891SAdriana Kobylak loop = gobject.MainLoop() 83753693891SAdriana Kobylak gcontext = loop.get_context() 83853693891SAdriana Kobylak count = 0 83953693891SAdriana Kobylak version_id = '' 84053693891SAdriana Kobylak while loop is not None: 84153693891SAdriana Kobylak try: 84253693891SAdriana Kobylak if gcontext.pending(): 84353693891SAdriana Kobylak gcontext.iteration() 84453693891SAdriana Kobylak if not paths: 84553693891SAdriana Kobylak gevent.sleep(1) 84653693891SAdriana Kobylak else: 84753693891SAdriana Kobylak version_id = os.path.basename(paths.pop()) 84853693891SAdriana Kobylak break 84953693891SAdriana Kobylak count += 1 85053693891SAdriana Kobylak if count == 10: 85153693891SAdriana Kobylak break 85253693891SAdriana Kobylak except Exception: 85353693891SAdriana Kobylak break 85453693891SAdriana Kobylak cls.signal.remove() 85553693891SAdriana Kobylak cls.signal = None 85697fe435fSAdriana Kobylak if version_id: 85753693891SAdriana Kobylak return version_id 85897fe435fSAdriana Kobylak else: 85997fe435fSAdriana Kobylak abort(400, "Version already exists or failed to be extracted") 8607ec0a4f4SDeepak Kodihalli 8617ec0a4f4SDeepak Kodihalli 8627ec0a4f4SDeepak Kodihalliclass ImagePostHandler(RouteHandler): 8637ec0a4f4SDeepak Kodihalli ''' Handles the /upload/image route. ''' 8647ec0a4f4SDeepak Kodihalli 8657ec0a4f4SDeepak Kodihalli verbs = ['POST'] 8667ec0a4f4SDeepak Kodihalli rules = ['/upload/image'] 8677ec0a4f4SDeepak Kodihalli content_type = 'application/octet-stream' 8687ec0a4f4SDeepak Kodihalli 8691af301a0SDeepak Kodihalli def __init__(self, app, bus): 8707ec0a4f4SDeepak Kodihalli super(ImagePostHandler, self).__init__( 8711af301a0SDeepak Kodihalli app, bus, self.verbs, self.rules, self.content_type) 8721af301a0SDeepak Kodihalli 8737ec0a4f4SDeepak Kodihalli def do_post(self, filename=''): 87453693891SAdriana Kobylak return ImageUploadUtils.do_upload() 8751af301a0SDeepak Kodihalli 8767ec0a4f4SDeepak Kodihalli def find(self, **kw): 8777ec0a4f4SDeepak Kodihalli pass 8781af301a0SDeepak Kodihalli 8797ec0a4f4SDeepak Kodihalli def setup(self, **kw): 8807ec0a4f4SDeepak Kodihalli pass 8817ec0a4f4SDeepak Kodihalli 8827ec0a4f4SDeepak Kodihalli 883dee2ef57SDhruvaraj Subhashchandranclass CertificateHandler: 884dee2ef57SDhruvaraj Subhashchandran file_suffix = '.pem' 885dee2ef57SDhruvaraj Subhashchandran file_prefix = 'cert_' 886dee2ef57SDhruvaraj Subhashchandran CERT_PATH = '/xyz/openbmc_project/certs' 887dee2ef57SDhruvaraj Subhashchandran CERT_IFACE = 'xyz.openbmc_project.Certs.Install' 888dee2ef57SDhruvaraj Subhashchandran 889a324acd9SDeepak Kodihalli def __init__(self, route_handler, cert_type, service): 890dee2ef57SDhruvaraj Subhashchandran if not service: 891dee2ef57SDhruvaraj Subhashchandran abort(500, "Missing service") 892dee2ef57SDhruvaraj Subhashchandran if not cert_type: 893dee2ef57SDhruvaraj Subhashchandran abort(500, "Missing certificate type") 894a324acd9SDeepak Kodihalli bus = dbus.SystemBus() 895a324acd9SDeepak Kodihalli certPath = self.CERT_PATH + "/" + cert_type + "/" + service 896a324acd9SDeepak Kodihalli intfs = route_handler.try_mapper_call( 897a324acd9SDeepak Kodihalli route_handler.mapper.get_object, path=certPath) 898a324acd9SDeepak Kodihalli for busName,intf in intfs.items(): 899a324acd9SDeepak Kodihalli if self.CERT_IFACE in intf: 900a324acd9SDeepak Kodihalli self.obj = bus.get_object(busName, certPath) 901a324acd9SDeepak Kodihalli return 902a324acd9SDeepak Kodihalli abort(404, "Path not found") 903a324acd9SDeepak Kodihalli 904a324acd9SDeepak Kodihalli def do_upload(self): 905a324acd9SDeepak Kodihalli def cleanup(): 906a324acd9SDeepak Kodihalli if os.path.exists(temp.name): 907a324acd9SDeepak Kodihalli os.remove(temp.name) 908dee2ef57SDhruvaraj Subhashchandran 909dee2ef57SDhruvaraj Subhashchandran with tempfile.NamedTemporaryFile( 910a324acd9SDeepak Kodihalli suffix=self.file_suffix, 911a324acd9SDeepak Kodihalli prefix=self.file_prefix, 912dee2ef57SDhruvaraj Subhashchandran delete=False) as temp: 913dee2ef57SDhruvaraj Subhashchandran try: 914dee2ef57SDhruvaraj Subhashchandran file_contents = request.body.read() 915dee2ef57SDhruvaraj Subhashchandran request.body.close() 916dee2ef57SDhruvaraj Subhashchandran temp.write(file_contents) 917dee2ef57SDhruvaraj Subhashchandran except (IOError, ValueError) as e: 918dee2ef57SDhruvaraj Subhashchandran cleanup() 919dee2ef57SDhruvaraj Subhashchandran abort(500, str(e)) 920dee2ef57SDhruvaraj Subhashchandran except Exception: 921dee2ef57SDhruvaraj Subhashchandran cleanup() 922dee2ef57SDhruvaraj Subhashchandran abort(500, "Unexpected Error") 923dee2ef57SDhruvaraj Subhashchandran 924dee2ef57SDhruvaraj Subhashchandran try: 925a324acd9SDeepak Kodihalli iface = dbus.Interface(self.obj, self.CERT_IFACE) 926dee2ef57SDhruvaraj Subhashchandran iface.Install(temp.name) 927c043cddaSDeepak Kodihalli except Exception as e: 928dee2ef57SDhruvaraj Subhashchandran cleanup() 929844bb4e1SDeepak Kodihalli abort(400, str(e)) 930a324acd9SDeepak Kodihalli cleanup() 931a324acd9SDeepak Kodihalli 932a324acd9SDeepak Kodihalli def do_delete(self): 933a324acd9SDeepak Kodihalli delete_iface = dbus.Interface( 934a324acd9SDeepak Kodihalli self.obj, dbus_interface=DELETE_IFACE) 935a324acd9SDeepak Kodihalli delete_iface.Delete() 936dee2ef57SDhruvaraj Subhashchandran 937dee2ef57SDhruvaraj Subhashchandran 938dee2ef57SDhruvaraj Subhashchandranclass CertificatePutHandler(RouteHandler): 939dee2ef57SDhruvaraj Subhashchandran ''' Handles the /xyz/openbmc_project/certs/<cert_type>/<service> route. ''' 940dee2ef57SDhruvaraj Subhashchandran 941a324acd9SDeepak Kodihalli verbs = ['PUT', 'DELETE'] 942dee2ef57SDhruvaraj Subhashchandran rules = ['/xyz/openbmc_project/certs/<cert_type>/<service>'] 943dee2ef57SDhruvaraj Subhashchandran content_type = 'application/octet-stream' 944dee2ef57SDhruvaraj Subhashchandran 945dee2ef57SDhruvaraj Subhashchandran def __init__(self, app, bus): 946dee2ef57SDhruvaraj Subhashchandran super(CertificatePutHandler, self).__init__( 947dee2ef57SDhruvaraj Subhashchandran app, bus, self.verbs, self.rules, self.content_type) 948dee2ef57SDhruvaraj Subhashchandran 949dee2ef57SDhruvaraj Subhashchandran def do_put(self, cert_type, service): 950a324acd9SDeepak Kodihalli return CertificateHandler(self, cert_type, service).do_upload() 951a324acd9SDeepak Kodihalli 952a324acd9SDeepak Kodihalli def do_delete(self, cert_type, service): 953a324acd9SDeepak Kodihalli return CertificateHandler(self, cert_type, service).do_delete() 954dee2ef57SDhruvaraj Subhashchandran 955dee2ef57SDhruvaraj Subhashchandran def find(self, **kw): 956dee2ef57SDhruvaraj Subhashchandran pass 957dee2ef57SDhruvaraj Subhashchandran 958dee2ef57SDhruvaraj Subhashchandran def setup(self, **kw): 959dee2ef57SDhruvaraj Subhashchandran pass 960dee2ef57SDhruvaraj Subhashchandran 961dee2ef57SDhruvaraj Subhashchandran 962639b502eSDeepak Kodihalliclass EventNotifier: 963639b502eSDeepak Kodihalli keyNames = {} 964639b502eSDeepak Kodihalli keyNames['event'] = 'event' 965639b502eSDeepak Kodihalli keyNames['path'] = 'path' 966639b502eSDeepak Kodihalli keyNames['intfMap'] = 'interfaces' 967639b502eSDeepak Kodihalli keyNames['propMap'] = 'properties' 968639b502eSDeepak Kodihalli keyNames['intf'] = 'interface' 969639b502eSDeepak Kodihalli 970639b502eSDeepak Kodihalli def __init__(self, wsock, filters): 971639b502eSDeepak Kodihalli self.wsock = wsock 972639b502eSDeepak Kodihalli self.paths = filters.get("paths", []) 973639b502eSDeepak Kodihalli self.interfaces = filters.get("interfaces", []) 9740f7019dfSAndrew Geissler self.signals = [] 9750f7019dfSAndrew Geissler self.socket_error = False 976639b502eSDeepak Kodihalli if not self.paths: 977639b502eSDeepak Kodihalli self.paths.append(None) 978639b502eSDeepak Kodihalli bus = dbus.SystemBus() 979639b502eSDeepak Kodihalli # Add a signal receiver for every path the client is interested in 980639b502eSDeepak Kodihalli for path in self.paths: 9810f7019dfSAndrew Geissler add_sig = bus.add_signal_receiver( 982639b502eSDeepak Kodihalli self.interfaces_added_handler, 983639b502eSDeepak Kodihalli dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager', 984639b502eSDeepak Kodihalli signal_name='InterfacesAdded', 985639b502eSDeepak Kodihalli path=path) 9860f7019dfSAndrew Geissler chg_sig = bus.add_signal_receiver( 987639b502eSDeepak Kodihalli self.properties_changed_handler, 988639b502eSDeepak Kodihalli dbus_interface=dbus.PROPERTIES_IFACE, 989639b502eSDeepak Kodihalli signal_name='PropertiesChanged', 990639b502eSDeepak Kodihalli path=path, 991639b502eSDeepak Kodihalli path_keyword='path') 9920f7019dfSAndrew Geissler self.signals.append(add_sig) 9930f7019dfSAndrew Geissler self.signals.append(chg_sig) 994639b502eSDeepak Kodihalli loop = gobject.MainLoop() 995639b502eSDeepak Kodihalli # gobject's mainloop.run() will block the entire process, so the gevent 996639b502eSDeepak Kodihalli # scheduler and hence greenlets won't execute. The while-loop below 997639b502eSDeepak Kodihalli # works around this limitation by using gevent's sleep, instead of 998639b502eSDeepak Kodihalli # calling loop.run() 999639b502eSDeepak Kodihalli gcontext = loop.get_context() 1000639b502eSDeepak Kodihalli while loop is not None: 1001639b502eSDeepak Kodihalli try: 10020f7019dfSAndrew Geissler if self.socket_error: 10030f7019dfSAndrew Geissler for signal in self.signals: 10040f7019dfSAndrew Geissler signal.remove() 10050f7019dfSAndrew Geissler loop.quit() 10060f7019dfSAndrew Geissler break; 1007639b502eSDeepak Kodihalli if gcontext.pending(): 1008639b502eSDeepak Kodihalli gcontext.iteration() 1009639b502eSDeepak Kodihalli else: 1010639b502eSDeepak Kodihalli # gevent.sleep puts only the current greenlet to sleep, 1011639b502eSDeepak Kodihalli # not the entire process. 1012639b502eSDeepak Kodihalli gevent.sleep(5) 1013639b502eSDeepak Kodihalli except WebSocketError: 1014639b502eSDeepak Kodihalli break 1015639b502eSDeepak Kodihalli 1016639b502eSDeepak Kodihalli def interfaces_added_handler(self, path, iprops, **kw): 1017639b502eSDeepak Kodihalli ''' If the client is interested in these changes, respond to the 1018639b502eSDeepak Kodihalli client. This handles d-bus interface additions.''' 1019639b502eSDeepak Kodihalli if (not self.interfaces) or \ 1020639b502eSDeepak Kodihalli (not set(iprops).isdisjoint(self.interfaces)): 1021639b502eSDeepak Kodihalli response = {} 1022639b502eSDeepak Kodihalli response[self.keyNames['event']] = "InterfacesAdded" 1023639b502eSDeepak Kodihalli response[self.keyNames['path']] = path 1024639b502eSDeepak Kodihalli response[self.keyNames['intfMap']] = iprops 1025639b502eSDeepak Kodihalli try: 1026639b502eSDeepak Kodihalli self.wsock.send(json.dumps(response)) 10270f7019dfSAndrew Geissler except: 10280f7019dfSAndrew Geissler self.socket_error = True 1029639b502eSDeepak Kodihalli return 1030639b502eSDeepak Kodihalli 1031639b502eSDeepak Kodihalli def properties_changed_handler(self, interface, new, old, **kw): 1032639b502eSDeepak Kodihalli ''' If the client is interested in these changes, respond to the 1033639b502eSDeepak Kodihalli client. This handles d-bus property changes. ''' 1034639b502eSDeepak Kodihalli if (not self.interfaces) or (interface in self.interfaces): 1035639b502eSDeepak Kodihalli path = str(kw['path']) 1036639b502eSDeepak Kodihalli response = {} 1037639b502eSDeepak Kodihalli response[self.keyNames['event']] = "PropertiesChanged" 1038639b502eSDeepak Kodihalli response[self.keyNames['path']] = path 1039639b502eSDeepak Kodihalli response[self.keyNames['intf']] = interface 1040639b502eSDeepak Kodihalli response[self.keyNames['propMap']] = new 1041639b502eSDeepak Kodihalli try: 1042639b502eSDeepak Kodihalli self.wsock.send(json.dumps(response)) 10430f7019dfSAndrew Geissler except: 10440f7019dfSAndrew Geissler self.socket_error = True 1045639b502eSDeepak Kodihalli return 1046639b502eSDeepak Kodihalli 1047639b502eSDeepak Kodihalli 1048b209dd16SDeepak Kodihalliclass EventHandler(RouteHandler): 1049b209dd16SDeepak Kodihalli ''' Handles the /subscribe route, for clients to be able 1050b209dd16SDeepak Kodihalli to subscribe to BMC events. ''' 1051b209dd16SDeepak Kodihalli 1052b209dd16SDeepak Kodihalli verbs = ['GET'] 1053b209dd16SDeepak Kodihalli rules = ['/subscribe'] 10546e1ca530SDeepak Kodihalli suppress_logging = True 1055b209dd16SDeepak Kodihalli 1056b209dd16SDeepak Kodihalli def __init__(self, app, bus): 1057b209dd16SDeepak Kodihalli super(EventHandler, self).__init__( 1058b209dd16SDeepak Kodihalli app, bus, self.verbs, self.rules) 1059b209dd16SDeepak Kodihalli 1060b209dd16SDeepak Kodihalli def find(self, **kw): 1061b209dd16SDeepak Kodihalli pass 1062b209dd16SDeepak Kodihalli 1063b209dd16SDeepak Kodihalli def setup(self, **kw): 1064b209dd16SDeepak Kodihalli pass 1065b209dd16SDeepak Kodihalli 1066b209dd16SDeepak Kodihalli def do_get(self): 1067b209dd16SDeepak Kodihalli wsock = request.environ.get('wsgi.websocket') 1068b209dd16SDeepak Kodihalli if not wsock: 1069b209dd16SDeepak Kodihalli abort(400, 'Expected WebSocket request.') 1070bec10c20SJayashankar Padath ping_sender = Greenlet.spawn(send_ws_ping, wsock, WEBSOCKET_TIMEOUT) 1071639b502eSDeepak Kodihalli filters = wsock.receive() 1072639b502eSDeepak Kodihalli filters = json.loads(filters) 1073639b502eSDeepak Kodihalli notifier = EventNotifier(wsock, filters) 1074b209dd16SDeepak Kodihalli 10755c518f63SDeepak Kodihalliclass HostConsoleHandler(RouteHandler): 10765c518f63SDeepak Kodihalli ''' Handles the /console route, for clients to be able 10775c518f63SDeepak Kodihalli read/write the host serial console. The way this is 10785c518f63SDeepak Kodihalli done is by exposing a websocket that's mirrored to an 10795c518f63SDeepak Kodihalli abstract UNIX domain socket, which is the source for 10805c518f63SDeepak Kodihalli the console data. ''' 10815c518f63SDeepak Kodihalli 10825c518f63SDeepak Kodihalli verbs = ['GET'] 10835c518f63SDeepak Kodihalli # Naming the route console0, because the numbering will help 10845c518f63SDeepak Kodihalli # on multi-bmc/multi-host systems. 10855c518f63SDeepak Kodihalli rules = ['/console0'] 10866e1ca530SDeepak Kodihalli suppress_logging = True 10875c518f63SDeepak Kodihalli 10885c518f63SDeepak Kodihalli def __init__(self, app, bus): 10895c518f63SDeepak Kodihalli super(HostConsoleHandler, self).__init__( 10905c518f63SDeepak Kodihalli app, bus, self.verbs, self.rules) 10915c518f63SDeepak Kodihalli 10925c518f63SDeepak Kodihalli def find(self, **kw): 10935c518f63SDeepak Kodihalli pass 10945c518f63SDeepak Kodihalli 10955c518f63SDeepak Kodihalli def setup(self, **kw): 10965c518f63SDeepak Kodihalli pass 10975c518f63SDeepak Kodihalli 10985c518f63SDeepak Kodihalli def read_wsock(self, wsock, sock): 10995c518f63SDeepak Kodihalli while True: 11005c518f63SDeepak Kodihalli try: 11015c518f63SDeepak Kodihalli incoming = wsock.receive() 11025c518f63SDeepak Kodihalli if incoming: 11035c518f63SDeepak Kodihalli # Read websocket, write to UNIX socket 11045c518f63SDeepak Kodihalli sock.send(incoming) 11055c518f63SDeepak Kodihalli except Exception as e: 11065c518f63SDeepak Kodihalli sock.close() 11075c518f63SDeepak Kodihalli return 11085c518f63SDeepak Kodihalli 11095c518f63SDeepak Kodihalli def read_sock(self, sock, wsock): 11105c518f63SDeepak Kodihalli max_sock_read_len = 4096 11115c518f63SDeepak Kodihalli while True: 11125c518f63SDeepak Kodihalli try: 11135c518f63SDeepak Kodihalli outgoing = sock.recv(max_sock_read_len) 11145c518f63SDeepak Kodihalli if outgoing: 11155c518f63SDeepak Kodihalli # Read UNIX socket, write to websocket 11165c518f63SDeepak Kodihalli wsock.send(outgoing) 11175c518f63SDeepak Kodihalli except Exception as e: 11185c518f63SDeepak Kodihalli wsock.close() 11195c518f63SDeepak Kodihalli return 11205c518f63SDeepak Kodihalli 11215c518f63SDeepak Kodihalli def do_get(self): 11225c518f63SDeepak Kodihalli wsock = request.environ.get('wsgi.websocket') 11235c518f63SDeepak Kodihalli if not wsock: 11245c518f63SDeepak Kodihalli abort(400, 'Expected WebSocket based request.') 11255c518f63SDeepak Kodihalli 1126dbc46919SVernon Mauery # An abstract Unix socket path must be less than or equal to 108 bytes 1127dbc46919SVernon Mauery # and does not need to be nul-terminated or padded out to 108 bytes 11285c518f63SDeepak Kodihalli socket_name = "\0obmc-console" 1129dbc46919SVernon Mauery socket_path = socket_name 11305c518f63SDeepak Kodihalli sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 11315c518f63SDeepak Kodihalli 11325c518f63SDeepak Kodihalli try: 11335c518f63SDeepak Kodihalli sock.connect(socket_path) 11345c518f63SDeepak Kodihalli except Exception as e: 11355c518f63SDeepak Kodihalli abort(500, str(e)) 11365c518f63SDeepak Kodihalli 11375c518f63SDeepak Kodihalli wsock_reader = Greenlet.spawn(self.read_wsock, wsock, sock) 11385c518f63SDeepak Kodihalli sock_reader = Greenlet.spawn(self.read_sock, sock, wsock) 1139bec10c20SJayashankar Padath ping_sender = Greenlet.spawn(send_ws_ping, wsock, WEBSOCKET_TIMEOUT) 11405c518f63SDeepak Kodihalli gevent.joinall([wsock_reader, sock_reader, ping_sender]) 11415c518f63SDeepak Kodihalli 11425c518f63SDeepak Kodihalli 11437ec0a4f4SDeepak Kodihalliclass ImagePutHandler(RouteHandler): 11447ec0a4f4SDeepak Kodihalli ''' Handles the /upload/image/<filename> route. ''' 11457ec0a4f4SDeepak Kodihalli 11467ec0a4f4SDeepak Kodihalli verbs = ['PUT'] 11477ec0a4f4SDeepak Kodihalli rules = ['/upload/image/<filename>'] 11487ec0a4f4SDeepak Kodihalli content_type = 'application/octet-stream' 11497ec0a4f4SDeepak Kodihalli 11507ec0a4f4SDeepak Kodihalli def __init__(self, app, bus): 11517ec0a4f4SDeepak Kodihalli super(ImagePutHandler, self).__init__( 11527ec0a4f4SDeepak Kodihalli app, bus, self.verbs, self.rules, self.content_type) 11537ec0a4f4SDeepak Kodihalli 11547ec0a4f4SDeepak Kodihalli def do_put(self, filename=''): 115553693891SAdriana Kobylak return ImageUploadUtils.do_upload(filename) 11561af301a0SDeepak Kodihalli 11571af301a0SDeepak Kodihalli def find(self, **kw): 11581af301a0SDeepak Kodihalli pass 11591af301a0SDeepak Kodihalli 11601af301a0SDeepak Kodihalli def setup(self, **kw): 11611af301a0SDeepak Kodihalli pass 11621af301a0SDeepak Kodihalli 11631af301a0SDeepak Kodihalli 11649bc94997SJayanth Othayothclass DownloadDumpHandler(RouteHandler): 11659bc94997SJayanth Othayoth ''' Handles the /download/dump route. ''' 11669bc94997SJayanth Othayoth 11679bc94997SJayanth Othayoth verbs = 'GET' 11689bc94997SJayanth Othayoth rules = ['/download/dump/<dumpid>'] 11699bc94997SJayanth Othayoth content_type = 'application/octet-stream' 117018c3a242SJayanth Othayoth dump_loc = '/var/lib/phosphor-debug-collector/dumps' 1171944cd04fSBrad Bishop suppress_json_resp = True 11726e1ca530SDeepak Kodihalli suppress_logging = True 11739bc94997SJayanth Othayoth 11749bc94997SJayanth Othayoth def __init__(self, app, bus): 11759bc94997SJayanth Othayoth super(DownloadDumpHandler, self).__init__( 11769bc94997SJayanth Othayoth app, bus, self.verbs, self.rules, self.content_type) 11779bc94997SJayanth Othayoth 11789bc94997SJayanth Othayoth def do_get(self, dumpid): 11799bc94997SJayanth Othayoth return self.do_download(dumpid) 11809bc94997SJayanth Othayoth 11819bc94997SJayanth Othayoth def find(self, **kw): 11829bc94997SJayanth Othayoth pass 11839bc94997SJayanth Othayoth 11849bc94997SJayanth Othayoth def setup(self, **kw): 11859bc94997SJayanth Othayoth pass 11869bc94997SJayanth Othayoth 11879bc94997SJayanth Othayoth def do_download(self, dumpid): 11889bc94997SJayanth Othayoth dump_loc = os.path.join(self.dump_loc, dumpid) 11899bc94997SJayanth Othayoth if not os.path.exists(dump_loc): 11909bc94997SJayanth Othayoth abort(404, "Path not found") 11919bc94997SJayanth Othayoth 11929bc94997SJayanth Othayoth files = os.listdir(dump_loc) 11939bc94997SJayanth Othayoth num_files = len(files) 11949bc94997SJayanth Othayoth if num_files == 0: 11959bc94997SJayanth Othayoth abort(404, "Dump not found") 11969bc94997SJayanth Othayoth 11979bc94997SJayanth Othayoth return static_file(os.path.basename(files[0]), root=dump_loc, 11989bc94997SJayanth Othayoth download=True, mimetype=self.content_type) 11999bc94997SJayanth Othayoth 12009bc94997SJayanth Othayoth 1201d41643ecSMatt Spinlerclass WebHandler(RouteHandler): 1202d41643ecSMatt Spinler ''' Handles the routes for the web UI files. ''' 1203d41643ecSMatt Spinler 1204d41643ecSMatt Spinler verbs = 'GET' 1205d41643ecSMatt Spinler 1206d41643ecSMatt Spinler # Match only what we know are web files, so everything else 1207d41643ecSMatt Spinler # can get routed to the REST handlers. 1208d41643ecSMatt Spinler rules = ['//', '/<filename:re:.+\.js>', '/<filename:re:.+\.svg>', 1209d41643ecSMatt Spinler '/<filename:re:.+\.css>', '/<filename:re:.+\.ttf>', 1210d41643ecSMatt Spinler '/<filename:re:.+\.eot>', '/<filename:re:.+\.woff>', 1211d41643ecSMatt Spinler '/<filename:re:.+\.woff2>', '/<filename:re:.+\.map>', 1212d41643ecSMatt Spinler '/<filename:re:.+\.png>', '/<filename:re:.+\.html>', 1213d41643ecSMatt Spinler '/<filename:re:.+\.ico>'] 1214d41643ecSMatt Spinler 1215d41643ecSMatt Spinler # The mimetypes module knows about most types, but not these 1216d41643ecSMatt Spinler content_types = { 1217d41643ecSMatt Spinler '.eot': 'application/vnd.ms-fontobject', 1218d41643ecSMatt Spinler '.woff': 'application/x-font-woff', 1219d41643ecSMatt Spinler '.woff2': 'application/x-font-woff2', 1220d41643ecSMatt Spinler '.ttf': 'application/x-font-ttf', 1221d41643ecSMatt Spinler '.map': 'application/json' 1222d41643ecSMatt Spinler } 1223d41643ecSMatt Spinler 1224d41643ecSMatt Spinler _require_auth = None 1225d41643ecSMatt Spinler suppress_json_resp = True 12266e1ca530SDeepak Kodihalli suppress_logging = True 1227d41643ecSMatt Spinler 1228d41643ecSMatt Spinler def __init__(self, app, bus): 1229d41643ecSMatt Spinler super(WebHandler, self).__init__( 1230d41643ecSMatt Spinler app, bus, self.verbs, self.rules) 1231d41643ecSMatt Spinler 1232d41643ecSMatt Spinler def get_type(self, filename): 1233d41643ecSMatt Spinler ''' Returns the content type and encoding for a file ''' 1234d41643ecSMatt Spinler 1235d41643ecSMatt Spinler content_type, encoding = mimetypes.guess_type(filename) 1236d41643ecSMatt Spinler 1237d41643ecSMatt Spinler # Try our own list if mimetypes didn't recognize it 1238d41643ecSMatt Spinler if content_type is None: 1239d41643ecSMatt Spinler if filename[-3:] == '.gz': 1240d41643ecSMatt Spinler filename = filename[:-3] 1241d41643ecSMatt Spinler extension = filename[filename.rfind('.'):] 1242d41643ecSMatt Spinler content_type = self.content_types.get(extension, None) 1243d41643ecSMatt Spinler 1244d41643ecSMatt Spinler return content_type, encoding 1245d41643ecSMatt Spinler 1246d41643ecSMatt Spinler def do_get(self, filename='index.html'): 1247d41643ecSMatt Spinler 1248d41643ecSMatt Spinler # If a gzipped version exists, use that instead. 1249d41643ecSMatt Spinler # Possible future enhancement: if the client doesn't 1250d41643ecSMatt Spinler # accept compressed files, unzip it ourselves before sending. 1251d41643ecSMatt Spinler if not os.path.exists(os.path.join(www_base_path, filename)): 1252d41643ecSMatt Spinler filename = filename + '.gz' 1253d41643ecSMatt Spinler 1254d41643ecSMatt Spinler # Though bottle should protect us, ensure path is valid 1255d41643ecSMatt Spinler realpath = os.path.realpath(filename) 1256d41643ecSMatt Spinler if realpath[0] == '/': 1257d41643ecSMatt Spinler realpath = realpath[1:] 1258d41643ecSMatt Spinler if not os.path.exists(os.path.join(www_base_path, realpath)): 1259d41643ecSMatt Spinler abort(404, "Path not found") 1260d41643ecSMatt Spinler 1261d41643ecSMatt Spinler mimetype, encoding = self.get_type(filename) 1262d41643ecSMatt Spinler 1263d41643ecSMatt Spinler # Couldn't find the type - let static_file() deal with it, 1264d41643ecSMatt Spinler # though this should never happen. 1265d41643ecSMatt Spinler if mimetype is None: 1266d41643ecSMatt Spinler print("Can't figure out content-type for %s" % filename) 1267d41643ecSMatt Spinler mimetype = 'auto' 1268d41643ecSMatt Spinler 1269d41643ecSMatt Spinler # This call will set several header fields for us, 1270d41643ecSMatt Spinler # including the charset if the type is text. 1271d41643ecSMatt Spinler response = static_file(filename, www_base_path, mimetype) 1272d41643ecSMatt Spinler 1273d41643ecSMatt Spinler # static_file() will only set the encoding if the 1274d41643ecSMatt Spinler # mimetype was auto, so set it here. 1275d41643ecSMatt Spinler if encoding is not None: 1276d41643ecSMatt Spinler response.set_header('Content-Encoding', encoding) 1277d41643ecSMatt Spinler 1278d41643ecSMatt Spinler return response 1279d41643ecSMatt Spinler 1280d41643ecSMatt Spinler def find(self, **kw): 1281d41643ecSMatt Spinler pass 1282d41643ecSMatt Spinler 1283d41643ecSMatt Spinler def setup(self, **kw): 1284d41643ecSMatt Spinler pass 1285d41643ecSMatt Spinler 1286d41643ecSMatt Spinler 12872c6fc760SBrad Bishopclass AuthorizationPlugin(object): 12882c6fc760SBrad Bishop ''' Invokes an optional list of authorization callbacks. ''' 12892c6fc760SBrad Bishop 12902c6fc760SBrad Bishop name = 'authorization' 12912c6fc760SBrad Bishop api = 2 12922c6fc760SBrad Bishop 12932c6fc760SBrad Bishop class Compose: 12942c6fc760SBrad Bishop def __init__(self, validators, callback, session_mgr): 12952c6fc760SBrad Bishop self.validators = validators 12962c6fc760SBrad Bishop self.callback = callback 12972c6fc760SBrad Bishop self.session_mgr = session_mgr 12982c6fc760SBrad Bishop 12992c6fc760SBrad Bishop def __call__(self, *a, **kw): 13002c6fc760SBrad Bishop sid = request.get_cookie('sid', secret=self.session_mgr.hmac_key) 13012c6fc760SBrad Bishop session = self.session_mgr.get_session(sid) 1302d4c1c55cSBrad Bishop if request.method != 'OPTIONS': 13032c6fc760SBrad Bishop for x in self.validators: 13042c6fc760SBrad Bishop x(session, *a, **kw) 13052c6fc760SBrad Bishop 13062c6fc760SBrad Bishop return self.callback(*a, **kw) 13072c6fc760SBrad Bishop 13082c6fc760SBrad Bishop def apply(self, callback, route): 13092c6fc760SBrad Bishop undecorated = route.get_undecorated_callback() 13102c6fc760SBrad Bishop if not isinstance(undecorated, RouteHandler): 13112c6fc760SBrad Bishop return callback 13122c6fc760SBrad Bishop 13132c6fc760SBrad Bishop auth_types = getattr( 13142c6fc760SBrad Bishop undecorated, '_require_auth', None) 13152c6fc760SBrad Bishop if not auth_types: 13162c6fc760SBrad Bishop return callback 13172c6fc760SBrad Bishop 13182c6fc760SBrad Bishop return self.Compose( 13192c6fc760SBrad Bishop auth_types, callback, undecorated.app.session_handler) 13202c6fc760SBrad Bishop 13212c6fc760SBrad Bishop 1322d0c404a6SBrad Bishopclass CorsPlugin(object): 1323d0c404a6SBrad Bishop ''' Add CORS headers. ''' 1324d0c404a6SBrad Bishop 1325d0c404a6SBrad Bishop name = 'cors' 1326d0c404a6SBrad Bishop api = 2 1327d0c404a6SBrad Bishop 1328d0c404a6SBrad Bishop @staticmethod 1329d0c404a6SBrad Bishop def process_origin(): 1330d0c404a6SBrad Bishop origin = request.headers.get('Origin') 1331d0c404a6SBrad Bishop if origin: 1332d0c404a6SBrad Bishop response.add_header('Access-Control-Allow-Origin', origin) 1333d0c404a6SBrad Bishop response.add_header( 1334d0c404a6SBrad Bishop 'Access-Control-Allow-Credentials', 'true') 1335d0c404a6SBrad Bishop 1336d0c404a6SBrad Bishop @staticmethod 1337d0c404a6SBrad Bishop def process_method_and_headers(verbs): 1338d0c404a6SBrad Bishop method = request.headers.get('Access-Control-Request-Method') 1339d0c404a6SBrad Bishop headers = request.headers.get('Access-Control-Request-Headers') 1340d0c404a6SBrad Bishop if headers: 1341d0c404a6SBrad Bishop headers = [x.lower() for x in headers.split(',')] 1342d0c404a6SBrad Bishop 1343d0c404a6SBrad Bishop if method in verbs \ 1344d0c404a6SBrad Bishop and headers == ['content-type']: 1345d0c404a6SBrad Bishop response.add_header('Access-Control-Allow-Methods', method) 1346d0c404a6SBrad Bishop response.add_header( 1347d0c404a6SBrad Bishop 'Access-Control-Allow-Headers', 'Content-Type') 134891b46f89SRatan Gupta response.add_header('X-Frame-Options', 'deny') 134991b46f89SRatan Gupta response.add_header('X-Content-Type-Options', 'nosniff') 135091b46f89SRatan Gupta response.add_header('X-XSS-Protection', '1; mode=block') 135191b46f89SRatan Gupta response.add_header( 135291b46f89SRatan Gupta 'Content-Security-Policy', "default-src 'self'") 135391b46f89SRatan Gupta response.add_header( 135491b46f89SRatan Gupta 'Strict-Transport-Security', 135591b46f89SRatan Gupta 'max-age=31536000; includeSubDomains; preload') 1356d0c404a6SBrad Bishop 1357d0c404a6SBrad Bishop def __init__(self, app): 1358d0c404a6SBrad Bishop app.install_error_callback(self.error_callback) 1359d0c404a6SBrad Bishop 1360d0c404a6SBrad Bishop def apply(self, callback, route): 1361d0c404a6SBrad Bishop undecorated = route.get_undecorated_callback() 1362d0c404a6SBrad Bishop if not isinstance(undecorated, RouteHandler): 1363d0c404a6SBrad Bishop return callback 1364d0c404a6SBrad Bishop 1365d0c404a6SBrad Bishop if not getattr(undecorated, '_enable_cors', None): 1366d0c404a6SBrad Bishop return callback 1367d0c404a6SBrad Bishop 1368d0c404a6SBrad Bishop def wrap(*a, **kw): 1369d0c404a6SBrad Bishop self.process_origin() 1370d0c404a6SBrad Bishop self.process_method_and_headers(undecorated._verbs) 1371d0c404a6SBrad Bishop return callback(*a, **kw) 1372d0c404a6SBrad Bishop 1373d0c404a6SBrad Bishop return wrap 1374d0c404a6SBrad Bishop 1375d0c404a6SBrad Bishop def error_callback(self, **kw): 1376d0c404a6SBrad Bishop self.process_origin() 1377d0c404a6SBrad Bishop 1378d0c404a6SBrad Bishop 13792c6fc760SBrad Bishopclass JsonApiRequestPlugin(object): 13802c6fc760SBrad Bishop ''' Ensures request content satisfies the OpenBMC json api format. ''' 13812c6fc760SBrad Bishop name = 'json_api_request' 13822c6fc760SBrad Bishop api = 2 13832c6fc760SBrad Bishop 13842c6fc760SBrad Bishop error_str = "Expecting request format { 'data': <value> }, got '%s'" 13852c6fc760SBrad Bishop type_error_str = "Unsupported Content-Type: '%s'" 13862c6fc760SBrad Bishop json_type = "application/json" 13872c6fc760SBrad Bishop request_methods = ['PUT', 'POST', 'PATCH'] 13882c6fc760SBrad Bishop 13892c6fc760SBrad Bishop @staticmethod 13902c6fc760SBrad Bishop def content_expected(): 13912c6fc760SBrad Bishop return request.method in JsonApiRequestPlugin.request_methods 13922c6fc760SBrad Bishop 13932c6fc760SBrad Bishop def validate_request(self): 13942c6fc760SBrad Bishop if request.content_length > 0 and \ 13952c6fc760SBrad Bishop request.content_type != self.json_type: 13962c6fc760SBrad Bishop abort(415, self.type_error_str % request.content_type) 13972c6fc760SBrad Bishop 13982c6fc760SBrad Bishop try: 13992c6fc760SBrad Bishop request.parameter_list = request.json.get('data') 1400249d1327SCamVan Nguyen except ValueError as e: 14012c6fc760SBrad Bishop abort(400, str(e)) 14022c6fc760SBrad Bishop except (AttributeError, KeyError, TypeError): 14032c6fc760SBrad Bishop abort(400, self.error_str % request.json) 14042c6fc760SBrad Bishop 14052c6fc760SBrad Bishop def apply(self, callback, route): 1406fb6cd483SDeepak Kodihalli content_type = getattr( 1407fb6cd483SDeepak Kodihalli route.get_undecorated_callback(), '_content_type', None) 1408fb6cd483SDeepak Kodihalli if self.json_type != content_type: 1409fb6cd483SDeepak Kodihalli return callback 1410fb6cd483SDeepak Kodihalli 14112c6fc760SBrad Bishop verbs = getattr( 14122c6fc760SBrad Bishop route.get_undecorated_callback(), '_verbs', None) 14132c6fc760SBrad Bishop if verbs is None: 14142c6fc760SBrad Bishop return callback 14152c6fc760SBrad Bishop 14162c6fc760SBrad Bishop if not set(self.request_methods).intersection(verbs): 14172c6fc760SBrad Bishop return callback 14182c6fc760SBrad Bishop 14192c6fc760SBrad Bishop def wrap(*a, **kw): 14202c6fc760SBrad Bishop if self.content_expected(): 14212c6fc760SBrad Bishop self.validate_request() 14222c6fc760SBrad Bishop return callback(*a, **kw) 14232c6fc760SBrad Bishop 14242c6fc760SBrad Bishop return wrap 14252c6fc760SBrad Bishop 14262c6fc760SBrad Bishop 14272c6fc760SBrad Bishopclass JsonApiRequestTypePlugin(object): 14282c6fc760SBrad Bishop ''' Ensures request content type satisfies the OpenBMC json api format. ''' 14292c6fc760SBrad Bishop name = 'json_api_method_request' 14302c6fc760SBrad Bishop api = 2 14312c6fc760SBrad Bishop 14322c6fc760SBrad Bishop error_str = "Expecting request format { 'data': %s }, got '%s'" 1433fb6cd483SDeepak Kodihalli json_type = "application/json" 14342c6fc760SBrad Bishop 14352c6fc760SBrad Bishop def apply(self, callback, route): 1436fb6cd483SDeepak Kodihalli content_type = getattr( 1437fb6cd483SDeepak Kodihalli route.get_undecorated_callback(), '_content_type', None) 1438fb6cd483SDeepak Kodihalli if self.json_type != content_type: 1439fb6cd483SDeepak Kodihalli return callback 1440fb6cd483SDeepak Kodihalli 14412c6fc760SBrad Bishop request_type = getattr( 14422c6fc760SBrad Bishop route.get_undecorated_callback(), 'request_type', None) 14432c6fc760SBrad Bishop if request_type is None: 14442c6fc760SBrad Bishop return callback 14452c6fc760SBrad Bishop 14462c6fc760SBrad Bishop def validate_request(): 14472c6fc760SBrad Bishop if not isinstance(request.parameter_list, request_type): 14482c6fc760SBrad Bishop abort(400, self.error_str % (str(request_type), request.json)) 14492c6fc760SBrad Bishop 14502c6fc760SBrad Bishop def wrap(*a, **kw): 14512c6fc760SBrad Bishop if JsonApiRequestPlugin.content_expected(): 14522c6fc760SBrad Bishop validate_request() 14532c6fc760SBrad Bishop return callback(*a, **kw) 14542c6fc760SBrad Bishop 14552c6fc760SBrad Bishop return wrap 14562c6fc760SBrad Bishop 14572c6fc760SBrad Bishop 1458080a48edSBrad Bishopclass JsonErrorsPlugin(JSONPlugin): 1459080a48edSBrad Bishop ''' Extend the Bottle JSONPlugin such that it also encodes error 1460080a48edSBrad Bishop responses. ''' 1461080a48edSBrad Bishop 1462080a48edSBrad Bishop def __init__(self, app, **kw): 1463080a48edSBrad Bishop super(JsonErrorsPlugin, self).__init__(**kw) 1464080a48edSBrad Bishop self.json_opts = { 1465249d1327SCamVan Nguyen x: y for x, y in kw.items() 1466080a48edSBrad Bishop if x in ['indent', 'sort_keys']} 1467080a48edSBrad Bishop app.install_error_callback(self.error_callback) 1468080a48edSBrad Bishop 1469080a48edSBrad Bishop def error_callback(self, response_object, response_body, **kw): 1470080a48edSBrad Bishop response_body['body'] = json.dumps(response_object, **self.json_opts) 1471080a48edSBrad Bishop response.content_type = 'application/json' 1472080a48edSBrad Bishop 1473080a48edSBrad Bishop 14742c6fc760SBrad Bishopclass JsonApiResponsePlugin(object): 1475080a48edSBrad Bishop ''' Emits responses in the OpenBMC json api format. ''' 14762c6fc760SBrad Bishop name = 'json_api_response' 14772c6fc760SBrad Bishop api = 2 14782c6fc760SBrad Bishop 1479d4c1c55cSBrad Bishop @staticmethod 1480d4c1c55cSBrad Bishop def has_body(): 1481d4c1c55cSBrad Bishop return request.method not in ['OPTIONS'] 1482d4c1c55cSBrad Bishop 1483080a48edSBrad Bishop def __init__(self, app): 1484080a48edSBrad Bishop app.install_error_callback(self.error_callback) 1485080a48edSBrad Bishop 14866691e7caSMatt Spinler @staticmethod 14876691e7caSMatt Spinler def dbus_boolean_to_bool(data): 14886691e7caSMatt Spinler ''' Convert all dbus.Booleans to true/false instead of 1/0 as 14896691e7caSMatt Spinler the JSON encoder thinks they're ints. Note that unlike 14906691e7caSMatt Spinler dicts and lists, tuples (from a dbus.Struct) are immutable 14916691e7caSMatt Spinler so they need special handling. ''' 14926691e7caSMatt Spinler 14936691e7caSMatt Spinler def walkdict(data): 14946691e7caSMatt Spinler for key, value in data.items(): 14956691e7caSMatt Spinler if isinstance(value, dbus.Boolean): 14966691e7caSMatt Spinler data[key] = bool(value) 14976691e7caSMatt Spinler elif isinstance(value, tuple): 14986691e7caSMatt Spinler data[key] = walktuple(value) 14996691e7caSMatt Spinler else: 15006691e7caSMatt Spinler JsonApiResponsePlugin.dbus_boolean_to_bool(value) 15016691e7caSMatt Spinler 15026691e7caSMatt Spinler def walklist(data): 15036691e7caSMatt Spinler for i in range(len(data)): 15046691e7caSMatt Spinler if isinstance(data[i], dbus.Boolean): 15056691e7caSMatt Spinler data[i] = bool(data[i]) 15066691e7caSMatt Spinler elif isinstance(data[i], tuple): 15076691e7caSMatt Spinler data[i] = walktuple(data[i]) 15086691e7caSMatt Spinler else: 15096691e7caSMatt Spinler JsonApiResponsePlugin.dbus_boolean_to_bool(data[i]) 15106691e7caSMatt Spinler 15116691e7caSMatt Spinler def walktuple(data): 15126691e7caSMatt Spinler new = [] 15136691e7caSMatt Spinler for item in data: 15146691e7caSMatt Spinler if isinstance(item, dbus.Boolean): 15156691e7caSMatt Spinler item = bool(item) 15166691e7caSMatt Spinler else: 15176691e7caSMatt Spinler JsonApiResponsePlugin.dbus_boolean_to_bool(item) 15186691e7caSMatt Spinler new.append(item) 15196691e7caSMatt Spinler return tuple(new) 15206691e7caSMatt Spinler 15216691e7caSMatt Spinler if isinstance(data, dict): 15226691e7caSMatt Spinler walkdict(data) 15236691e7caSMatt Spinler elif isinstance(data, list): 15246691e7caSMatt Spinler walklist(data) 15256691e7caSMatt Spinler 15262c6fc760SBrad Bishop def apply(self, callback, route): 1527944cd04fSBrad Bishop skip = getattr( 1528944cd04fSBrad Bishop route.get_undecorated_callback(), 'suppress_json_resp', None) 1529944cd04fSBrad Bishop if skip: 15301444fd8cSJayanth Othayoth return callback 15311444fd8cSJayanth Othayoth 15322c6fc760SBrad Bishop def wrap(*a, **kw): 1533d4c1c55cSBrad Bishop data = callback(*a, **kw) 15346691e7caSMatt Spinler JsonApiResponsePlugin.dbus_boolean_to_bool(data) 1535d4c1c55cSBrad Bishop if self.has_body(): 1536d4c1c55cSBrad Bishop resp = {'data': data} 15372c6fc760SBrad Bishop resp['status'] = 'ok' 15382c6fc760SBrad Bishop resp['message'] = response.status_line 15392c6fc760SBrad Bishop return resp 15402c6fc760SBrad Bishop return wrap 15412c6fc760SBrad Bishop 1542080a48edSBrad Bishop def error_callback(self, error, response_object, **kw): 15432c6fc760SBrad Bishop response_object['message'] = error.status_line 15449c2531eeSBrad Bishop response_object['status'] = 'error' 1545080a48edSBrad Bishop response_object.setdefault('data', {})['description'] = str(error.body) 15462c6fc760SBrad Bishop if error.status_code == 500: 15472c6fc760SBrad Bishop response_object['data']['exception'] = repr(error.exception) 15482c6fc760SBrad Bishop response_object['data']['traceback'] = error.traceback.splitlines() 15492c6fc760SBrad Bishop 15502c6fc760SBrad Bishop 1551080a48edSBrad Bishopclass JsonpPlugin(object): 15522c6fc760SBrad Bishop ''' Json javascript wrapper. ''' 15532c6fc760SBrad Bishop name = 'jsonp' 15542c6fc760SBrad Bishop api = 2 15552c6fc760SBrad Bishop 1556080a48edSBrad Bishop def __init__(self, app, **kw): 1557080a48edSBrad Bishop app.install_error_callback(self.error_callback) 15582c6fc760SBrad Bishop 15592c6fc760SBrad Bishop @staticmethod 15602c6fc760SBrad Bishop def to_jsonp(json): 15612c6fc760SBrad Bishop jwrapper = request.query.callback or None 15622c6fc760SBrad Bishop if(jwrapper): 15632c6fc760SBrad Bishop response.set_header('Content-Type', 'application/javascript') 15642c6fc760SBrad Bishop json = jwrapper + '(' + json + ');' 15652c6fc760SBrad Bishop return json 15662c6fc760SBrad Bishop 15672c6fc760SBrad Bishop def apply(self, callback, route): 15682c6fc760SBrad Bishop def wrap(*a, **kw): 15692c6fc760SBrad Bishop return self.to_jsonp(callback(*a, **kw)) 15702c6fc760SBrad Bishop return wrap 15712c6fc760SBrad Bishop 1572080a48edSBrad Bishop def error_callback(self, response_body, **kw): 1573080a48edSBrad Bishop response_body['body'] = self.to_jsonp(response_body['body']) 15742c6fc760SBrad Bishop 15752c6fc760SBrad Bishop 1576461367a5SDeepak Kodihalliclass ContentCheckerPlugin(object): 1577461367a5SDeepak Kodihalli ''' Ensures that a route is associated with the expected content-type 1578461367a5SDeepak Kodihalli header. ''' 1579461367a5SDeepak Kodihalli name = 'content_checker' 1580461367a5SDeepak Kodihalli api = 2 1581461367a5SDeepak Kodihalli 1582461367a5SDeepak Kodihalli class Checker: 1583461367a5SDeepak Kodihalli def __init__(self, type, callback): 1584461367a5SDeepak Kodihalli self.expected_type = type 1585461367a5SDeepak Kodihalli self.callback = callback 1586461367a5SDeepak Kodihalli self.error_str = "Expecting content type '%s', got '%s'" 1587461367a5SDeepak Kodihalli 1588461367a5SDeepak Kodihalli def __call__(self, *a, **kw): 1589db1a21e3SDeepak Kodihalli if request.method in ['PUT', 'POST', 'PATCH'] and \ 1590db1a21e3SDeepak Kodihalli self.expected_type and \ 1591461367a5SDeepak Kodihalli self.expected_type != request.content_type: 1592461367a5SDeepak Kodihalli abort(415, self.error_str % (self.expected_type, 1593461367a5SDeepak Kodihalli request.content_type)) 1594461367a5SDeepak Kodihalli 1595461367a5SDeepak Kodihalli return self.callback(*a, **kw) 1596461367a5SDeepak Kodihalli 1597461367a5SDeepak Kodihalli def apply(self, callback, route): 1598461367a5SDeepak Kodihalli content_type = getattr( 1599461367a5SDeepak Kodihalli route.get_undecorated_callback(), '_content_type', None) 1600461367a5SDeepak Kodihalli 1601461367a5SDeepak Kodihalli return self.Checker(content_type, callback) 1602461367a5SDeepak Kodihalli 1603461367a5SDeepak Kodihalli 16046e1ca530SDeepak Kodihalliclass LoggingPlugin(object): 16056e1ca530SDeepak Kodihalli ''' Wraps a request in order to emit a log after the request is handled. ''' 16066e1ca530SDeepak Kodihalli name = 'loggingp' 16076e1ca530SDeepak Kodihalli api = 2 16086e1ca530SDeepak Kodihalli 16096e1ca530SDeepak Kodihalli class Logger: 16106e1ca530SDeepak Kodihalli def __init__(self, suppress_json_logging, callback, app): 16116e1ca530SDeepak Kodihalli self.suppress_json_logging = suppress_json_logging 16126e1ca530SDeepak Kodihalli self.callback = callback 16136e1ca530SDeepak Kodihalli self.app = app 161495803684SDeepak Kodihalli self.logging_enabled = None 161595803684SDeepak Kodihalli self.bus = dbus.SystemBus() 161695803684SDeepak Kodihalli self.dbus_path = '/xyz/openbmc_project/logging/rest_api_logs' 16174b412ac9SDeepak Kodihalli self.no_json = [ 16184b412ac9SDeepak Kodihalli '/xyz/openbmc_project/user/ldap/action/CreateConfig' 16194b412ac9SDeepak Kodihalli ] 162095803684SDeepak Kodihalli self.bus.add_signal_receiver( 162195803684SDeepak Kodihalli self.properties_changed_handler, 162295803684SDeepak Kodihalli dbus_interface=dbus.PROPERTIES_IFACE, 162395803684SDeepak Kodihalli signal_name='PropertiesChanged', 162495803684SDeepak Kodihalli path=self.dbus_path) 162595803684SDeepak Kodihalli Greenlet.spawn(self.dbus_loop) 16266e1ca530SDeepak Kodihalli 16276e1ca530SDeepak Kodihalli def __call__(self, *a, **kw): 16286e1ca530SDeepak Kodihalli resp = self.callback(*a, **kw) 162995803684SDeepak Kodihalli if not self.enabled(): 16304aa10001SDeepak Kodihalli return resp 16316e1ca530SDeepak Kodihalli if request.method == 'GET': 16324aa10001SDeepak Kodihalli return resp 16336e1ca530SDeepak Kodihalli json = request.json 16346e1ca530SDeepak Kodihalli if self.suppress_json_logging: 16356e1ca530SDeepak Kodihalli json = None 16364b412ac9SDeepak Kodihalli elif any(substring in request.url for substring in self.no_json): 16374b412ac9SDeepak Kodihalli json = None 16386e1ca530SDeepak Kodihalli session = self.app.session_handler.get_session_from_cookie() 16396e1ca530SDeepak Kodihalli user = None 16406e1ca530SDeepak Kodihalli if "/login" in request.url: 16416e1ca530SDeepak Kodihalli user = request.parameter_list[0] 16426e1ca530SDeepak Kodihalli elif session is not None: 16436e1ca530SDeepak Kodihalli user = session['user'] 16446e1ca530SDeepak Kodihalli print("{remote} user:{user} {method} {url} json:{json} {status}" \ 16456e1ca530SDeepak Kodihalli .format( 16466e1ca530SDeepak Kodihalli user=user, 16476e1ca530SDeepak Kodihalli remote=request.remote_addr, 16486e1ca530SDeepak Kodihalli method=request.method, 16496e1ca530SDeepak Kodihalli url=request.url, 16506e1ca530SDeepak Kodihalli json=json, 16516e1ca530SDeepak Kodihalli status=response.status)) 16524aa10001SDeepak Kodihalli return resp 16536e1ca530SDeepak Kodihalli 165495803684SDeepak Kodihalli def enabled(self): 165595803684SDeepak Kodihalli if self.logging_enabled is None: 165695803684SDeepak Kodihalli try: 165795803684SDeepak Kodihalli obj = self.bus.get_object( 165895803684SDeepak Kodihalli 'xyz.openbmc_project.Settings', 165995803684SDeepak Kodihalli self.dbus_path) 166095803684SDeepak Kodihalli iface = dbus.Interface(obj, dbus.PROPERTIES_IFACE) 166195803684SDeepak Kodihalli logging_enabled = iface.Get( 166295803684SDeepak Kodihalli 'xyz.openbmc_project.Object.Enable', 166395803684SDeepak Kodihalli 'Enabled') 166495803684SDeepak Kodihalli self.logging_enabled = logging_enabled 166595803684SDeepak Kodihalli except dbus.exceptions.DBusException: 166695803684SDeepak Kodihalli self.logging_enabled = False 166795803684SDeepak Kodihalli return self.logging_enabled 166895803684SDeepak Kodihalli 166995803684SDeepak Kodihalli def dbus_loop(self): 167095803684SDeepak Kodihalli loop = gobject.MainLoop() 167195803684SDeepak Kodihalli gcontext = loop.get_context() 167295803684SDeepak Kodihalli while loop is not None: 167395803684SDeepak Kodihalli try: 167495803684SDeepak Kodihalli if gcontext.pending(): 167595803684SDeepak Kodihalli gcontext.iteration() 167695803684SDeepak Kodihalli else: 167795803684SDeepak Kodihalli gevent.sleep(5) 167895803684SDeepak Kodihalli except Exception as e: 167995803684SDeepak Kodihalli break 168095803684SDeepak Kodihalli 168195803684SDeepak Kodihalli def properties_changed_handler(self, interface, new, old, **kw): 168295803684SDeepak Kodihalli self.logging_enabled = new.values()[0] 168395803684SDeepak Kodihalli 16846e1ca530SDeepak Kodihalli def apply(self, callback, route): 16856e1ca530SDeepak Kodihalli cb = route.get_undecorated_callback() 16866e1ca530SDeepak Kodihalli skip = getattr( 16876e1ca530SDeepak Kodihalli cb, 'suppress_logging', None) 16886e1ca530SDeepak Kodihalli if skip: 16896e1ca530SDeepak Kodihalli return callback 16906e1ca530SDeepak Kodihalli 16916e1ca530SDeepak Kodihalli suppress_json_logging = getattr( 16926e1ca530SDeepak Kodihalli cb, 'suppress_json_logging', None) 16936e1ca530SDeepak Kodihalli return self.Logger(suppress_json_logging, callback, cb.app) 16946e1ca530SDeepak Kodihalli 16956e1ca530SDeepak Kodihalli 16962c6fc760SBrad Bishopclass App(Bottle): 16970fe213fbSDeepak Kodihalli def __init__(self, **kw): 16982c6fc760SBrad Bishop super(App, self).__init__(autojson=False) 1699b209dd16SDeepak Kodihalli 1700b209dd16SDeepak Kodihalli self.have_wsock = kw.get('have_wsock', False) 1701d08a4569SAlexander Filippov self.with_bmc_check = '--with-bmc-check' in sys.argv 1702b209dd16SDeepak Kodihalli 17032c6fc760SBrad Bishop self.bus = dbus.SystemBus() 17042c6fc760SBrad Bishop self.mapper = obmc.mapper.Mapper(self.bus) 1705080a48edSBrad Bishop self.error_callbacks = [] 17062c6fc760SBrad Bishop 17072c6fc760SBrad Bishop self.install_hooks() 17082c6fc760SBrad Bishop self.install_plugins() 17092c6fc760SBrad Bishop self.create_handlers() 17102c6fc760SBrad Bishop self.install_handlers() 17112c6fc760SBrad Bishop 17122c6fc760SBrad Bishop def install_plugins(self): 17132c6fc760SBrad Bishop # install json api plugins 17142c6fc760SBrad Bishop json_kw = {'indent': 2, 'sort_keys': True} 17152c6fc760SBrad Bishop self.install(AuthorizationPlugin()) 1716d0c404a6SBrad Bishop self.install(CorsPlugin(self)) 1717461367a5SDeepak Kodihalli self.install(ContentCheckerPlugin()) 1718080a48edSBrad Bishop self.install(JsonpPlugin(self, **json_kw)) 1719080a48edSBrad Bishop self.install(JsonErrorsPlugin(self, **json_kw)) 1720080a48edSBrad Bishop self.install(JsonApiResponsePlugin(self)) 17212c6fc760SBrad Bishop self.install(JsonApiRequestPlugin()) 17222c6fc760SBrad Bishop self.install(JsonApiRequestTypePlugin()) 17236e1ca530SDeepak Kodihalli self.install(LoggingPlugin()) 17242c6fc760SBrad Bishop 17252c6fc760SBrad Bishop def install_hooks(self): 1726080a48edSBrad Bishop self.error_handler_type = type(self.default_error_handler) 1727080a48edSBrad Bishop self.original_error_handler = self.default_error_handler 1728080a48edSBrad Bishop self.default_error_handler = self.error_handler_type( 1729080a48edSBrad Bishop self.custom_error_handler, self, Bottle) 1730080a48edSBrad Bishop 17312c6fc760SBrad Bishop self.real_router_match = self.router.match 17322c6fc760SBrad Bishop self.router.match = self.custom_router_match 17332c6fc760SBrad Bishop self.add_hook('before_request', self.strip_extra_slashes) 17342c6fc760SBrad Bishop 17352c6fc760SBrad Bishop def create_handlers(self): 17362c6fc760SBrad Bishop # create route handlers 17372c6fc760SBrad Bishop self.session_handler = SessionHandler(self, self.bus) 1738d41643ecSMatt Spinler self.web_handler = WebHandler(self, self.bus) 17392c6fc760SBrad Bishop self.directory_handler = DirectoryHandler(self, self.bus) 17402c6fc760SBrad Bishop self.list_names_handler = ListNamesHandler(self, self.bus) 17412c6fc760SBrad Bishop self.list_handler = ListHandler(self, self.bus) 17422c6fc760SBrad Bishop self.method_handler = MethodHandler(self, self.bus) 17432c6fc760SBrad Bishop self.property_handler = PropertyHandler(self, self.bus) 17442c6fc760SBrad Bishop self.schema_handler = SchemaHandler(self, self.bus) 17457ec0a4f4SDeepak Kodihalli self.image_upload_post_handler = ImagePostHandler(self, self.bus) 17467ec0a4f4SDeepak Kodihalli self.image_upload_put_handler = ImagePutHandler(self, self.bus) 17479bc94997SJayanth Othayoth self.download_dump_get_handler = DownloadDumpHandler(self, self.bus) 1748dee2ef57SDhruvaraj Subhashchandran self.certificate_put_handler = CertificatePutHandler(self, self.bus) 1749b209dd16SDeepak Kodihalli if self.have_wsock: 1750b209dd16SDeepak Kodihalli self.event_handler = EventHandler(self, self.bus) 17515c518f63SDeepak Kodihalli self.host_console_handler = HostConsoleHandler(self, self.bus) 17522c6fc760SBrad Bishop self.instance_handler = InstanceHandler(self, self.bus) 17532c6fc760SBrad Bishop 17542c6fc760SBrad Bishop def install_handlers(self): 17552c6fc760SBrad Bishop self.session_handler.install() 1756d41643ecSMatt Spinler self.web_handler.install() 17572c6fc760SBrad Bishop self.directory_handler.install() 17582c6fc760SBrad Bishop self.list_names_handler.install() 17592c6fc760SBrad Bishop self.list_handler.install() 17602c6fc760SBrad Bishop self.method_handler.install() 17612c6fc760SBrad Bishop self.property_handler.install() 17622c6fc760SBrad Bishop self.schema_handler.install() 17637ec0a4f4SDeepak Kodihalli self.image_upload_post_handler.install() 17647ec0a4f4SDeepak Kodihalli self.image_upload_put_handler.install() 17659bc94997SJayanth Othayoth self.download_dump_get_handler.install() 1766dee2ef57SDhruvaraj Subhashchandran self.certificate_put_handler.install() 1767b209dd16SDeepak Kodihalli if self.have_wsock: 1768b209dd16SDeepak Kodihalli self.event_handler.install() 17695c518f63SDeepak Kodihalli self.host_console_handler.install() 17702c6fc760SBrad Bishop # this has to come last, since it matches everything 17712c6fc760SBrad Bishop self.instance_handler.install() 17722c6fc760SBrad Bishop 1773080a48edSBrad Bishop def install_error_callback(self, callback): 1774080a48edSBrad Bishop self.error_callbacks.insert(0, callback) 1775080a48edSBrad Bishop 17762c6fc760SBrad Bishop def custom_router_match(self, environ): 17772c6fc760SBrad Bishop ''' The built-in Bottle algorithm for figuring out if a 404 or 405 is 17782c6fc760SBrad Bishop needed doesn't work for us since the instance rules match 17792c6fc760SBrad Bishop everything. This monkey-patch lets the route handler figure 17802c6fc760SBrad Bishop out which response is needed. This could be accomplished 17812c6fc760SBrad Bishop with a hook but that would require calling the router match 17822c6fc760SBrad Bishop function twice. 17832c6fc760SBrad Bishop ''' 17842c6fc760SBrad Bishop route, args = self.real_router_match(environ) 17852c6fc760SBrad Bishop if isinstance(route.callback, RouteHandler): 17862c6fc760SBrad Bishop route.callback._setup(**args) 17872c6fc760SBrad Bishop 17882c6fc760SBrad Bishop return route, args 17892c6fc760SBrad Bishop 1790080a48edSBrad Bishop def custom_error_handler(self, res, error): 1791f01d0ba3SGunnar Mills ''' Allow plugins to modify error responses too via this custom 1792080a48edSBrad Bishop error handler. ''' 1793080a48edSBrad Bishop 1794080a48edSBrad Bishop response_object = {} 1795080a48edSBrad Bishop response_body = {} 1796080a48edSBrad Bishop for x in self.error_callbacks: 1797080a48edSBrad Bishop x(error=error, 1798080a48edSBrad Bishop response_object=response_object, 1799080a48edSBrad Bishop response_body=response_body) 1800080a48edSBrad Bishop 1801080a48edSBrad Bishop return response_body.get('body', "") 1802080a48edSBrad Bishop 18032c6fc760SBrad Bishop @staticmethod 18042c6fc760SBrad Bishop def strip_extra_slashes(): 18052c6fc760SBrad Bishop path = request.environ['PATH_INFO'] 18062c6fc760SBrad Bishop trailing = ("", "/")[path[-1] == '/'] 1807249d1327SCamVan Nguyen parts = list(filter(bool, path.split('/'))) 18082c6fc760SBrad Bishop request.environ['PATH_INFO'] = '/' + '/'.join(parts) + trailing 1809