blob: 3d056fb9b65efbb890b6f7d665c6185bdf35ae09 [file] [log] [blame]
Brad Bishop68caa1e2016-03-04 15:42:08 -05001# Contributors Listed Below - COPYRIGHT 2016
2# [+] International Business Machines Corp.
3#
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14# implied. See the License for the specific language governing
15# permissions and limitations under the License.
16
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050017import os
Brad Bishopaa65f6e2015-10-27 16:28:51 -040018import dbus
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050019import dbus.exceptions
20import json
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050021from xml.etree import ElementTree
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050022from bottle import Bottle, abort, request, response, JSONPlugin, HTTPError
Jayanth Othayoth9bc94992017-06-29 06:30:40 -050023from bottle import static_file
Brad Bishopb103d2d2016-03-04 16:19:14 -050024import obmc.utils.misc
Brad Bishopb103d2d2016-03-04 16:19:14 -050025from obmc.dbuslib.introspection import IntrospectionNodeParser
26import obmc.mapper
Brad Bishop2f428582015-12-02 10:56:11 -050027import spwd
28import grp
29import crypt
Deepak Kodihalli1af301a2017-04-11 07:29:01 -050030import tempfile
Leonel Gonzalez0bdef952017-04-18 08:17:49 -050031import re
Deepak Kodihalli639b5022017-10-13 06:40:26 -050032have_wsock = True
33try:
34 from geventwebsocket import WebSocketError
35except ImportError:
36 have_wsock = False
37if have_wsock:
38 from dbus.mainloop.glib import DBusGMainLoop
39 DBusGMainLoop(set_as_default=True)
40 import gobject
41 import gevent
Brad Bishopaa65f6e2015-10-27 16:28:51 -040042
Adriana Kobylakf92cf4d2017-12-13 11:46:50 -060043DBUS_UNKNOWN_INTERFACE = 'org.freedesktop.DBus.Error.UnknownInterface'
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050044DBUS_UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod'
45DBUS_INVALID_ARGS = 'org.freedesktop.DBus.Error.InvalidArgs'
Brad Bishopd4578922015-12-02 11:10:36 -050046DBUS_TYPE_ERROR = 'org.freedesktop.DBus.Python.TypeError'
Deepak Kodihalli6075bb42017-04-04 05:49:17 -050047DELETE_IFACE = 'xyz.openbmc_project.Object.Delete'
Brad Bishop9ee57c42015-11-03 14:59:29 -050048
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050049_4034_msg = "The specified %s cannot be %s: '%s'"
Brad Bishopaa65f6e2015-10-27 16:28:51 -040050
Brad Bishop87b63c12016-03-18 14:47:51 -040051
Brad Bishop2f428582015-12-02 10:56:11 -050052def valid_user(session, *a, **kw):
Brad Bishop87b63c12016-03-18 14:47:51 -040053 ''' Authorization plugin callback that checks
54 that the user is logged in. '''
55 if session is None:
Brad Bishopdc3fbfa2016-09-08 09:51:38 -040056 abort(401, 'Login required')
Brad Bishop87b63c12016-03-18 14:47:51 -040057
Brad Bishop2f428582015-12-02 10:56:11 -050058
Leonel Gonzalez0bdef952017-04-18 08:17:49 -050059def get_type_signature_by_introspection(bus, service, object_path,
60 property_name):
61 obj = bus.get_object(service, object_path)
62 iface = dbus.Interface(obj, 'org.freedesktop.DBus.Introspectable')
63 xml_string = iface.Introspect()
64 for child in ElementTree.fromstring(xml_string):
65 # Iterate over each interfaces's properties to find
66 # matching property_name, and return its signature string
67 if child.tag == 'interface':
68 for i in child.iter():
69 if ('name' in i.attrib) and \
70 (i.attrib['name'] == property_name):
71 type_signature = i.attrib['type']
72 return type_signature
73
74
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +053075def get_method_signature(bus, service, object_path, interface, method):
76 obj = bus.get_object(service, object_path)
77 iface = dbus.Interface(obj, 'org.freedesktop.DBus.Introspectable')
78 xml_string = iface.Introspect()
79 arglist = []
80
81 root = ElementTree.fromstring(xml_string)
82 for dbus_intf in root.findall('interface'):
83 if (dbus_intf.get('name') == interface):
84 for dbus_method in dbus_intf.findall('method'):
85 if(dbus_method.get('name') == method):
86 for arg in dbus_method.findall('arg'):
87 arglist.append(arg.get('type'))
88 return arglist
89
90
Leonel Gonzalez0bdef952017-04-18 08:17:49 -050091def split_struct_signature(signature):
92 struct_regex = r'(b|y|n|i|x|q|u|t|d|s|a\(.+?\)|\(.+?\))|a\{.+?\}+?'
93 struct_matches = re.findall(struct_regex, signature)
94 return struct_matches
95
96
97def convert_type(signature, value):
98 # Basic Types
99 converted_value = None
100 converted_container = None
101 basic_types = {'b': bool, 'y': dbus.Byte, 'n': dbus.Int16, 'i': int,
102 'x': long, 'q': dbus.UInt16, 'u': dbus.UInt32,
103 't': dbus.UInt64, 'd': float, 's': str}
104 array_matches = re.match(r'a\((\S+)\)', signature)
105 struct_matches = re.match(r'\((\S+)\)', signature)
106 dictionary_matches = re.match(r'a{(\S+)}', signature)
107 if signature in basic_types:
108 converted_value = basic_types[signature](value)
109 return converted_value
110 # Array
111 if array_matches:
112 element_type = array_matches.group(1)
113 converted_container = list()
114 # Test if value is a list
115 # to avoid iterating over each character in a string.
116 # Iterate over each item and convert type
117 if isinstance(value, list):
118 for i in value:
119 converted_element = convert_type(element_type, i)
120 converted_container.append(converted_element)
121 # Convert non-sequence to expected type, and append to list
122 else:
123 converted_element = convert_type(element_type, value)
124 converted_container.append(converted_element)
125 return converted_container
126 # Struct
127 if struct_matches:
128 element_types = struct_matches.group(1)
129 split_element_types = split_struct_signature(element_types)
130 converted_container = list()
131 # Test if value is a list
132 if isinstance(value, list):
133 for index, val in enumerate(value):
134 converted_element = convert_type(split_element_types[index],
135 value[index])
136 converted_container.append(converted_element)
137 else:
138 converted_element = convert_type(element_types, value)
139 converted_container.append(converted_element)
140 return tuple(converted_container)
141 # Dictionary
142 if dictionary_matches:
143 element_types = dictionary_matches.group(1)
144 split_element_types = split_struct_signature(element_types)
145 converted_container = dict()
146 # Convert each element of dict
147 for key, val in value.iteritems():
148 converted_key = convert_type(split_element_types[0], key)
149 converted_val = convert_type(split_element_types[1], val)
150 converted_container[converted_key] = converted_val
151 return converted_container
152
153
Brad Bishop2f428582015-12-02 10:56:11 -0500154class UserInGroup:
Brad Bishop87b63c12016-03-18 14:47:51 -0400155 ''' Authorization plugin callback that checks that the user is logged in
156 and a member of a group. '''
157 def __init__(self, group):
158 self.group = group
Brad Bishop2f428582015-12-02 10:56:11 -0500159
Brad Bishop87b63c12016-03-18 14:47:51 -0400160 def __call__(self, session, *a, **kw):
161 valid_user(session, *a, **kw)
162 res = False
Brad Bishop2f428582015-12-02 10:56:11 -0500163
Brad Bishop87b63c12016-03-18 14:47:51 -0400164 try:
165 res = session['user'] in grp.getgrnam(self.group)[3]
166 except KeyError:
167 pass
Brad Bishop2f428582015-12-02 10:56:11 -0500168
Brad Bishop87b63c12016-03-18 14:47:51 -0400169 if not res:
170 abort(403, 'Insufficient access')
171
Brad Bishop2f428582015-12-02 10:56:11 -0500172
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500173class RouteHandler(object):
Brad Bishop6d190602016-04-15 13:09:39 -0400174 _require_auth = obmc.utils.misc.makelist(valid_user)
Brad Bishopd0c404a2017-02-21 09:23:25 -0500175 _enable_cors = True
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400176
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500177 def __init__(self, app, bus, verbs, rules, content_type=''):
Brad Bishop87b63c12016-03-18 14:47:51 -0400178 self.app = app
179 self.bus = bus
Brad Bishopb103d2d2016-03-04 16:19:14 -0500180 self.mapper = obmc.mapper.Mapper(bus)
Brad Bishop6d190602016-04-15 13:09:39 -0400181 self._verbs = obmc.utils.misc.makelist(verbs)
Brad Bishop87b63c12016-03-18 14:47:51 -0400182 self._rules = rules
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500183 self._content_type = content_type
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400184
Brad Bishop88c76a42017-02-21 00:02:02 -0500185 if 'GET' in self._verbs:
186 self._verbs = list(set(self._verbs + ['HEAD']))
Brad Bishopd4c1c552017-02-21 00:07:28 -0500187 if 'OPTIONS' not in self._verbs:
188 self._verbs.append('OPTIONS')
Brad Bishop88c76a42017-02-21 00:02:02 -0500189
Brad Bishop87b63c12016-03-18 14:47:51 -0400190 def _setup(self, **kw):
191 request.route_data = {}
Brad Bishopd4c1c552017-02-21 00:07:28 -0500192
Brad Bishop87b63c12016-03-18 14:47:51 -0400193 if request.method in self._verbs:
Brad Bishopd4c1c552017-02-21 00:07:28 -0500194 if request.method != 'OPTIONS':
195 return self.setup(**kw)
Brad Bishop88c76a42017-02-21 00:02:02 -0500196
Brad Bishopd4c1c552017-02-21 00:07:28 -0500197 # Javascript implementations will not send credentials
198 # with an OPTIONS request. Don't help malicious clients
199 # by checking the path here and returning a 404 if the
200 # path doesn't exist.
201 return None
Brad Bishop88c76a42017-02-21 00:02:02 -0500202
Brad Bishopd4c1c552017-02-21 00:07:28 -0500203 # Return 405
Brad Bishop88c76a42017-02-21 00:02:02 -0500204 raise HTTPError(
205 405, "Method not allowed.", Allow=','.join(self._verbs))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400206
Brad Bishop87b63c12016-03-18 14:47:51 -0400207 def __call__(self, **kw):
208 return getattr(self, 'do_' + request.method.lower())(**kw)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400209
Brad Bishop88c76a42017-02-21 00:02:02 -0500210 def do_head(self, **kw):
211 return self.do_get(**kw)
212
Brad Bishopd4c1c552017-02-21 00:07:28 -0500213 def do_options(self, **kw):
214 for v in self._verbs:
215 response.set_header(
216 'Allow',
217 ','.join(self._verbs))
218 return None
219
Brad Bishop87b63c12016-03-18 14:47:51 -0400220 def install(self):
221 self.app.route(
222 self._rules, callback=self,
Brad Bishopd4c1c552017-02-21 00:07:28 -0500223 method=['OPTIONS', 'GET', 'PUT', 'PATCH', 'POST', 'DELETE'])
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400224
Brad Bishop87b63c12016-03-18 14:47:51 -0400225 @staticmethod
226 def try_mapper_call(f, callback=None, **kw):
227 try:
228 return f(**kw)
229 except dbus.exceptions.DBusException, e:
Brad Bishopfce77562016-11-28 15:44:18 -0500230 if e.get_dbus_name() == \
231 'org.freedesktop.DBus.Error.ObjectPathInUse':
232 abort(503, str(e))
Brad Bishopb103d2d2016-03-04 16:19:14 -0500233 if e.get_dbus_name() != obmc.mapper.MAPPER_NOT_FOUND:
Brad Bishop87b63c12016-03-18 14:47:51 -0400234 raise
235 if callback is None:
236 def callback(e, **kw):
237 abort(404, str(e))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400238
Brad Bishop87b63c12016-03-18 14:47:51 -0400239 callback(e, **kw)
240
241 @staticmethod
242 def try_properties_interface(f, *a):
243 try:
244 return f(*a)
245 except dbus.exceptions.DBusException, e:
Adriana Kobylakf92cf4d2017-12-13 11:46:50 -0600246 if DBUS_UNKNOWN_INTERFACE in e.get_dbus_name():
Brad Bishopf4e74982016-04-01 14:53:05 -0400247 # interface doesn't have any properties
248 return None
Brad Bishop87b63c12016-03-18 14:47:51 -0400249 if DBUS_UNKNOWN_METHOD == e.get_dbus_name():
250 # properties interface not implemented at all
251 return None
252 raise
253
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400254
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500255class DirectoryHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400256 verbs = 'GET'
257 rules = '<path:path>/'
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400258
Brad Bishop87b63c12016-03-18 14:47:51 -0400259 def __init__(self, app, bus):
260 super(DirectoryHandler, self).__init__(
Brad Bishopc431e1a2017-07-10 16:44:51 -0400261 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400262
Brad Bishop87b63c12016-03-18 14:47:51 -0400263 def find(self, path='/'):
264 return self.try_mapper_call(
265 self.mapper.get_subtree_paths, path=path, depth=1)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400266
Brad Bishop87b63c12016-03-18 14:47:51 -0400267 def setup(self, path='/'):
268 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400269
Brad Bishop87b63c12016-03-18 14:47:51 -0400270 def do_get(self, path='/'):
271 return request.route_data['map']
272
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400273
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500274class ListNamesHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400275 verbs = 'GET'
276 rules = ['/list', '<path:path>/list']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400277
Brad Bishop87b63c12016-03-18 14:47:51 -0400278 def __init__(self, app, bus):
279 super(ListNamesHandler, self).__init__(
Brad Bishopc431e1a2017-07-10 16:44:51 -0400280 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400281
Brad Bishop87b63c12016-03-18 14:47:51 -0400282 def find(self, path='/'):
283 return self.try_mapper_call(
284 self.mapper.get_subtree, path=path).keys()
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400285
Brad Bishop87b63c12016-03-18 14:47:51 -0400286 def setup(self, path='/'):
287 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400288
Brad Bishop87b63c12016-03-18 14:47:51 -0400289 def do_get(self, path='/'):
290 return request.route_data['map']
291
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400292
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500293class ListHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400294 verbs = 'GET'
295 rules = ['/enumerate', '<path:path>/enumerate']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400296
Brad Bishop87b63c12016-03-18 14:47:51 -0400297 def __init__(self, app, bus):
298 super(ListHandler, self).__init__(
Brad Bishopc431e1a2017-07-10 16:44:51 -0400299 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400300
Brad Bishop87b63c12016-03-18 14:47:51 -0400301 def find(self, path='/'):
302 return self.try_mapper_call(
303 self.mapper.get_subtree, path=path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400304
Brad Bishop87b63c12016-03-18 14:47:51 -0400305 def setup(self, path='/'):
306 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400307
Brad Bishop87b63c12016-03-18 14:47:51 -0400308 def do_get(self, path='/'):
Brad Bishop71527b42016-04-01 14:51:14 -0400309 return {x: y for x, y in self.mapper.enumerate_subtree(
310 path,
311 mapper_data=request.route_data['map']).dataitems()}
Brad Bishop87b63c12016-03-18 14:47:51 -0400312
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400313
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500314class MethodHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400315 verbs = 'POST'
316 rules = '<path:path>/action/<method>'
317 request_type = list
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500318 content_type = 'application/json'
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400319
Brad Bishop87b63c12016-03-18 14:47:51 -0400320 def __init__(self, app, bus):
321 super(MethodHandler, self).__init__(
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500322 app, bus, self.verbs, self.rules, self.content_type)
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530323 self.service = ''
324 self.interface = ''
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400325
Brad Bishop87b63c12016-03-18 14:47:51 -0400326 def find(self, path, method):
Saqib Khan3a00b1f2017-11-04 15:56:21 -0500327 method_list = []
Brad Bishop87b63c12016-03-18 14:47:51 -0400328 busses = self.try_mapper_call(
329 self.mapper.get_object, path=path)
330 for items in busses.iteritems():
331 m = self.find_method_on_bus(path, method, *items)
332 if m:
Saqib Khan3a00b1f2017-11-04 15:56:21 -0500333 method_list.append(m)
Nagaraju Goruganti765c2c82017-11-13 06:17:13 -0600334 if method_list:
335 return method_list
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400336
Brad Bishop87b63c12016-03-18 14:47:51 -0400337 abort(404, _4034_msg % ('method', 'found', method))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400338
Brad Bishop87b63c12016-03-18 14:47:51 -0400339 def setup(self, path, method):
Saqib Khan3a00b1f2017-11-04 15:56:21 -0500340 request.route_data['map'] = self.find(path, method)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400341
Marri Devender Raobc0c6732017-11-20 00:15:47 -0600342 def do_post(self, path, method, retry=True):
Brad Bishop87b63c12016-03-18 14:47:51 -0400343 try:
Nagaraju Goruganti765c2c82017-11-13 06:17:13 -0600344 args = []
345 if request.parameter_list:
346 args = request.parameter_list
347 # To see if the return type is capable of being merged
348 if len(request.route_data['map']) > 1:
349 results = None
350 for item in request.route_data['map']:
351 tmp = item(*args)
352 if not results:
353 if tmp is not None:
354 results = type(tmp)()
355 if isinstance(results, dict):
356 results = results.update(tmp)
357 elif isinstance(results, list):
358 results = results + tmp
359 elif isinstance(results, type(None)):
360 results = None
361 else:
362 abort(501, 'Don\'t know how to merge method call '
363 'results of {}'.format(type(tmp)))
364 return results
365 # There is only one method
366 return request.route_data['map'][0](*args)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400367
Brad Bishop87b63c12016-03-18 14:47:51 -0400368 except dbus.exceptions.DBusException, e:
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530369 paramlist = []
Marri Devender Raobc0c6732017-11-20 00:15:47 -0600370 if e.get_dbus_name() == DBUS_INVALID_ARGS and retry == True:
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530371
372 signature_list = get_method_signature(self.bus, self.service,
373 path, self.interface,
374 method)
375 if not signature_list:
376 abort(400, "Failed to get method signature: %s" % str(e))
377 if len(signature_list) != len(request.parameter_list):
378 abort(400, "Invalid number of args")
379 converted_value = None
380 try:
381 for index, expected_type in enumerate(signature_list):
382 value = request.parameter_list[index]
383 converted_value = convert_type(expected_type, value)
384 paramlist.append(converted_value)
385 request.parameter_list = paramlist
Marri Devender Raobc0c6732017-11-20 00:15:47 -0600386 self.do_post(path, method, False)
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530387 return
388 except Exception as ex:
Nagaraju Gorugantiab404fa2017-12-14 10:24:40 -0600389 abort(400, "Bad Request/Invalid Args given")
Brad Bishop87b63c12016-03-18 14:47:51 -0400390 abort(400, str(e))
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530391
Brad Bishop87b63c12016-03-18 14:47:51 -0400392 if e.get_dbus_name() == DBUS_TYPE_ERROR:
393 abort(400, str(e))
394 raise
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400395
Brad Bishop87b63c12016-03-18 14:47:51 -0400396 @staticmethod
397 def find_method_in_interface(method, obj, interface, methods):
398 if methods is None:
399 return None
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400400
Brad Bishop6d190602016-04-15 13:09:39 -0400401 method = obmc.utils.misc.find_case_insensitive(method, methods.keys())
Brad Bishop87b63c12016-03-18 14:47:51 -0400402 if method is not None:
403 iface = dbus.Interface(obj, interface)
404 return iface.get_dbus_method(method)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400405
Brad Bishop87b63c12016-03-18 14:47:51 -0400406 def find_method_on_bus(self, path, method, bus, interfaces):
407 obj = self.bus.get_object(bus, path, introspect=False)
408 iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
409 data = iface.Introspect()
410 parser = IntrospectionNodeParser(
411 ElementTree.fromstring(data),
Brad Bishopb103d2d2016-03-04 16:19:14 -0500412 intf_match=obmc.utils.misc.ListMatch(interfaces))
Brad Bishop87b63c12016-03-18 14:47:51 -0400413 for x, y in parser.get_interfaces().iteritems():
414 m = self.find_method_in_interface(
415 method, obj, x, y.get('method'))
416 if m:
Ratan Guptaa6a8a4c2017-08-07 08:18:44 +0530417 self.service = bus
418 self.interface = x
Brad Bishop87b63c12016-03-18 14:47:51 -0400419 return m
420
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400421
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500422class PropertyHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400423 verbs = ['PUT', 'GET']
424 rules = '<path:path>/attr/<prop>'
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500425 content_type = 'application/json'
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400426
Brad Bishop87b63c12016-03-18 14:47:51 -0400427 def __init__(self, app, bus):
428 super(PropertyHandler, self).__init__(
Deepak Kodihalli83afbaf2017-04-10 06:37:19 -0500429 app, bus, self.verbs, self.rules, self.content_type)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400430
Brad Bishop87b63c12016-03-18 14:47:51 -0400431 def find(self, path, prop):
432 self.app.instance_handler.setup(path)
433 obj = self.app.instance_handler.do_get(path)
Brad Bishop56ad87f2017-02-21 23:33:29 -0500434 real_name = obmc.utils.misc.find_case_insensitive(
435 prop, obj.keys())
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400436
Brad Bishop56ad87f2017-02-21 23:33:29 -0500437 if not real_name:
438 if request.method == 'PUT':
439 abort(403, _4034_msg % ('property', 'created', prop))
440 else:
441 abort(404, _4034_msg % ('property', 'found', prop))
442 return real_name, {path: obj}
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500443
Brad Bishop87b63c12016-03-18 14:47:51 -0400444 def setup(self, path, prop):
Brad Bishop56ad87f2017-02-21 23:33:29 -0500445 name, obj = self.find(path, prop)
446 request.route_data['obj'] = obj
447 request.route_data['name'] = name
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500448
Brad Bishop87b63c12016-03-18 14:47:51 -0400449 def do_get(self, path, prop):
Brad Bishop56ad87f2017-02-21 23:33:29 -0500450 name = request.route_data['name']
451 return request.route_data['obj'][path][name]
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500452
Marri Devender Raobc0c6732017-11-20 00:15:47 -0600453 def do_put(self, path, prop, value=None, retry=True):
Brad Bishop87b63c12016-03-18 14:47:51 -0400454 if value is None:
455 value = request.parameter_list
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500456
Brad Bishop87b63c12016-03-18 14:47:51 -0400457 prop, iface, properties_iface = self.get_host_interface(
458 path, prop, request.route_data['map'][path])
459 try:
460 properties_iface.Set(iface, prop, value)
461 except ValueError, e:
462 abort(400, str(e))
463 except dbus.exceptions.DBusException, e:
Marri Devender Raobc0c6732017-11-20 00:15:47 -0600464 if e.get_dbus_name() == DBUS_INVALID_ARGS and retry == True:
Leonel Gonzalez0bdef952017-04-18 08:17:49 -0500465 bus_name = properties_iface.bus_name
466 expected_type = get_type_signature_by_introspection(self.bus,
467 bus_name,
468 path,
469 prop)
470 if not expected_type:
471 abort(403, "Failed to get expected type: %s" % str(e))
472 converted_value = None
473 try:
474 converted_value = convert_type(expected_type, value)
Marri Devender Raobc0c6732017-11-20 00:15:47 -0600475 self.do_put(path, prop, converted_value, False)
Leonel Gonzalez0bdef952017-04-18 08:17:49 -0500476 return
477 except Exception as ex:
478 abort(403, "Failed to convert %s to type %s" %
479 (value, expected_type))
Brad Bishop87b63c12016-03-18 14:47:51 -0400480 abort(403, str(e))
481 raise
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500482
Brad Bishop87b63c12016-03-18 14:47:51 -0400483 def get_host_interface(self, path, prop, bus_info):
484 for bus, interfaces in bus_info.iteritems():
485 obj = self.bus.get_object(bus, path, introspect=True)
486 properties_iface = dbus.Interface(
487 obj, dbus_interface=dbus.PROPERTIES_IFACE)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500488
Brad Bishop87b63c12016-03-18 14:47:51 -0400489 info = self.get_host_interface_on_bus(
490 path, prop, properties_iface, bus, interfaces)
491 if info is not None:
492 prop, iface = info
493 return prop, iface, properties_iface
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500494
Brad Bishop87b63c12016-03-18 14:47:51 -0400495 def get_host_interface_on_bus(self, path, prop, iface, bus, interfaces):
496 for i in interfaces:
497 properties = self.try_properties_interface(iface.GetAll, i)
Brad Bishop69cb6d12017-02-21 12:01:52 -0500498 if not properties:
Brad Bishop87b63c12016-03-18 14:47:51 -0400499 continue
Leonel Gonzalez409f6712017-05-24 09:51:55 -0500500 match = obmc.utils.misc.find_case_insensitive(
Brad Bishop8b0d3fa2016-11-28 15:41:47 -0500501 prop, properties.keys())
Leonel Gonzalez409f6712017-05-24 09:51:55 -0500502 if match is None:
Brad Bishop87b63c12016-03-18 14:47:51 -0400503 continue
Leonel Gonzalez409f6712017-05-24 09:51:55 -0500504 prop = match
Brad Bishop87b63c12016-03-18 14:47:51 -0400505 return prop, i
506
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500507
Brad Bishop2503bd62015-12-16 17:56:12 -0500508class SchemaHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400509 verbs = ['GET']
510 rules = '<path:path>/schema'
Brad Bishop2503bd62015-12-16 17:56:12 -0500511
Brad Bishop87b63c12016-03-18 14:47:51 -0400512 def __init__(self, app, bus):
513 super(SchemaHandler, self).__init__(
Brad Bishop529029b2017-07-10 16:46:01 -0400514 app, bus, self.verbs, self.rules)
Brad Bishop2503bd62015-12-16 17:56:12 -0500515
Brad Bishop87b63c12016-03-18 14:47:51 -0400516 def find(self, path):
517 return self.try_mapper_call(
518 self.mapper.get_object,
519 path=path)
Brad Bishop2503bd62015-12-16 17:56:12 -0500520
Brad Bishop87b63c12016-03-18 14:47:51 -0400521 def setup(self, path):
522 request.route_data['map'] = self.find(path)
Brad Bishop2503bd62015-12-16 17:56:12 -0500523
Brad Bishop87b63c12016-03-18 14:47:51 -0400524 def do_get(self, path):
525 schema = {}
526 for x in request.route_data['map'].iterkeys():
527 obj = self.bus.get_object(x, path, introspect=False)
528 iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
529 data = iface.Introspect()
530 parser = IntrospectionNodeParser(
531 ElementTree.fromstring(data))
532 for x, y in parser.get_interfaces().iteritems():
533 schema[x] = y
Brad Bishop2503bd62015-12-16 17:56:12 -0500534
Brad Bishop87b63c12016-03-18 14:47:51 -0400535 return schema
536
Brad Bishop2503bd62015-12-16 17:56:12 -0500537
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500538class InstanceHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400539 verbs = ['GET', 'PUT', 'DELETE']
540 rules = '<path:path>'
541 request_type = dict
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500542
Brad Bishop87b63c12016-03-18 14:47:51 -0400543 def __init__(self, app, bus):
544 super(InstanceHandler, self).__init__(
Brad Bishop529029b2017-07-10 16:46:01 -0400545 app, bus, self.verbs, self.rules)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500546
Brad Bishop87b63c12016-03-18 14:47:51 -0400547 def find(self, path, callback=None):
548 return {path: self.try_mapper_call(
549 self.mapper.get_object,
550 callback,
551 path=path)}
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500552
Brad Bishop87b63c12016-03-18 14:47:51 -0400553 def setup(self, path):
554 callback = None
555 if request.method == 'PUT':
556 def callback(e, **kw):
557 abort(403, _4034_msg % ('resource', 'created', path))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500558
Brad Bishop87b63c12016-03-18 14:47:51 -0400559 if request.route_data.get('map') is None:
560 request.route_data['map'] = self.find(path, callback)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500561
Brad Bishop87b63c12016-03-18 14:47:51 -0400562 def do_get(self, path):
Brad Bishop71527b42016-04-01 14:51:14 -0400563 return self.mapper.enumerate_object(
564 path,
565 mapper_data=request.route_data['map'])
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500566
Brad Bishop87b63c12016-03-18 14:47:51 -0400567 def do_put(self, path):
568 # make sure all properties exist in the request
569 obj = set(self.do_get(path).keys())
570 req = set(request.parameter_list.keys())
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500571
Brad Bishop87b63c12016-03-18 14:47:51 -0400572 diff = list(obj.difference(req))
573 if diff:
574 abort(403, _4034_msg % (
575 'resource', 'removed', '%s/attr/%s' % (path, diff[0])))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500576
Brad Bishop87b63c12016-03-18 14:47:51 -0400577 diff = list(req.difference(obj))
578 if diff:
579 abort(403, _4034_msg % (
580 'resource', 'created', '%s/attr/%s' % (path, diff[0])))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500581
Brad Bishop87b63c12016-03-18 14:47:51 -0400582 for p, v in request.parameter_list.iteritems():
583 self.app.property_handler.do_put(
584 path, p, v)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500585
Brad Bishop87b63c12016-03-18 14:47:51 -0400586 def do_delete(self, path):
587 for bus_info in request.route_data['map'][path].iteritems():
588 if self.bus_missing_delete(path, *bus_info):
589 abort(403, _4034_msg % ('resource', 'removed', path))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500590
Brad Bishop87b63c12016-03-18 14:47:51 -0400591 for bus in request.route_data['map'][path].iterkeys():
592 self.delete_on_bus(path, bus)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500593
Brad Bishop87b63c12016-03-18 14:47:51 -0400594 def bus_missing_delete(self, path, bus, interfaces):
595 return DELETE_IFACE not in interfaces
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500596
Brad Bishop87b63c12016-03-18 14:47:51 -0400597 def delete_on_bus(self, path, bus):
598 obj = self.bus.get_object(bus, path, introspect=False)
599 delete_iface = dbus.Interface(
600 obj, dbus_interface=DELETE_IFACE)
601 delete_iface.Delete()
602
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500603
Brad Bishop2f428582015-12-02 10:56:11 -0500604class SessionHandler(MethodHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400605 ''' Handles the /login and /logout routes, manages
606 server side session store and session cookies. '''
Brad Bishop2f428582015-12-02 10:56:11 -0500607
Brad Bishop87b63c12016-03-18 14:47:51 -0400608 rules = ['/login', '/logout']
609 login_str = "User '%s' logged %s"
610 bad_passwd_str = "Invalid username or password"
611 no_user_str = "No user logged in"
612 bad_json_str = "Expecting request format { 'data': " \
613 "[<username>, <password>] }, got '%s'"
614 _require_auth = None
615 MAX_SESSIONS = 16
Brad Bishop2f428582015-12-02 10:56:11 -0500616
Brad Bishop87b63c12016-03-18 14:47:51 -0400617 def __init__(self, app, bus):
618 super(SessionHandler, self).__init__(
619 app, bus)
620 self.hmac_key = os.urandom(128)
621 self.session_store = []
Brad Bishop2f428582015-12-02 10:56:11 -0500622
Brad Bishop87b63c12016-03-18 14:47:51 -0400623 @staticmethod
624 def authenticate(username, clear):
625 try:
626 encoded = spwd.getspnam(username)[1]
627 return encoded == crypt.crypt(clear, encoded)
628 except KeyError:
629 return False
Brad Bishop2f428582015-12-02 10:56:11 -0500630
Brad Bishop87b63c12016-03-18 14:47:51 -0400631 def invalidate_session(self, session):
632 try:
633 self.session_store.remove(session)
634 except ValueError:
635 pass
Brad Bishop2f428582015-12-02 10:56:11 -0500636
Brad Bishop87b63c12016-03-18 14:47:51 -0400637 def new_session(self):
638 sid = os.urandom(32)
639 if self.MAX_SESSIONS <= len(self.session_store):
640 self.session_store.pop()
641 self.session_store.insert(0, {'sid': sid})
Brad Bishop2f428582015-12-02 10:56:11 -0500642
Brad Bishop87b63c12016-03-18 14:47:51 -0400643 return self.session_store[0]
Brad Bishop2f428582015-12-02 10:56:11 -0500644
Brad Bishop87b63c12016-03-18 14:47:51 -0400645 def get_session(self, sid):
646 sids = [x['sid'] for x in self.session_store]
647 try:
648 return self.session_store[sids.index(sid)]
649 except ValueError:
650 return None
Brad Bishop2f428582015-12-02 10:56:11 -0500651
Brad Bishop87b63c12016-03-18 14:47:51 -0400652 def get_session_from_cookie(self):
653 return self.get_session(
654 request.get_cookie(
655 'sid', secret=self.hmac_key))
Brad Bishop2f428582015-12-02 10:56:11 -0500656
Brad Bishop87b63c12016-03-18 14:47:51 -0400657 def do_post(self, **kw):
658 if request.path == '/login':
659 return self.do_login(**kw)
660 else:
661 return self.do_logout(**kw)
Brad Bishop2f428582015-12-02 10:56:11 -0500662
Brad Bishop87b63c12016-03-18 14:47:51 -0400663 def do_logout(self, **kw):
664 session = self.get_session_from_cookie()
665 if session is not None:
666 user = session['user']
667 self.invalidate_session(session)
668 response.delete_cookie('sid')
669 return self.login_str % (user, 'out')
Brad Bishop2f428582015-12-02 10:56:11 -0500670
Brad Bishop87b63c12016-03-18 14:47:51 -0400671 return self.no_user_str
Brad Bishop2f428582015-12-02 10:56:11 -0500672
Brad Bishop87b63c12016-03-18 14:47:51 -0400673 def do_login(self, **kw):
674 session = self.get_session_from_cookie()
675 if session is not None:
676 return self.login_str % (session['user'], 'in')
Brad Bishop2f428582015-12-02 10:56:11 -0500677
Brad Bishop87b63c12016-03-18 14:47:51 -0400678 if len(request.parameter_list) != 2:
679 abort(400, self.bad_json_str % (request.json))
Brad Bishop2f428582015-12-02 10:56:11 -0500680
Brad Bishop87b63c12016-03-18 14:47:51 -0400681 if not self.authenticate(*request.parameter_list):
Brad Bishopdc3fbfa2016-09-08 09:51:38 -0400682 abort(401, self.bad_passwd_str)
Brad Bishop2f428582015-12-02 10:56:11 -0500683
Brad Bishop87b63c12016-03-18 14:47:51 -0400684 user = request.parameter_list[0]
685 session = self.new_session()
686 session['user'] = user
687 response.set_cookie(
688 'sid', session['sid'], secret=self.hmac_key,
689 secure=True,
690 httponly=True)
691 return self.login_str % (user, 'in')
Brad Bishop2f428582015-12-02 10:56:11 -0500692
Brad Bishop87b63c12016-03-18 14:47:51 -0400693 def find(self, **kw):
694 pass
Brad Bishop2f428582015-12-02 10:56:11 -0500695
Brad Bishop87b63c12016-03-18 14:47:51 -0400696 def setup(self, **kw):
697 pass
698
Brad Bishop2f428582015-12-02 10:56:11 -0500699
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500700class ImageUploadUtils:
701 ''' Provides common utils for image upload. '''
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500702
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500703 file_loc = '/tmp/images'
704 file_prefix = 'img'
705 file_suffix = ''
706
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500707 @classmethod
708 def do_upload(cls, filename=''):
709 if not os.path.exists(cls.file_loc):
Gunnar Millsfb515792017-11-09 15:52:17 -0600710 abort(500, "Error Directory not found")
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500711 if not filename:
712 handle, filename = tempfile.mkstemp(cls.file_suffix,
713 cls.file_prefix, cls.file_loc)
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500714 else:
715 filename = os.path.join(cls.file_loc, filename)
Gunnar Millsb66b18c2017-08-21 16:17:21 -0500716 handle = os.open(filename, os.O_WRONLY | os.O_CREAT)
Leonel Gonzalez0b62edf2017-06-08 15:10:03 -0500717 try:
718 file_contents = request.body.read()
719 request.body.close()
Gunnar Millsb66b18c2017-08-21 16:17:21 -0500720 os.write(handle, file_contents)
Leonel Gonzalez0b62edf2017-06-08 15:10:03 -0500721 except (IOError, ValueError), e:
722 abort(400, str(e))
723 except:
724 abort(400, "Unexpected Error")
Gunnar Millsb66b18c2017-08-21 16:17:21 -0500725 finally:
726 os.close(handle)
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500727
728
729class ImagePostHandler(RouteHandler):
730 ''' Handles the /upload/image route. '''
731
732 verbs = ['POST']
733 rules = ['/upload/image']
734 content_type = 'application/octet-stream'
735
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500736 def __init__(self, app, bus):
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500737 super(ImagePostHandler, self).__init__(
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500738 app, bus, self.verbs, self.rules, self.content_type)
739
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500740 def do_post(self, filename=''):
741 ImageUploadUtils.do_upload()
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500742
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500743 def find(self, **kw):
744 pass
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500745
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500746 def setup(self, **kw):
747 pass
748
749
Deepak Kodihalli639b5022017-10-13 06:40:26 -0500750class EventNotifier:
751 keyNames = {}
752 keyNames['event'] = 'event'
753 keyNames['path'] = 'path'
754 keyNames['intfMap'] = 'interfaces'
755 keyNames['propMap'] = 'properties'
756 keyNames['intf'] = 'interface'
757
758 def __init__(self, wsock, filters):
759 self.wsock = wsock
760 self.paths = filters.get("paths", [])
761 self.interfaces = filters.get("interfaces", [])
762 if not self.paths:
763 self.paths.append(None)
764 bus = dbus.SystemBus()
765 # Add a signal receiver for every path the client is interested in
766 for path in self.paths:
767 bus.add_signal_receiver(
768 self.interfaces_added_handler,
769 dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager',
770 signal_name='InterfacesAdded',
771 path=path)
772 bus.add_signal_receiver(
773 self.properties_changed_handler,
774 dbus_interface=dbus.PROPERTIES_IFACE,
775 signal_name='PropertiesChanged',
776 path=path,
777 path_keyword='path')
778 loop = gobject.MainLoop()
779 # gobject's mainloop.run() will block the entire process, so the gevent
780 # scheduler and hence greenlets won't execute. The while-loop below
781 # works around this limitation by using gevent's sleep, instead of
782 # calling loop.run()
783 gcontext = loop.get_context()
784 while loop is not None:
785 try:
786 if gcontext.pending():
787 gcontext.iteration()
788 else:
789 # gevent.sleep puts only the current greenlet to sleep,
790 # not the entire process.
791 gevent.sleep(5)
792 except WebSocketError:
793 break
794
795 def interfaces_added_handler(self, path, iprops, **kw):
796 ''' If the client is interested in these changes, respond to the
797 client. This handles d-bus interface additions.'''
798 if (not self.interfaces) or \
799 (not set(iprops).isdisjoint(self.interfaces)):
800 response = {}
801 response[self.keyNames['event']] = "InterfacesAdded"
802 response[self.keyNames['path']] = path
803 response[self.keyNames['intfMap']] = iprops
804 try:
805 self.wsock.send(json.dumps(response))
806 except WebSocketError:
807 return
808
809 def properties_changed_handler(self, interface, new, old, **kw):
810 ''' If the client is interested in these changes, respond to the
811 client. This handles d-bus property changes. '''
812 if (not self.interfaces) or (interface in self.interfaces):
813 path = str(kw['path'])
814 response = {}
815 response[self.keyNames['event']] = "PropertiesChanged"
816 response[self.keyNames['path']] = path
817 response[self.keyNames['intf']] = interface
818 response[self.keyNames['propMap']] = new
819 try:
820 self.wsock.send(json.dumps(response))
821 except WebSocketError:
822 return
823
824
Deepak Kodihallib209dd12017-10-11 01:19:17 -0500825class EventHandler(RouteHandler):
826 ''' Handles the /subscribe route, for clients to be able
827 to subscribe to BMC events. '''
828
829 verbs = ['GET']
830 rules = ['/subscribe']
831
832 def __init__(self, app, bus):
833 super(EventHandler, self).__init__(
834 app, bus, self.verbs, self.rules)
835
836 def find(self, **kw):
837 pass
838
839 def setup(self, **kw):
840 pass
841
842 def do_get(self):
843 wsock = request.environ.get('wsgi.websocket')
844 if not wsock:
845 abort(400, 'Expected WebSocket request.')
Deepak Kodihalli639b5022017-10-13 06:40:26 -0500846 filters = wsock.receive()
847 filters = json.loads(filters)
848 notifier = EventNotifier(wsock, filters)
Deepak Kodihallib209dd12017-10-11 01:19:17 -0500849
850
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -0500851class ImagePutHandler(RouteHandler):
852 ''' Handles the /upload/image/<filename> route. '''
853
854 verbs = ['PUT']
855 rules = ['/upload/image/<filename>']
856 content_type = 'application/octet-stream'
857
858 def __init__(self, app, bus):
859 super(ImagePutHandler, self).__init__(
860 app, bus, self.verbs, self.rules, self.content_type)
861
862 def do_put(self, filename=''):
863 ImageUploadUtils.do_upload(filename)
Deepak Kodihalli1af301a2017-04-11 07:29:01 -0500864
865 def find(self, **kw):
866 pass
867
868 def setup(self, **kw):
869 pass
870
871
Jayanth Othayoth9bc94992017-06-29 06:30:40 -0500872class DownloadDumpHandler(RouteHandler):
873 ''' Handles the /download/dump route. '''
874
875 verbs = 'GET'
876 rules = ['/download/dump/<dumpid>']
877 content_type = 'application/octet-stream'
Jayanth Othayoth18c3a242017-08-02 08:16:11 -0500878 dump_loc = '/var/lib/phosphor-debug-collector/dumps'
Brad Bishop944cd042017-07-10 16:42:41 -0400879 suppress_json_resp = True
Jayanth Othayoth9bc94992017-06-29 06:30:40 -0500880
881 def __init__(self, app, bus):
882 super(DownloadDumpHandler, self).__init__(
883 app, bus, self.verbs, self.rules, self.content_type)
884
885 def do_get(self, dumpid):
886 return self.do_download(dumpid)
887
888 def find(self, **kw):
889 pass
890
891 def setup(self, **kw):
892 pass
893
894 def do_download(self, dumpid):
895 dump_loc = os.path.join(self.dump_loc, dumpid)
896 if not os.path.exists(dump_loc):
897 abort(404, "Path not found")
898
899 files = os.listdir(dump_loc)
900 num_files = len(files)
901 if num_files == 0:
902 abort(404, "Dump not found")
903
904 return static_file(os.path.basename(files[0]), root=dump_loc,
905 download=True, mimetype=self.content_type)
906
907
Brad Bishop2f428582015-12-02 10:56:11 -0500908class AuthorizationPlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -0400909 ''' Invokes an optional list of authorization callbacks. '''
Brad Bishop2f428582015-12-02 10:56:11 -0500910
Brad Bishop87b63c12016-03-18 14:47:51 -0400911 name = 'authorization'
912 api = 2
Brad Bishop2f428582015-12-02 10:56:11 -0500913
Brad Bishop87b63c12016-03-18 14:47:51 -0400914 class Compose:
915 def __init__(self, validators, callback, session_mgr):
916 self.validators = validators
917 self.callback = callback
918 self.session_mgr = session_mgr
Brad Bishop2f428582015-12-02 10:56:11 -0500919
Brad Bishop87b63c12016-03-18 14:47:51 -0400920 def __call__(self, *a, **kw):
921 sid = request.get_cookie('sid', secret=self.session_mgr.hmac_key)
922 session = self.session_mgr.get_session(sid)
Brad Bishopd4c1c552017-02-21 00:07:28 -0500923 if request.method != 'OPTIONS':
924 for x in self.validators:
925 x(session, *a, **kw)
Brad Bishop2f428582015-12-02 10:56:11 -0500926
Brad Bishop87b63c12016-03-18 14:47:51 -0400927 return self.callback(*a, **kw)
Brad Bishop2f428582015-12-02 10:56:11 -0500928
Brad Bishop87b63c12016-03-18 14:47:51 -0400929 def apply(self, callback, route):
930 undecorated = route.get_undecorated_callback()
931 if not isinstance(undecorated, RouteHandler):
932 return callback
Brad Bishop2f428582015-12-02 10:56:11 -0500933
Brad Bishop87b63c12016-03-18 14:47:51 -0400934 auth_types = getattr(
935 undecorated, '_require_auth', None)
936 if not auth_types:
937 return callback
Brad Bishop2f428582015-12-02 10:56:11 -0500938
Brad Bishop87b63c12016-03-18 14:47:51 -0400939 return self.Compose(
940 auth_types, callback, undecorated.app.session_handler)
941
Brad Bishop2f428582015-12-02 10:56:11 -0500942
Brad Bishopd0c404a2017-02-21 09:23:25 -0500943class CorsPlugin(object):
944 ''' Add CORS headers. '''
945
946 name = 'cors'
947 api = 2
948
949 @staticmethod
950 def process_origin():
951 origin = request.headers.get('Origin')
952 if origin:
953 response.add_header('Access-Control-Allow-Origin', origin)
954 response.add_header(
955 'Access-Control-Allow-Credentials', 'true')
956
957 @staticmethod
958 def process_method_and_headers(verbs):
959 method = request.headers.get('Access-Control-Request-Method')
960 headers = request.headers.get('Access-Control-Request-Headers')
961 if headers:
962 headers = [x.lower() for x in headers.split(',')]
963
964 if method in verbs \
965 and headers == ['content-type']:
966 response.add_header('Access-Control-Allow-Methods', method)
967 response.add_header(
968 'Access-Control-Allow-Headers', 'Content-Type')
969
970 def __init__(self, app):
971 app.install_error_callback(self.error_callback)
972
973 def apply(self, callback, route):
974 undecorated = route.get_undecorated_callback()
975 if not isinstance(undecorated, RouteHandler):
976 return callback
977
978 if not getattr(undecorated, '_enable_cors', None):
979 return callback
980
981 def wrap(*a, **kw):
982 self.process_origin()
983 self.process_method_and_headers(undecorated._verbs)
984 return callback(*a, **kw)
985
986 return wrap
987
988 def error_callback(self, **kw):
989 self.process_origin()
990
991
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500992class JsonApiRequestPlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -0400993 ''' Ensures request content satisfies the OpenBMC json api format. '''
994 name = 'json_api_request'
995 api = 2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500996
Brad Bishop87b63c12016-03-18 14:47:51 -0400997 error_str = "Expecting request format { 'data': <value> }, got '%s'"
998 type_error_str = "Unsupported Content-Type: '%s'"
999 json_type = "application/json"
1000 request_methods = ['PUT', 'POST', 'PATCH']
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001001
Brad Bishop87b63c12016-03-18 14:47:51 -04001002 @staticmethod
1003 def content_expected():
1004 return request.method in JsonApiRequestPlugin.request_methods
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001005
Brad Bishop87b63c12016-03-18 14:47:51 -04001006 def validate_request(self):
1007 if request.content_length > 0 and \
1008 request.content_type != self.json_type:
1009 abort(415, self.type_error_str % request.content_type)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001010
Brad Bishop87b63c12016-03-18 14:47:51 -04001011 try:
1012 request.parameter_list = request.json.get('data')
1013 except ValueError, e:
1014 abort(400, str(e))
1015 except (AttributeError, KeyError, TypeError):
1016 abort(400, self.error_str % request.json)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001017
Brad Bishop87b63c12016-03-18 14:47:51 -04001018 def apply(self, callback, route):
Deepak Kodihallifb6cd482017-04-10 07:27:09 -05001019 content_type = getattr(
1020 route.get_undecorated_callback(), '_content_type', None)
1021 if self.json_type != content_type:
1022 return callback
1023
Brad Bishop87b63c12016-03-18 14:47:51 -04001024 verbs = getattr(
1025 route.get_undecorated_callback(), '_verbs', None)
1026 if verbs is None:
1027 return callback
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001028
Brad Bishop87b63c12016-03-18 14:47:51 -04001029 if not set(self.request_methods).intersection(verbs):
1030 return callback
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001031
Brad Bishop87b63c12016-03-18 14:47:51 -04001032 def wrap(*a, **kw):
1033 if self.content_expected():
1034 self.validate_request()
1035 return callback(*a, **kw)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001036
Brad Bishop87b63c12016-03-18 14:47:51 -04001037 return wrap
1038
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001039
1040class JsonApiRequestTypePlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -04001041 ''' Ensures request content type satisfies the OpenBMC json api format. '''
1042 name = 'json_api_method_request'
1043 api = 2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001044
Brad Bishop87b63c12016-03-18 14:47:51 -04001045 error_str = "Expecting request format { 'data': %s }, got '%s'"
Deepak Kodihallifb6cd482017-04-10 07:27:09 -05001046 json_type = "application/json"
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001047
Brad Bishop87b63c12016-03-18 14:47:51 -04001048 def apply(self, callback, route):
Deepak Kodihallifb6cd482017-04-10 07:27:09 -05001049 content_type = getattr(
1050 route.get_undecorated_callback(), '_content_type', None)
1051 if self.json_type != content_type:
1052 return callback
1053
Brad Bishop87b63c12016-03-18 14:47:51 -04001054 request_type = getattr(
1055 route.get_undecorated_callback(), 'request_type', None)
1056 if request_type is None:
1057 return callback
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001058
Brad Bishop87b63c12016-03-18 14:47:51 -04001059 def validate_request():
1060 if not isinstance(request.parameter_list, request_type):
1061 abort(400, self.error_str % (str(request_type), request.json))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001062
Brad Bishop87b63c12016-03-18 14:47:51 -04001063 def wrap(*a, **kw):
1064 if JsonApiRequestPlugin.content_expected():
1065 validate_request()
1066 return callback(*a, **kw)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001067
Brad Bishop87b63c12016-03-18 14:47:51 -04001068 return wrap
1069
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001070
Brad Bishop080a48e2017-02-21 22:34:43 -05001071class JsonErrorsPlugin(JSONPlugin):
1072 ''' Extend the Bottle JSONPlugin such that it also encodes error
1073 responses. '''
1074
1075 def __init__(self, app, **kw):
1076 super(JsonErrorsPlugin, self).__init__(**kw)
1077 self.json_opts = {
1078 x: y for x, y in kw.iteritems()
1079 if x in ['indent', 'sort_keys']}
1080 app.install_error_callback(self.error_callback)
1081
1082 def error_callback(self, response_object, response_body, **kw):
1083 response_body['body'] = json.dumps(response_object, **self.json_opts)
1084 response.content_type = 'application/json'
1085
1086
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001087class JsonApiResponsePlugin(object):
Brad Bishop080a48e2017-02-21 22:34:43 -05001088 ''' Emits responses in the OpenBMC json api format. '''
Brad Bishop87b63c12016-03-18 14:47:51 -04001089 name = 'json_api_response'
1090 api = 2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001091
Brad Bishopd4c1c552017-02-21 00:07:28 -05001092 @staticmethod
1093 def has_body():
1094 return request.method not in ['OPTIONS']
1095
Brad Bishop080a48e2017-02-21 22:34:43 -05001096 def __init__(self, app):
1097 app.install_error_callback(self.error_callback)
1098
Brad Bishop87b63c12016-03-18 14:47:51 -04001099 def apply(self, callback, route):
Brad Bishop944cd042017-07-10 16:42:41 -04001100 skip = getattr(
1101 route.get_undecorated_callback(), 'suppress_json_resp', None)
1102 if skip:
Jayanth Othayoth1444fd82017-06-29 05:45:07 -05001103 return callback
1104
Brad Bishop87b63c12016-03-18 14:47:51 -04001105 def wrap(*a, **kw):
Brad Bishopd4c1c552017-02-21 00:07:28 -05001106 data = callback(*a, **kw)
1107 if self.has_body():
1108 resp = {'data': data}
1109 resp['status'] = 'ok'
1110 resp['message'] = response.status_line
1111 return resp
Brad Bishop87b63c12016-03-18 14:47:51 -04001112 return wrap
1113
Brad Bishop080a48e2017-02-21 22:34:43 -05001114 def error_callback(self, error, response_object, **kw):
Brad Bishop87b63c12016-03-18 14:47:51 -04001115 response_object['message'] = error.status_line
Brad Bishop9c2531e2017-03-07 10:22:40 -05001116 response_object['status'] = 'error'
Brad Bishop080a48e2017-02-21 22:34:43 -05001117 response_object.setdefault('data', {})['description'] = str(error.body)
Brad Bishop87b63c12016-03-18 14:47:51 -04001118 if error.status_code == 500:
1119 response_object['data']['exception'] = repr(error.exception)
1120 response_object['data']['traceback'] = error.traceback.splitlines()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001121
Brad Bishop87b63c12016-03-18 14:47:51 -04001122
Brad Bishop080a48e2017-02-21 22:34:43 -05001123class JsonpPlugin(object):
Brad Bishop80fe37a2016-03-29 10:54:54 -04001124 ''' Json javascript wrapper. '''
1125 name = 'jsonp'
1126 api = 2
1127
Brad Bishop080a48e2017-02-21 22:34:43 -05001128 def __init__(self, app, **kw):
1129 app.install_error_callback(self.error_callback)
Brad Bishop80fe37a2016-03-29 10:54:54 -04001130
1131 @staticmethod
1132 def to_jsonp(json):
1133 jwrapper = request.query.callback or None
1134 if(jwrapper):
1135 response.set_header('Content-Type', 'application/javascript')
1136 json = jwrapper + '(' + json + ');'
1137 return json
1138
1139 def apply(self, callback, route):
1140 def wrap(*a, **kw):
1141 return self.to_jsonp(callback(*a, **kw))
1142 return wrap
1143
Brad Bishop080a48e2017-02-21 22:34:43 -05001144 def error_callback(self, response_body, **kw):
1145 response_body['body'] = self.to_jsonp(response_body['body'])
Brad Bishop80fe37a2016-03-29 10:54:54 -04001146
1147
Deepak Kodihalli461367a2017-04-10 07:11:38 -05001148class ContentCheckerPlugin(object):
1149 ''' Ensures that a route is associated with the expected content-type
1150 header. '''
1151 name = 'content_checker'
1152 api = 2
1153
1154 class Checker:
1155 def __init__(self, type, callback):
1156 self.expected_type = type
1157 self.callback = callback
1158 self.error_str = "Expecting content type '%s', got '%s'"
1159
1160 def __call__(self, *a, **kw):
Deepak Kodihallidb1a21e2017-04-27 06:30:11 -05001161 if request.method in ['PUT', 'POST', 'PATCH'] and \
1162 self.expected_type and \
Deepak Kodihalli461367a2017-04-10 07:11:38 -05001163 self.expected_type != request.content_type:
1164 abort(415, self.error_str % (self.expected_type,
1165 request.content_type))
1166
1167 return self.callback(*a, **kw)
1168
1169 def apply(self, callback, route):
1170 content_type = getattr(
1171 route.get_undecorated_callback(), '_content_type', None)
1172
1173 return self.Checker(content_type, callback)
1174
1175
Brad Bishop2c6fc762016-08-29 15:53:25 -04001176class App(Bottle):
Deepak Kodihalli0fe213f2017-10-11 00:08:48 -05001177 def __init__(self, **kw):
Brad Bishop2c6fc762016-08-29 15:53:25 -04001178 super(App, self).__init__(autojson=False)
Deepak Kodihallib209dd12017-10-11 01:19:17 -05001179
1180 self.have_wsock = kw.get('have_wsock', False)
1181
Brad Bishop2ddfa002016-08-29 15:11:55 -04001182 self.bus = dbus.SystemBus()
1183 self.mapper = obmc.mapper.Mapper(self.bus)
Brad Bishop080a48e2017-02-21 22:34:43 -05001184 self.error_callbacks = []
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001185
Brad Bishop87b63c12016-03-18 14:47:51 -04001186 self.install_hooks()
1187 self.install_plugins()
1188 self.create_handlers()
1189 self.install_handlers()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001190
Brad Bishop87b63c12016-03-18 14:47:51 -04001191 def install_plugins(self):
1192 # install json api plugins
1193 json_kw = {'indent': 2, 'sort_keys': True}
Brad Bishop87b63c12016-03-18 14:47:51 -04001194 self.install(AuthorizationPlugin())
Brad Bishopd0c404a2017-02-21 09:23:25 -05001195 self.install(CorsPlugin(self))
Deepak Kodihalli461367a2017-04-10 07:11:38 -05001196 self.install(ContentCheckerPlugin())
Brad Bishop080a48e2017-02-21 22:34:43 -05001197 self.install(JsonpPlugin(self, **json_kw))
1198 self.install(JsonErrorsPlugin(self, **json_kw))
1199 self.install(JsonApiResponsePlugin(self))
Brad Bishop87b63c12016-03-18 14:47:51 -04001200 self.install(JsonApiRequestPlugin())
1201 self.install(JsonApiRequestTypePlugin())
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001202
Brad Bishop87b63c12016-03-18 14:47:51 -04001203 def install_hooks(self):
Brad Bishop080a48e2017-02-21 22:34:43 -05001204 self.error_handler_type = type(self.default_error_handler)
1205 self.original_error_handler = self.default_error_handler
1206 self.default_error_handler = self.error_handler_type(
1207 self.custom_error_handler, self, Bottle)
1208
Brad Bishop87b63c12016-03-18 14:47:51 -04001209 self.real_router_match = self.router.match
1210 self.router.match = self.custom_router_match
1211 self.add_hook('before_request', self.strip_extra_slashes)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001212
Brad Bishop87b63c12016-03-18 14:47:51 -04001213 def create_handlers(self):
1214 # create route handlers
1215 self.session_handler = SessionHandler(self, self.bus)
1216 self.directory_handler = DirectoryHandler(self, self.bus)
1217 self.list_names_handler = ListNamesHandler(self, self.bus)
1218 self.list_handler = ListHandler(self, self.bus)
1219 self.method_handler = MethodHandler(self, self.bus)
1220 self.property_handler = PropertyHandler(self, self.bus)
1221 self.schema_handler = SchemaHandler(self, self.bus)
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -05001222 self.image_upload_post_handler = ImagePostHandler(self, self.bus)
1223 self.image_upload_put_handler = ImagePutHandler(self, self.bus)
Jayanth Othayoth9bc94992017-06-29 06:30:40 -05001224 self.download_dump_get_handler = DownloadDumpHandler(self, self.bus)
Deepak Kodihallib209dd12017-10-11 01:19:17 -05001225 if self.have_wsock:
1226 self.event_handler = EventHandler(self, self.bus)
Brad Bishop87b63c12016-03-18 14:47:51 -04001227 self.instance_handler = InstanceHandler(self, self.bus)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001228
Brad Bishop87b63c12016-03-18 14:47:51 -04001229 def install_handlers(self):
1230 self.session_handler.install()
1231 self.directory_handler.install()
1232 self.list_names_handler.install()
1233 self.list_handler.install()
1234 self.method_handler.install()
1235 self.property_handler.install()
1236 self.schema_handler.install()
Deepak Kodihalli7ec0a4f2017-04-11 07:50:27 -05001237 self.image_upload_post_handler.install()
1238 self.image_upload_put_handler.install()
Jayanth Othayoth9bc94992017-06-29 06:30:40 -05001239 self.download_dump_get_handler.install()
Deepak Kodihallib209dd12017-10-11 01:19:17 -05001240 if self.have_wsock:
1241 self.event_handler.install()
Brad Bishop87b63c12016-03-18 14:47:51 -04001242 # this has to come last, since it matches everything
1243 self.instance_handler.install()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001244
Brad Bishop080a48e2017-02-21 22:34:43 -05001245 def install_error_callback(self, callback):
1246 self.error_callbacks.insert(0, callback)
1247
Brad Bishop87b63c12016-03-18 14:47:51 -04001248 def custom_router_match(self, environ):
1249 ''' The built-in Bottle algorithm for figuring out if a 404 or 405 is
1250 needed doesn't work for us since the instance rules match
1251 everything. This monkey-patch lets the route handler figure
1252 out which response is needed. This could be accomplished
1253 with a hook but that would require calling the router match
1254 function twice.
1255 '''
1256 route, args = self.real_router_match(environ)
1257 if isinstance(route.callback, RouteHandler):
1258 route.callback._setup(**args)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001259
Brad Bishop87b63c12016-03-18 14:47:51 -04001260 return route, args
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05001261
Brad Bishop080a48e2017-02-21 22:34:43 -05001262 def custom_error_handler(self, res, error):
Gunnar Millsf01d0ba2017-10-25 20:37:24 -05001263 ''' Allow plugins to modify error responses too via this custom
Brad Bishop080a48e2017-02-21 22:34:43 -05001264 error handler. '''
1265
1266 response_object = {}
1267 response_body = {}
1268 for x in self.error_callbacks:
1269 x(error=error,
1270 response_object=response_object,
1271 response_body=response_body)
1272
1273 return response_body.get('body', "")
1274
Brad Bishop87b63c12016-03-18 14:47:51 -04001275 @staticmethod
1276 def strip_extra_slashes():
1277 path = request.environ['PATH_INFO']
1278 trailing = ("", "/")[path[-1] == '/']
1279 parts = filter(bool, path.split('/'))
1280 request.environ['PATH_INFO'] = '/' + '/'.join(parts) + trailing