blob: 7c4ddfdffe965cc4ed9f1e02babbf6a561924926 [file] [log] [blame]
Brad Bishopaa65f6e2015-10-27 16:28:51 -04001#!/usr/bin/env python
2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05003import os
4import sys
Brad Bishopaa65f6e2015-10-27 16:28:51 -04005import dbus
Brad Bishopb1cbdaf2015-11-13 21:28:16 -05006import dbus.exceptions
7import json
8import logging
9from xml.etree import ElementTree
10from rocket import Rocket
11from bottle import Bottle, abort, request, response, JSONPlugin, HTTPError
Brad Bishopb103d2d2016-03-04 16:19:14 -050012import obmc.utils.misc
13import obmc.utils.pathtree
14from obmc.dbuslib.introspection import IntrospectionNodeParser
15import obmc.mapper
Brad Bishop2f428582015-12-02 10:56:11 -050016import spwd
17import grp
18import crypt
Brad Bishopaa65f6e2015-10-27 16:28:51 -040019
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050020DBUS_UNKNOWN_INTERFACE = 'org.freedesktop.UnknownInterface'
21DBUS_UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod'
22DBUS_INVALID_ARGS = 'org.freedesktop.DBus.Error.InvalidArgs'
Brad Bishopd4578922015-12-02 11:10:36 -050023DBUS_TYPE_ERROR = 'org.freedesktop.DBus.Python.TypeError'
Brad Bishopaac521c2015-11-25 09:16:35 -050024DELETE_IFACE = 'org.openbmc.Object.Delete'
Brad Bishop9ee57c42015-11-03 14:59:29 -050025
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050026_4034_msg = "The specified %s cannot be %s: '%s'"
Brad Bishopaa65f6e2015-10-27 16:28:51 -040027
Brad Bishop87b63c12016-03-18 14:47:51 -040028
Brad Bishop2f428582015-12-02 10:56:11 -050029def valid_user(session, *a, **kw):
Brad Bishop87b63c12016-03-18 14:47:51 -040030 ''' Authorization plugin callback that checks
31 that the user is logged in. '''
32 if session is None:
33 abort(403, 'Login required')
34
Brad Bishop2f428582015-12-02 10:56:11 -050035
36class UserInGroup:
Brad Bishop87b63c12016-03-18 14:47:51 -040037 ''' Authorization plugin callback that checks that the user is logged in
38 and a member of a group. '''
39 def __init__(self, group):
40 self.group = group
Brad Bishop2f428582015-12-02 10:56:11 -050041
Brad Bishop87b63c12016-03-18 14:47:51 -040042 def __call__(self, session, *a, **kw):
43 valid_user(session, *a, **kw)
44 res = False
Brad Bishop2f428582015-12-02 10:56:11 -050045
Brad Bishop87b63c12016-03-18 14:47:51 -040046 try:
47 res = session['user'] in grp.getgrnam(self.group)[3]
48 except KeyError:
49 pass
Brad Bishop2f428582015-12-02 10:56:11 -050050
Brad Bishop87b63c12016-03-18 14:47:51 -040051 if not res:
52 abort(403, 'Insufficient access')
53
Brad Bishop2f428582015-12-02 10:56:11 -050054
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050055def find_case_insensitive(value, lst):
Brad Bishop87b63c12016-03-18 14:47:51 -040056 return next((x for x in lst if x.lower() == value.lower()), None)
57
Brad Bishopaa65f6e2015-10-27 16:28:51 -040058
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050059def makelist(data):
Brad Bishop87b63c12016-03-18 14:47:51 -040060 if isinstance(data, list):
61 return data
62 elif data:
63 return [data]
64 else:
65 return []
66
Brad Bishopaa65f6e2015-10-27 16:28:51 -040067
Brad Bishopb1cbdaf2015-11-13 21:28:16 -050068class RouteHandler(object):
Brad Bishop87b63c12016-03-18 14:47:51 -040069 _require_auth = makelist(valid_user)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040070
Brad Bishop87b63c12016-03-18 14:47:51 -040071 def __init__(self, app, bus, verbs, rules):
72 self.app = app
73 self.bus = bus
Brad Bishopb103d2d2016-03-04 16:19:14 -050074 self.mapper = obmc.mapper.Mapper(bus)
Brad Bishop87b63c12016-03-18 14:47:51 -040075 self._verbs = makelist(verbs)
76 self._rules = rules
Brad Bishopaa65f6e2015-10-27 16:28:51 -040077
Brad Bishop87b63c12016-03-18 14:47:51 -040078 def _setup(self, **kw):
79 request.route_data = {}
80 if request.method in self._verbs:
81 return self.setup(**kw)
82 else:
83 self.find(**kw)
84 raise HTTPError(
85 405, "Method not allowed.", Allow=','.join(self._verbs))
Brad Bishopaa65f6e2015-10-27 16:28:51 -040086
Brad Bishop87b63c12016-03-18 14:47:51 -040087 def __call__(self, **kw):
88 return getattr(self, 'do_' + request.method.lower())(**kw)
Brad Bishopaa65f6e2015-10-27 16:28:51 -040089
Brad Bishop87b63c12016-03-18 14:47:51 -040090 def install(self):
91 self.app.route(
92 self._rules, callback=self,
93 method=['GET', 'PUT', 'PATCH', 'POST', 'DELETE'])
Brad Bishopaa65f6e2015-10-27 16:28:51 -040094
Brad Bishop87b63c12016-03-18 14:47:51 -040095 @staticmethod
96 def try_mapper_call(f, callback=None, **kw):
97 try:
98 return f(**kw)
99 except dbus.exceptions.DBusException, e:
Brad Bishopb103d2d2016-03-04 16:19:14 -0500100 if e.get_dbus_name() != obmc.mapper.MAPPER_NOT_FOUND:
Brad Bishop87b63c12016-03-18 14:47:51 -0400101 raise
102 if callback is None:
103 def callback(e, **kw):
104 abort(404, str(e))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400105
Brad Bishop87b63c12016-03-18 14:47:51 -0400106 callback(e, **kw)
107
108 @staticmethod
109 def try_properties_interface(f, *a):
110 try:
111 return f(*a)
112 except dbus.exceptions.DBusException, e:
113 if DBUS_UNKNOWN_INTERFACE in e.get_dbus_message():
114 # interface doesn't have any properties
115 return None
116 if DBUS_UNKNOWN_METHOD == e.get_dbus_name():
117 # properties interface not implemented at all
118 return None
119 raise
120
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400121
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500122class DirectoryHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400123 verbs = 'GET'
124 rules = '<path:path>/'
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400125
Brad Bishop87b63c12016-03-18 14:47:51 -0400126 def __init__(self, app, bus):
127 super(DirectoryHandler, self).__init__(
128 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400129
Brad Bishop87b63c12016-03-18 14:47:51 -0400130 def find(self, path='/'):
131 return self.try_mapper_call(
132 self.mapper.get_subtree_paths, path=path, depth=1)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400133
Brad Bishop87b63c12016-03-18 14:47:51 -0400134 def setup(self, path='/'):
135 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400136
Brad Bishop87b63c12016-03-18 14:47:51 -0400137 def do_get(self, path='/'):
138 return request.route_data['map']
139
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400140
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500141class ListNamesHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400142 verbs = 'GET'
143 rules = ['/list', '<path:path>/list']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400144
Brad Bishop87b63c12016-03-18 14:47:51 -0400145 def __init__(self, app, bus):
146 super(ListNamesHandler, self).__init__(
147 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400148
Brad Bishop87b63c12016-03-18 14:47:51 -0400149 def find(self, path='/'):
150 return self.try_mapper_call(
151 self.mapper.get_subtree, path=path).keys()
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400152
Brad Bishop87b63c12016-03-18 14:47:51 -0400153 def setup(self, path='/'):
154 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400155
Brad Bishop87b63c12016-03-18 14:47:51 -0400156 def do_get(self, path='/'):
157 return request.route_data['map']
158
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400159
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500160class ListHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400161 verbs = 'GET'
162 rules = ['/enumerate', '<path:path>/enumerate']
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400163
Brad Bishop87b63c12016-03-18 14:47:51 -0400164 def __init__(self, app, bus):
165 super(ListHandler, self).__init__(
166 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400167
Brad Bishop87b63c12016-03-18 14:47:51 -0400168 def find(self, path='/'):
169 return self.try_mapper_call(
170 self.mapper.get_subtree, path=path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400171
Brad Bishop87b63c12016-03-18 14:47:51 -0400172 def setup(self, path='/'):
173 request.route_data['map'] = self.find(path)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400174
Brad Bishop87b63c12016-03-18 14:47:51 -0400175 def do_get(self, path='/'):
176 objs = {}
177 mapper_data = request.route_data['map']
Brad Bishopb103d2d2016-03-04 16:19:14 -0500178 tree = obmc.utils.pathreee.PathTree()
Brad Bishop87b63c12016-03-18 14:47:51 -0400179 for x, y in mapper_data.iteritems():
180 tree[x] = y
Brad Bishop936f5fe2015-11-03 15:10:11 -0500181
Brad Bishop87b63c12016-03-18 14:47:51 -0400182 try:
183 # Check to see if the root path implements
184 # enumerate in addition to any sub tree
185 # objects.
186 root = self.try_mapper_call(
187 self.mapper.get_object, path=path)
188 mapper_data[path] = root
189 except:
190 pass
Brad Bishop936f5fe2015-11-03 15:10:11 -0500191
Brad Bishop87b63c12016-03-18 14:47:51 -0400192 have_enumerate = [
193 (x[0], self.enumerate_capable(*x))
194 for x in mapper_data.iteritems() if self.enumerate_capable(*x)]
Brad Bishop936f5fe2015-11-03 15:10:11 -0500195
Brad Bishop87b63c12016-03-18 14:47:51 -0400196 for x, y in have_enumerate:
197 objs.update(self.call_enumerate(x, y))
198 tmp = tree[x]
199 # remove the subtree
200 del tree[x]
201 # add the new leaf back since enumerate results don't
202 # include the object enumerate is being invoked on
203 tree[x] = tmp
Brad Bishop936f5fe2015-11-03 15:10:11 -0500204
Brad Bishop87b63c12016-03-18 14:47:51 -0400205 # make dbus calls for any remaining objects
206 for x, y in tree.dataitems():
207 objs[x] = self.app.instance_handler.do_get(x)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400208
Brad Bishop87b63c12016-03-18 14:47:51 -0400209 return objs
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400210
Brad Bishop87b63c12016-03-18 14:47:51 -0400211 @staticmethod
212 def enumerate_capable(path, bus_data):
213 busses = []
214 for name, ifaces in bus_data.iteritems():
Brad Bishopb103d2d2016-03-04 16:19:14 -0500215 if obmc.mapper.ENUMERATE_IFACE in ifaces:
Brad Bishop87b63c12016-03-18 14:47:51 -0400216 busses.append(name)
217 return busses
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400218
Brad Bishop87b63c12016-03-18 14:47:51 -0400219 def call_enumerate(self, path, busses):
220 objs = {}
221 for b in busses:
222 obj = self.bus.get_object(b, path, introspect=False)
Brad Bishopb103d2d2016-03-04 16:19:14 -0500223 iface = dbus.Interface(obj, obmc.mapper.ENUMERATE_IFACE)
Brad Bishop87b63c12016-03-18 14:47:51 -0400224 objs.update(iface.enumerate())
225 return objs
226
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400227
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500228class MethodHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400229 verbs = 'POST'
230 rules = '<path:path>/action/<method>'
231 request_type = list
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400232
Brad Bishop87b63c12016-03-18 14:47:51 -0400233 def __init__(self, app, bus):
234 super(MethodHandler, self).__init__(
235 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400236
Brad Bishop87b63c12016-03-18 14:47:51 -0400237 def find(self, path, method):
238 busses = self.try_mapper_call(
239 self.mapper.get_object, path=path)
240 for items in busses.iteritems():
241 m = self.find_method_on_bus(path, method, *items)
242 if m:
243 return m
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400244
Brad Bishop87b63c12016-03-18 14:47:51 -0400245 abort(404, _4034_msg % ('method', 'found', method))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400246
Brad Bishop87b63c12016-03-18 14:47:51 -0400247 def setup(self, path, method):
248 request.route_data['method'] = self.find(path, method)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400249
Brad Bishop87b63c12016-03-18 14:47:51 -0400250 def do_post(self, path, method):
251 try:
252 if request.parameter_list:
253 return request.route_data['method'](*request.parameter_list)
254 else:
255 return request.route_data['method']()
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400256
Brad Bishop87b63c12016-03-18 14:47:51 -0400257 except dbus.exceptions.DBusException, e:
258 if e.get_dbus_name() == DBUS_INVALID_ARGS:
259 abort(400, str(e))
260 if e.get_dbus_name() == DBUS_TYPE_ERROR:
261 abort(400, str(e))
262 raise
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400263
Brad Bishop87b63c12016-03-18 14:47:51 -0400264 @staticmethod
265 def find_method_in_interface(method, obj, interface, methods):
266 if methods is None:
267 return None
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400268
Brad Bishop87b63c12016-03-18 14:47:51 -0400269 method = find_case_insensitive(method, methods.keys())
270 if method is not None:
271 iface = dbus.Interface(obj, interface)
272 return iface.get_dbus_method(method)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400273
Brad Bishop87b63c12016-03-18 14:47:51 -0400274 def find_method_on_bus(self, path, method, bus, interfaces):
275 obj = self.bus.get_object(bus, path, introspect=False)
276 iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
277 data = iface.Introspect()
278 parser = IntrospectionNodeParser(
279 ElementTree.fromstring(data),
Brad Bishopb103d2d2016-03-04 16:19:14 -0500280 intf_match=obmc.utils.misc.ListMatch(interfaces))
Brad Bishop87b63c12016-03-18 14:47:51 -0400281 for x, y in parser.get_interfaces().iteritems():
282 m = self.find_method_in_interface(
283 method, obj, x, y.get('method'))
284 if m:
285 return m
286
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400287
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500288class PropertyHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400289 verbs = ['PUT', 'GET']
290 rules = '<path:path>/attr/<prop>'
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400291
Brad Bishop87b63c12016-03-18 14:47:51 -0400292 def __init__(self, app, bus):
293 super(PropertyHandler, self).__init__(
294 app, bus, self.verbs, self.rules)
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400295
Brad Bishop87b63c12016-03-18 14:47:51 -0400296 def find(self, path, prop):
297 self.app.instance_handler.setup(path)
298 obj = self.app.instance_handler.do_get(path)
299 try:
300 obj[prop]
301 except KeyError, e:
302 if request.method == 'PUT':
303 abort(403, _4034_msg % ('property', 'created', str(e)))
304 else:
305 abort(404, _4034_msg % ('property', 'found', str(e)))
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400306
Brad Bishop87b63c12016-03-18 14:47:51 -0400307 return {path: obj}
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500308
Brad Bishop87b63c12016-03-18 14:47:51 -0400309 def setup(self, path, prop):
310 request.route_data['obj'] = self.find(path, prop)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500311
Brad Bishop87b63c12016-03-18 14:47:51 -0400312 def do_get(self, path, prop):
313 return request.route_data['obj'][path][prop]
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500314
Brad Bishop87b63c12016-03-18 14:47:51 -0400315 def do_put(self, path, prop, value=None):
316 if value is None:
317 value = request.parameter_list
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500318
Brad Bishop87b63c12016-03-18 14:47:51 -0400319 prop, iface, properties_iface = self.get_host_interface(
320 path, prop, request.route_data['map'][path])
321 try:
322 properties_iface.Set(iface, prop, value)
323 except ValueError, e:
324 abort(400, str(e))
325 except dbus.exceptions.DBusException, e:
326 if e.get_dbus_name() == DBUS_INVALID_ARGS:
327 abort(403, str(e))
328 raise
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500329
Brad Bishop87b63c12016-03-18 14:47:51 -0400330 def get_host_interface(self, path, prop, bus_info):
331 for bus, interfaces in bus_info.iteritems():
332 obj = self.bus.get_object(bus, path, introspect=True)
333 properties_iface = dbus.Interface(
334 obj, dbus_interface=dbus.PROPERTIES_IFACE)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500335
Brad Bishop87b63c12016-03-18 14:47:51 -0400336 info = self.get_host_interface_on_bus(
337 path, prop, properties_iface, bus, interfaces)
338 if info is not None:
339 prop, iface = info
340 return prop, iface, properties_iface
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500341
Brad Bishop87b63c12016-03-18 14:47:51 -0400342 def get_host_interface_on_bus(self, path, prop, iface, bus, interfaces):
343 for i in interfaces:
344 properties = self.try_properties_interface(iface.GetAll, i)
345 if properties is None:
346 continue
347 prop = find_case_insensitive(prop, properties.keys())
348 if prop is None:
349 continue
350 return prop, i
351
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500352
Brad Bishop2503bd62015-12-16 17:56:12 -0500353class SchemaHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400354 verbs = ['GET']
355 rules = '<path:path>/schema'
Brad Bishop2503bd62015-12-16 17:56:12 -0500356
Brad Bishop87b63c12016-03-18 14:47:51 -0400357 def __init__(self, app, bus):
358 super(SchemaHandler, self).__init__(
359 app, bus, self.verbs, self.rules)
Brad Bishop2503bd62015-12-16 17:56:12 -0500360
Brad Bishop87b63c12016-03-18 14:47:51 -0400361 def find(self, path):
362 return self.try_mapper_call(
363 self.mapper.get_object,
364 path=path)
Brad Bishop2503bd62015-12-16 17:56:12 -0500365
Brad Bishop87b63c12016-03-18 14:47:51 -0400366 def setup(self, path):
367 request.route_data['map'] = self.find(path)
Brad Bishop2503bd62015-12-16 17:56:12 -0500368
Brad Bishop87b63c12016-03-18 14:47:51 -0400369 def do_get(self, path):
370 schema = {}
371 for x in request.route_data['map'].iterkeys():
372 obj = self.bus.get_object(x, path, introspect=False)
373 iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE)
374 data = iface.Introspect()
375 parser = IntrospectionNodeParser(
376 ElementTree.fromstring(data))
377 for x, y in parser.get_interfaces().iteritems():
378 schema[x] = y
Brad Bishop2503bd62015-12-16 17:56:12 -0500379
Brad Bishop87b63c12016-03-18 14:47:51 -0400380 return schema
381
Brad Bishop2503bd62015-12-16 17:56:12 -0500382
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500383class InstanceHandler(RouteHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400384 verbs = ['GET', 'PUT', 'DELETE']
385 rules = '<path:path>'
386 request_type = dict
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500387
Brad Bishop87b63c12016-03-18 14:47:51 -0400388 def __init__(self, app, bus):
389 super(InstanceHandler, self).__init__(
390 app, bus, self.verbs, self.rules)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500391
Brad Bishop87b63c12016-03-18 14:47:51 -0400392 def find(self, path, callback=None):
393 return {path: self.try_mapper_call(
394 self.mapper.get_object,
395 callback,
396 path=path)}
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500397
Brad Bishop87b63c12016-03-18 14:47:51 -0400398 def setup(self, path):
399 callback = None
400 if request.method == 'PUT':
401 def callback(e, **kw):
402 abort(403, _4034_msg % ('resource', 'created', path))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500403
Brad Bishop87b63c12016-03-18 14:47:51 -0400404 if request.route_data.get('map') is None:
405 request.route_data['map'] = self.find(path, callback)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500406
Brad Bishop87b63c12016-03-18 14:47:51 -0400407 def do_get(self, path):
408 properties = {}
409 for item in request.route_data['map'][path].iteritems():
410 properties.update(self.get_properties_on_bus(
411 path, *item))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500412
Brad Bishop87b63c12016-03-18 14:47:51 -0400413 return properties
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500414
Brad Bishop87b63c12016-03-18 14:47:51 -0400415 @staticmethod
416 def get_properties_on_iface(properties_iface, iface):
417 properties = InstanceHandler.try_properties_interface(
418 properties_iface.GetAll, iface)
419 if properties is None:
420 return {}
421 return properties
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500422
Brad Bishop87b63c12016-03-18 14:47:51 -0400423 def get_properties_on_bus(self, path, bus, interfaces):
424 properties = {}
425 obj = self.bus.get_object(bus, path, introspect=False)
426 properties_iface = dbus.Interface(
427 obj, dbus_interface=dbus.PROPERTIES_IFACE)
428 for i in interfaces:
429 properties.update(self.get_properties_on_iface(
430 properties_iface, i))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500431
Brad Bishop87b63c12016-03-18 14:47:51 -0400432 return properties
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500433
Brad Bishop87b63c12016-03-18 14:47:51 -0400434 def do_put(self, path):
435 # make sure all properties exist in the request
436 obj = set(self.do_get(path).keys())
437 req = set(request.parameter_list.keys())
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500438
Brad Bishop87b63c12016-03-18 14:47:51 -0400439 diff = list(obj.difference(req))
440 if diff:
441 abort(403, _4034_msg % (
442 'resource', 'removed', '%s/attr/%s' % (path, diff[0])))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500443
Brad Bishop87b63c12016-03-18 14:47:51 -0400444 diff = list(req.difference(obj))
445 if diff:
446 abort(403, _4034_msg % (
447 'resource', 'created', '%s/attr/%s' % (path, diff[0])))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500448
Brad Bishop87b63c12016-03-18 14:47:51 -0400449 for p, v in request.parameter_list.iteritems():
450 self.app.property_handler.do_put(
451 path, p, v)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500452
Brad Bishop87b63c12016-03-18 14:47:51 -0400453 def do_delete(self, path):
454 for bus_info in request.route_data['map'][path].iteritems():
455 if self.bus_missing_delete(path, *bus_info):
456 abort(403, _4034_msg % ('resource', 'removed', path))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500457
Brad Bishop87b63c12016-03-18 14:47:51 -0400458 for bus in request.route_data['map'][path].iterkeys():
459 self.delete_on_bus(path, bus)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500460
Brad Bishop87b63c12016-03-18 14:47:51 -0400461 def bus_missing_delete(self, path, bus, interfaces):
462 return DELETE_IFACE not in interfaces
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500463
Brad Bishop87b63c12016-03-18 14:47:51 -0400464 def delete_on_bus(self, path, bus):
465 obj = self.bus.get_object(bus, path, introspect=False)
466 delete_iface = dbus.Interface(
467 obj, dbus_interface=DELETE_IFACE)
468 delete_iface.Delete()
469
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500470
Brad Bishop2f428582015-12-02 10:56:11 -0500471class SessionHandler(MethodHandler):
Brad Bishop87b63c12016-03-18 14:47:51 -0400472 ''' Handles the /login and /logout routes, manages
473 server side session store and session cookies. '''
Brad Bishop2f428582015-12-02 10:56:11 -0500474
Brad Bishop87b63c12016-03-18 14:47:51 -0400475 rules = ['/login', '/logout']
476 login_str = "User '%s' logged %s"
477 bad_passwd_str = "Invalid username or password"
478 no_user_str = "No user logged in"
479 bad_json_str = "Expecting request format { 'data': " \
480 "[<username>, <password>] }, got '%s'"
481 _require_auth = None
482 MAX_SESSIONS = 16
Brad Bishop2f428582015-12-02 10:56:11 -0500483
Brad Bishop87b63c12016-03-18 14:47:51 -0400484 def __init__(self, app, bus):
485 super(SessionHandler, self).__init__(
486 app, bus)
487 self.hmac_key = os.urandom(128)
488 self.session_store = []
Brad Bishop2f428582015-12-02 10:56:11 -0500489
Brad Bishop87b63c12016-03-18 14:47:51 -0400490 @staticmethod
491 def authenticate(username, clear):
492 try:
493 encoded = spwd.getspnam(username)[1]
494 return encoded == crypt.crypt(clear, encoded)
495 except KeyError:
496 return False
Brad Bishop2f428582015-12-02 10:56:11 -0500497
Brad Bishop87b63c12016-03-18 14:47:51 -0400498 def invalidate_session(self, session):
499 try:
500 self.session_store.remove(session)
501 except ValueError:
502 pass
Brad Bishop2f428582015-12-02 10:56:11 -0500503
Brad Bishop87b63c12016-03-18 14:47:51 -0400504 def new_session(self):
505 sid = os.urandom(32)
506 if self.MAX_SESSIONS <= len(self.session_store):
507 self.session_store.pop()
508 self.session_store.insert(0, {'sid': sid})
Brad Bishop2f428582015-12-02 10:56:11 -0500509
Brad Bishop87b63c12016-03-18 14:47:51 -0400510 return self.session_store[0]
Brad Bishop2f428582015-12-02 10:56:11 -0500511
Brad Bishop87b63c12016-03-18 14:47:51 -0400512 def get_session(self, sid):
513 sids = [x['sid'] for x in self.session_store]
514 try:
515 return self.session_store[sids.index(sid)]
516 except ValueError:
517 return None
Brad Bishop2f428582015-12-02 10:56:11 -0500518
Brad Bishop87b63c12016-03-18 14:47:51 -0400519 def get_session_from_cookie(self):
520 return self.get_session(
521 request.get_cookie(
522 'sid', secret=self.hmac_key))
Brad Bishop2f428582015-12-02 10:56:11 -0500523
Brad Bishop87b63c12016-03-18 14:47:51 -0400524 def do_post(self, **kw):
525 if request.path == '/login':
526 return self.do_login(**kw)
527 else:
528 return self.do_logout(**kw)
Brad Bishop2f428582015-12-02 10:56:11 -0500529
Brad Bishop87b63c12016-03-18 14:47:51 -0400530 def do_logout(self, **kw):
531 session = self.get_session_from_cookie()
532 if session is not None:
533 user = session['user']
534 self.invalidate_session(session)
535 response.delete_cookie('sid')
536 return self.login_str % (user, 'out')
Brad Bishop2f428582015-12-02 10:56:11 -0500537
Brad Bishop87b63c12016-03-18 14:47:51 -0400538 return self.no_user_str
Brad Bishop2f428582015-12-02 10:56:11 -0500539
Brad Bishop87b63c12016-03-18 14:47:51 -0400540 def do_login(self, **kw):
541 session = self.get_session_from_cookie()
542 if session is not None:
543 return self.login_str % (session['user'], 'in')
Brad Bishop2f428582015-12-02 10:56:11 -0500544
Brad Bishop87b63c12016-03-18 14:47:51 -0400545 if len(request.parameter_list) != 2:
546 abort(400, self.bad_json_str % (request.json))
Brad Bishop2f428582015-12-02 10:56:11 -0500547
Brad Bishop87b63c12016-03-18 14:47:51 -0400548 if not self.authenticate(*request.parameter_list):
549 return self.bad_passwd_str
Brad Bishop2f428582015-12-02 10:56:11 -0500550
Brad Bishop87b63c12016-03-18 14:47:51 -0400551 user = request.parameter_list[0]
552 session = self.new_session()
553 session['user'] = user
554 response.set_cookie(
555 'sid', session['sid'], secret=self.hmac_key,
556 secure=True,
557 httponly=True)
558 return self.login_str % (user, 'in')
Brad Bishop2f428582015-12-02 10:56:11 -0500559
Brad Bishop87b63c12016-03-18 14:47:51 -0400560 def find(self, **kw):
561 pass
Brad Bishop2f428582015-12-02 10:56:11 -0500562
Brad Bishop87b63c12016-03-18 14:47:51 -0400563 def setup(self, **kw):
564 pass
565
Brad Bishop2f428582015-12-02 10:56:11 -0500566
567class AuthorizationPlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -0400568 ''' Invokes an optional list of authorization callbacks. '''
Brad Bishop2f428582015-12-02 10:56:11 -0500569
Brad Bishop87b63c12016-03-18 14:47:51 -0400570 name = 'authorization'
571 api = 2
Brad Bishop2f428582015-12-02 10:56:11 -0500572
Brad Bishop87b63c12016-03-18 14:47:51 -0400573 class Compose:
574 def __init__(self, validators, callback, session_mgr):
575 self.validators = validators
576 self.callback = callback
577 self.session_mgr = session_mgr
Brad Bishop2f428582015-12-02 10:56:11 -0500578
Brad Bishop87b63c12016-03-18 14:47:51 -0400579 def __call__(self, *a, **kw):
580 sid = request.get_cookie('sid', secret=self.session_mgr.hmac_key)
581 session = self.session_mgr.get_session(sid)
582 for x in self.validators:
583 x(session, *a, **kw)
Brad Bishop2f428582015-12-02 10:56:11 -0500584
Brad Bishop87b63c12016-03-18 14:47:51 -0400585 return self.callback(*a, **kw)
Brad Bishop2f428582015-12-02 10:56:11 -0500586
Brad Bishop87b63c12016-03-18 14:47:51 -0400587 def apply(self, callback, route):
588 undecorated = route.get_undecorated_callback()
589 if not isinstance(undecorated, RouteHandler):
590 return callback
Brad Bishop2f428582015-12-02 10:56:11 -0500591
Brad Bishop87b63c12016-03-18 14:47:51 -0400592 auth_types = getattr(
593 undecorated, '_require_auth', None)
594 if not auth_types:
595 return callback
Brad Bishop2f428582015-12-02 10:56:11 -0500596
Brad Bishop87b63c12016-03-18 14:47:51 -0400597 return self.Compose(
598 auth_types, callback, undecorated.app.session_handler)
599
Brad Bishop2f428582015-12-02 10:56:11 -0500600
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500601class JsonApiRequestPlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -0400602 ''' Ensures request content satisfies the OpenBMC json api format. '''
603 name = 'json_api_request'
604 api = 2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500605
Brad Bishop87b63c12016-03-18 14:47:51 -0400606 error_str = "Expecting request format { 'data': <value> }, got '%s'"
607 type_error_str = "Unsupported Content-Type: '%s'"
608 json_type = "application/json"
609 request_methods = ['PUT', 'POST', 'PATCH']
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500610
Brad Bishop87b63c12016-03-18 14:47:51 -0400611 @staticmethod
612 def content_expected():
613 return request.method in JsonApiRequestPlugin.request_methods
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500614
Brad Bishop87b63c12016-03-18 14:47:51 -0400615 def validate_request(self):
616 if request.content_length > 0 and \
617 request.content_type != self.json_type:
618 abort(415, self.type_error_str % request.content_type)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500619
Brad Bishop87b63c12016-03-18 14:47:51 -0400620 try:
621 request.parameter_list = request.json.get('data')
622 except ValueError, e:
623 abort(400, str(e))
624 except (AttributeError, KeyError, TypeError):
625 abort(400, self.error_str % request.json)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500626
Brad Bishop87b63c12016-03-18 14:47:51 -0400627 def apply(self, callback, route):
628 verbs = getattr(
629 route.get_undecorated_callback(), '_verbs', None)
630 if verbs is None:
631 return callback
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500632
Brad Bishop87b63c12016-03-18 14:47:51 -0400633 if not set(self.request_methods).intersection(verbs):
634 return callback
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500635
Brad Bishop87b63c12016-03-18 14:47:51 -0400636 def wrap(*a, **kw):
637 if self.content_expected():
638 self.validate_request()
639 return callback(*a, **kw)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500640
Brad Bishop87b63c12016-03-18 14:47:51 -0400641 return wrap
642
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500643
644class JsonApiRequestTypePlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -0400645 ''' Ensures request content type satisfies the OpenBMC json api format. '''
646 name = 'json_api_method_request'
647 api = 2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500648
Brad Bishop87b63c12016-03-18 14:47:51 -0400649 error_str = "Expecting request format { 'data': %s }, got '%s'"
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500650
Brad Bishop87b63c12016-03-18 14:47:51 -0400651 def apply(self, callback, route):
652 request_type = getattr(
653 route.get_undecorated_callback(), 'request_type', None)
654 if request_type is None:
655 return callback
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500656
Brad Bishop87b63c12016-03-18 14:47:51 -0400657 def validate_request():
658 if not isinstance(request.parameter_list, request_type):
659 abort(400, self.error_str % (str(request_type), request.json))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500660
Brad Bishop87b63c12016-03-18 14:47:51 -0400661 def wrap(*a, **kw):
662 if JsonApiRequestPlugin.content_expected():
663 validate_request()
664 return callback(*a, **kw)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500665
Brad Bishop87b63c12016-03-18 14:47:51 -0400666 return wrap
667
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500668
669class JsonApiResponsePlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -0400670 ''' Emits normal responses in the OpenBMC json api format. '''
671 name = 'json_api_response'
672 api = 2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500673
Brad Bishop87b63c12016-03-18 14:47:51 -0400674 def apply(self, callback, route):
675 def wrap(*a, **kw):
676 resp = {'data': callback(*a, **kw)}
677 resp['status'] = 'ok'
678 resp['message'] = response.status_line
679 return resp
680 return wrap
681
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500682
683class JsonApiErrorsPlugin(object):
Brad Bishop87b63c12016-03-18 14:47:51 -0400684 ''' Emits error responses in the OpenBMC json api format. '''
685 name = 'json_api_errors'
686 api = 2
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500687
Brad Bishop87b63c12016-03-18 14:47:51 -0400688 def __init__(self, **kw):
689 self.app = None
690 self.function_type = None
691 self.original = None
692 self.json_opts = {
693 x: y for x, y in kw.iteritems()
694 if x in ['indent', 'sort_keys']}
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500695
Brad Bishop87b63c12016-03-18 14:47:51 -0400696 def setup(self, app):
697 self.app = app
698 self.function_type = type(app.default_error_handler)
699 self.original = app.default_error_handler
700 self.app.default_error_handler = self.function_type(
701 self.json_errors, app, Bottle)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500702
Brad Bishop87b63c12016-03-18 14:47:51 -0400703 def apply(self, callback, route):
704 return callback
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500705
Brad Bishop87b63c12016-03-18 14:47:51 -0400706 def close(self):
707 self.app.default_error_handler = self.function_type(
708 self.original, self.app, Bottle)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500709
Brad Bishop87b63c12016-03-18 14:47:51 -0400710 def json_errors(self, res, error):
711 response_object = {'status': 'error', 'data': {}}
712 response_object['message'] = error.status_line
713 response_object['data']['description'] = str(error.body)
714 if error.status_code == 500:
715 response_object['data']['exception'] = repr(error.exception)
716 response_object['data']['traceback'] = error.traceback.splitlines()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500717
Brad Bishop87b63c12016-03-18 14:47:51 -0400718 json_response = json.dumps(response_object, **self.json_opts)
719 response.content_type = 'application/json'
720 return json_response
721
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500722
723class RestApp(Bottle):
Brad Bishop87b63c12016-03-18 14:47:51 -0400724 def __init__(self, bus):
725 super(RestApp, self).__init__(autojson=False)
726 self.bus = bus
Brad Bishopb103d2d2016-03-04 16:19:14 -0500727 self.mapper = obmc.mapper.Mapper(bus)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500728
Brad Bishop87b63c12016-03-18 14:47:51 -0400729 self.install_hooks()
730 self.install_plugins()
731 self.create_handlers()
732 self.install_handlers()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500733
Brad Bishop87b63c12016-03-18 14:47:51 -0400734 def install_plugins(self):
735 # install json api plugins
736 json_kw = {'indent': 2, 'sort_keys': True}
737 self.install(JSONPlugin(**json_kw))
738 self.install(JsonApiErrorsPlugin(**json_kw))
739 self.install(AuthorizationPlugin())
740 self.install(JsonApiResponsePlugin())
741 self.install(JsonApiRequestPlugin())
742 self.install(JsonApiRequestTypePlugin())
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500743
Brad Bishop87b63c12016-03-18 14:47:51 -0400744 def install_hooks(self):
745 self.real_router_match = self.router.match
746 self.router.match = self.custom_router_match
747 self.add_hook('before_request', self.strip_extra_slashes)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500748
Brad Bishop87b63c12016-03-18 14:47:51 -0400749 def create_handlers(self):
750 # create route handlers
751 self.session_handler = SessionHandler(self, self.bus)
752 self.directory_handler = DirectoryHandler(self, self.bus)
753 self.list_names_handler = ListNamesHandler(self, self.bus)
754 self.list_handler = ListHandler(self, self.bus)
755 self.method_handler = MethodHandler(self, self.bus)
756 self.property_handler = PropertyHandler(self, self.bus)
757 self.schema_handler = SchemaHandler(self, self.bus)
758 self.instance_handler = InstanceHandler(self, self.bus)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500759
Brad Bishop87b63c12016-03-18 14:47:51 -0400760 def install_handlers(self):
761 self.session_handler.install()
762 self.directory_handler.install()
763 self.list_names_handler.install()
764 self.list_handler.install()
765 self.method_handler.install()
766 self.property_handler.install()
767 self.schema_handler.install()
768 # this has to come last, since it matches everything
769 self.instance_handler.install()
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500770
Brad Bishop87b63c12016-03-18 14:47:51 -0400771 def custom_router_match(self, environ):
772 ''' The built-in Bottle algorithm for figuring out if a 404 or 405 is
773 needed doesn't work for us since the instance rules match
774 everything. This monkey-patch lets the route handler figure
775 out which response is needed. This could be accomplished
776 with a hook but that would require calling the router match
777 function twice.
778 '''
779 route, args = self.real_router_match(environ)
780 if isinstance(route.callback, RouteHandler):
781 route.callback._setup(**args)
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500782
Brad Bishop87b63c12016-03-18 14:47:51 -0400783 return route, args
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500784
Brad Bishop87b63c12016-03-18 14:47:51 -0400785 @staticmethod
786 def strip_extra_slashes():
787 path = request.environ['PATH_INFO']
788 trailing = ("", "/")[path[-1] == '/']
789 parts = filter(bool, path.split('/'))
790 request.environ['PATH_INFO'] = '/' + '/'.join(parts) + trailing
Brad Bishopaa65f6e2015-10-27 16:28:51 -0400791
792if __name__ == '__main__':
Brad Bishop87b63c12016-03-18 14:47:51 -0400793 log = logging.getLogger('Rocket.Errors')
794 log.setLevel(logging.INFO)
795 log.addHandler(logging.StreamHandler(sys.stdout))
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500796
Brad Bishop87b63c12016-03-18 14:47:51 -0400797 bus = dbus.SystemBus()
798 app = RestApp(bus)
799 default_cert = os.path.join(
800 sys.prefix, 'share', os.path.basename(__file__), 'cert.pem')
Brad Bishopb1cbdaf2015-11-13 21:28:16 -0500801
Brad Bishop87b63c12016-03-18 14:47:51 -0400802 server = Rocket(
803 ('0.0.0.0', 443, default_cert, default_cert),
804 'wsgi', {'wsgi_app': app},
805 min_threads=1,
806 max_threads=1)
807 server.start()