| Brad Bishop | 68caa1e | 2016-03-04 15:42:08 -0500 | [diff] [blame] | 1 | # 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 Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 17 | import os | 
| Alexander Filippov | d08a456 | 2018-03-20 12:02:23 +0300 | [diff] [blame] | 18 | import sys | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 19 | import dbus | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 20 | import dbus.exceptions | 
 | 21 | import json | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 22 | from xml.etree import ElementTree | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 23 | from bottle import Bottle, abort, request, response, JSONPlugin, HTTPError | 
| Jayanth Othayoth | 9bc9499 | 2017-06-29 06:30:40 -0500 | [diff] [blame] | 24 | from bottle import static_file | 
| Brad Bishop | b103d2d | 2016-03-04 16:19:14 -0500 | [diff] [blame] | 25 | import obmc.utils.misc | 
| Brad Bishop | b103d2d | 2016-03-04 16:19:14 -0500 | [diff] [blame] | 26 | from obmc.dbuslib.introspection import IntrospectionNodeParser | 
 | 27 | import obmc.mapper | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 28 | import spwd | 
 | 29 | import grp | 
 | 30 | import crypt | 
| Deepak Kodihalli | 1af301a | 2017-04-11 07:29:01 -0500 | [diff] [blame] | 31 | import tempfile | 
| Leonel Gonzalez | 0bdef95 | 2017-04-18 08:17:49 -0500 | [diff] [blame] | 32 | import re | 
| Matt Spinler | d41643e | 2018-02-02 13:51:38 -0600 | [diff] [blame] | 33 | import mimetypes | 
| Deepak Kodihalli | 639b502 | 2017-10-13 06:40:26 -0500 | [diff] [blame] | 34 | have_wsock = True | 
 | 35 | try: | 
 | 36 |     from geventwebsocket import WebSocketError | 
 | 37 | except ImportError: | 
 | 38 |     have_wsock = False | 
 | 39 | if have_wsock: | 
 | 40 |     from dbus.mainloop.glib import DBusGMainLoop | 
 | 41 |     DBusGMainLoop(set_as_default=True) | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 42 |     # TODO: openbmc/openbmc#2994 remove python 2 support | 
 | 43 |     try:  # python 2 | 
 | 44 |         import gobject | 
 | 45 |     except ImportError:  # python 3 | 
 | 46 |         from gi.repository import GObject as gobject | 
| Deepak Kodihalli | 639b502 | 2017-10-13 06:40:26 -0500 | [diff] [blame] | 47 |     import gevent | 
| Deepak Kodihalli | 5c518f6 | 2018-04-23 03:26:38 -0500 | [diff] [blame] | 48 |     from gevent import socket | 
 | 49 |     from gevent import Greenlet | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 50 |  | 
| Adriana Kobylak | f92cf4d | 2017-12-13 11:46:50 -0600 | [diff] [blame] | 51 | DBUS_UNKNOWN_INTERFACE = 'org.freedesktop.DBus.Error.UnknownInterface' | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 52 | DBUS_UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod' | 
| Adriana Kobylak | a8b05d1 | 2018-08-23 10:44:07 -0500 | [diff] [blame] | 53 | DBUS_PROPERTY_READONLY = 'org.freedesktop.DBus.Error.PropertyReadOnly' | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 54 | DBUS_INVALID_ARGS = 'org.freedesktop.DBus.Error.InvalidArgs' | 
| Brad Bishop | d457892 | 2015-12-02 11:10:36 -0500 | [diff] [blame] | 55 | DBUS_TYPE_ERROR = 'org.freedesktop.DBus.Python.TypeError' | 
| Deepak Kodihalli | 6075bb4 | 2017-04-04 05:49:17 -0500 | [diff] [blame] | 56 | DELETE_IFACE = 'xyz.openbmc_project.Object.Delete' | 
| Adriana Kobylak | 5369389 | 2018-03-12 13:05:50 -0500 | [diff] [blame] | 57 | SOFTWARE_PATH = '/xyz/openbmc_project/software' | 
| Jayashankar Padath | bec10c2 | 2018-05-29 18:22:59 +0530 | [diff] [blame] | 58 | WEBSOCKET_TIMEOUT = 45 | 
| Brad Bishop | 9ee57c4 | 2015-11-03 14:59:29 -0500 | [diff] [blame] | 59 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 60 | _4034_msg = "The specified %s cannot be %s: '%s'" | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 61 |  | 
| Matt Spinler | d41643e | 2018-02-02 13:51:38 -0600 | [diff] [blame] | 62 | www_base_path = '/usr/share/www/' | 
 | 63 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 64 |  | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 65 | def valid_user(session, *a, **kw): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 66 |     ''' Authorization plugin callback that checks | 
 | 67 |     that the user is logged in. ''' | 
 | 68 |     if session is None: | 
| Brad Bishop | dc3fbfa | 2016-09-08 09:51:38 -0400 | [diff] [blame] | 69 |         abort(401, 'Login required') | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 70 |  | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 71 |  | 
| Leonel Gonzalez | 0bdef95 | 2017-04-18 08:17:49 -0500 | [diff] [blame] | 72 | def get_type_signature_by_introspection(bus, service, object_path, | 
 | 73 |                                         property_name): | 
 | 74 |     obj = bus.get_object(service, object_path) | 
 | 75 |     iface = dbus.Interface(obj, 'org.freedesktop.DBus.Introspectable') | 
 | 76 |     xml_string = iface.Introspect() | 
 | 77 |     for child in ElementTree.fromstring(xml_string): | 
 | 78 |         # Iterate over each interfaces's properties to find | 
 | 79 |         # matching property_name, and return its signature string | 
 | 80 |         if child.tag == 'interface': | 
 | 81 |             for i in child.iter(): | 
 | 82 |                 if ('name' in i.attrib) and \ | 
 | 83 |                    (i.attrib['name'] == property_name): | 
 | 84 |                     type_signature = i.attrib['type'] | 
 | 85 |                     return type_signature | 
 | 86 |  | 
 | 87 |  | 
| Ratan Gupta | a6a8a4c | 2017-08-07 08:18:44 +0530 | [diff] [blame] | 88 | def get_method_signature(bus, service, object_path, interface, method): | 
 | 89 |     obj = bus.get_object(service, object_path) | 
 | 90 |     iface = dbus.Interface(obj, 'org.freedesktop.DBus.Introspectable') | 
 | 91 |     xml_string = iface.Introspect() | 
 | 92 |     arglist = [] | 
 | 93 |  | 
 | 94 |     root = ElementTree.fromstring(xml_string) | 
 | 95 |     for dbus_intf in root.findall('interface'): | 
 | 96 |         if (dbus_intf.get('name') == interface): | 
 | 97 |             for dbus_method in dbus_intf.findall('method'): | 
 | 98 |                 if(dbus_method.get('name') == method): | 
 | 99 |                     for arg in dbus_method.findall('arg'): | 
 | 100 |                         arglist.append(arg.get('type')) | 
 | 101 |                     return arglist | 
 | 102 |  | 
 | 103 |  | 
| Leonel Gonzalez | 0bdef95 | 2017-04-18 08:17:49 -0500 | [diff] [blame] | 104 | def split_struct_signature(signature): | 
 | 105 |     struct_regex = r'(b|y|n|i|x|q|u|t|d|s|a\(.+?\)|\(.+?\))|a\{.+?\}+?' | 
 | 106 |     struct_matches = re.findall(struct_regex, signature) | 
 | 107 |     return struct_matches | 
 | 108 |  | 
 | 109 |  | 
 | 110 | def convert_type(signature, value): | 
 | 111 |     # Basic Types | 
 | 112 |     converted_value = None | 
 | 113 |     converted_container = None | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 114 |     # TODO: openbmc/openbmc#2994 remove python 2 support | 
 | 115 |     try:  # python 2 | 
 | 116 |         basic_types = {'b': bool, 'y': dbus.Byte, 'n': dbus.Int16, 'i': int, | 
 | 117 |                        'x': long, 'q': dbus.UInt16, 'u': dbus.UInt32, | 
 | 118 |                        't': dbus.UInt64, 'd': float, 's': str} | 
 | 119 |     except NameError:  # python 3 | 
 | 120 |         basic_types = {'b': bool, 'y': dbus.Byte, 'n': dbus.Int16, 'i': int, | 
 | 121 |                        'x': int, 'q': dbus.UInt16, 'u': dbus.UInt32, | 
 | 122 |                        't': dbus.UInt64, 'd': float, 's': str} | 
| Leonel Gonzalez | 0bdef95 | 2017-04-18 08:17:49 -0500 | [diff] [blame] | 123 |     array_matches = re.match(r'a\((\S+)\)', signature) | 
 | 124 |     struct_matches = re.match(r'\((\S+)\)', signature) | 
 | 125 |     dictionary_matches = re.match(r'a{(\S+)}', signature) | 
 | 126 |     if signature in basic_types: | 
 | 127 |         converted_value = basic_types[signature](value) | 
 | 128 |         return converted_value | 
 | 129 |     # Array | 
 | 130 |     if array_matches: | 
 | 131 |         element_type = array_matches.group(1) | 
 | 132 |         converted_container = list() | 
 | 133 |         # Test if value is a list | 
 | 134 |         # to avoid iterating over each character in a string. | 
 | 135 |         # Iterate over each item and convert type | 
 | 136 |         if isinstance(value, list): | 
 | 137 |             for i in value: | 
 | 138 |                 converted_element = convert_type(element_type, i) | 
 | 139 |                 converted_container.append(converted_element) | 
 | 140 |         # Convert non-sequence to expected type, and append to list | 
 | 141 |         else: | 
 | 142 |             converted_element = convert_type(element_type, value) | 
 | 143 |             converted_container.append(converted_element) | 
 | 144 |         return converted_container | 
 | 145 |     # Struct | 
 | 146 |     if struct_matches: | 
 | 147 |         element_types = struct_matches.group(1) | 
 | 148 |         split_element_types = split_struct_signature(element_types) | 
 | 149 |         converted_container = list() | 
 | 150 |         # Test if value is a list | 
 | 151 |         if isinstance(value, list): | 
 | 152 |             for index, val in enumerate(value): | 
 | 153 |                 converted_element = convert_type(split_element_types[index], | 
 | 154 |                                                  value[index]) | 
 | 155 |                 converted_container.append(converted_element) | 
 | 156 |         else: | 
 | 157 |             converted_element = convert_type(element_types, value) | 
 | 158 |             converted_container.append(converted_element) | 
 | 159 |         return tuple(converted_container) | 
 | 160 |     # Dictionary | 
 | 161 |     if dictionary_matches: | 
 | 162 |         element_types = dictionary_matches.group(1) | 
 | 163 |         split_element_types = split_struct_signature(element_types) | 
 | 164 |         converted_container = dict() | 
 | 165 |         # Convert each element of dict | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 166 |         for key, val in value.items(): | 
| Leonel Gonzalez | 0bdef95 | 2017-04-18 08:17:49 -0500 | [diff] [blame] | 167 |             converted_key = convert_type(split_element_types[0], key) | 
 | 168 |             converted_val = convert_type(split_element_types[1], val) | 
 | 169 |             converted_container[converted_key] = converted_val | 
 | 170 |         return converted_container | 
 | 171 |  | 
 | 172 |  | 
| Jayashankar Padath | bec10c2 | 2018-05-29 18:22:59 +0530 | [diff] [blame] | 173 | def send_ws_ping(wsock, timeout) : | 
 | 174 |     # Most webservers close websockets after 60 seconds of | 
 | 175 |     # inactivity. Make sure to send a ping before that. | 
 | 176 |     payload = "ping" | 
 | 177 |     # the ping payload can be anything, the receiver has to just | 
 | 178 |     # return the same back. | 
 | 179 |     while True: | 
 | 180 |         gevent.sleep(timeout) | 
 | 181 |         try: | 
 | 182 |             if wsock: | 
 | 183 |                 wsock.send_frame(payload, wsock.OPCODE_PING) | 
 | 184 |         except Exception as e: | 
 | 185 |             wsock.close() | 
 | 186 |             return | 
 | 187 |  | 
 | 188 |  | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 189 | class UserInGroup: | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 190 |     ''' Authorization plugin callback that checks that the user is logged in | 
 | 191 |     and a member of a group. ''' | 
 | 192 |     def __init__(self, group): | 
 | 193 |         self.group = group | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 194 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 195 |     def __call__(self, session, *a, **kw): | 
 | 196 |         valid_user(session, *a, **kw) | 
 | 197 |         res = False | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 198 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 199 |         try: | 
 | 200 |             res = session['user'] in grp.getgrnam(self.group)[3] | 
 | 201 |         except KeyError: | 
 | 202 |             pass | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 203 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 204 |         if not res: | 
 | 205 |             abort(403, 'Insufficient access') | 
 | 206 |  | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 207 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 208 | class RouteHandler(object): | 
| Brad Bishop | 6d19060 | 2016-04-15 13:09:39 -0400 | [diff] [blame] | 209 |     _require_auth = obmc.utils.misc.makelist(valid_user) | 
| Brad Bishop | d0c404a | 2017-02-21 09:23:25 -0500 | [diff] [blame] | 210 |     _enable_cors = True | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 211 |  | 
| Deepak Kodihalli | 83afbaf | 2017-04-10 06:37:19 -0500 | [diff] [blame] | 212 |     def __init__(self, app, bus, verbs, rules, content_type=''): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 213 |         self.app = app | 
 | 214 |         self.bus = bus | 
| Brad Bishop | b103d2d | 2016-03-04 16:19:14 -0500 | [diff] [blame] | 215 |         self.mapper = obmc.mapper.Mapper(bus) | 
| Brad Bishop | 6d19060 | 2016-04-15 13:09:39 -0400 | [diff] [blame] | 216 |         self._verbs = obmc.utils.misc.makelist(verbs) | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 217 |         self._rules = rules | 
| Deepak Kodihalli | 83afbaf | 2017-04-10 06:37:19 -0500 | [diff] [blame] | 218 |         self._content_type = content_type | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 219 |  | 
| Brad Bishop | 88c76a4 | 2017-02-21 00:02:02 -0500 | [diff] [blame] | 220 |         if 'GET' in self._verbs: | 
 | 221 |             self._verbs = list(set(self._verbs + ['HEAD'])) | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 222 |         if 'OPTIONS' not in self._verbs: | 
 | 223 |             self._verbs.append('OPTIONS') | 
| Brad Bishop | 88c76a4 | 2017-02-21 00:02:02 -0500 | [diff] [blame] | 224 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 225 |     def _setup(self, **kw): | 
 | 226 |         request.route_data = {} | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 227 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 228 |         if request.method in self._verbs: | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 229 |             if request.method != 'OPTIONS': | 
 | 230 |                 return self.setup(**kw) | 
| Brad Bishop | 88c76a4 | 2017-02-21 00:02:02 -0500 | [diff] [blame] | 231 |  | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 232 |             # Javascript implementations will not send credentials | 
 | 233 |             # with an OPTIONS request.  Don't help malicious clients | 
 | 234 |             # by checking the path here and returning a 404 if the | 
 | 235 |             # path doesn't exist. | 
 | 236 |             return None | 
| Brad Bishop | 88c76a4 | 2017-02-21 00:02:02 -0500 | [diff] [blame] | 237 |  | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 238 |         # Return 405 | 
| Brad Bishop | 88c76a4 | 2017-02-21 00:02:02 -0500 | [diff] [blame] | 239 |         raise HTTPError( | 
 | 240 |             405, "Method not allowed.", Allow=','.join(self._verbs)) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 241 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 242 |     def __call__(self, **kw): | 
 | 243 |         return getattr(self, 'do_' + request.method.lower())(**kw) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 244 |  | 
| Brad Bishop | 88c76a4 | 2017-02-21 00:02:02 -0500 | [diff] [blame] | 245 |     def do_head(self, **kw): | 
 | 246 |         return self.do_get(**kw) | 
 | 247 |  | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 248 |     def do_options(self, **kw): | 
 | 249 |         for v in self._verbs: | 
 | 250 |             response.set_header( | 
 | 251 |                 'Allow', | 
 | 252 |                 ','.join(self._verbs)) | 
 | 253 |         return None | 
 | 254 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 255 |     def install(self): | 
 | 256 |         self.app.route( | 
 | 257 |             self._rules, callback=self, | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 258 |             method=['OPTIONS', 'GET', 'PUT', 'PATCH', 'POST', 'DELETE']) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 259 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 260 |     @staticmethod | 
 | 261 |     def try_mapper_call(f, callback=None, **kw): | 
 | 262 |         try: | 
 | 263 |             return f(**kw) | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 264 |         except dbus.exceptions.DBusException as e: | 
| Brad Bishop | fce7756 | 2016-11-28 15:44:18 -0500 | [diff] [blame] | 265 |             if e.get_dbus_name() == \ | 
 | 266 |                     'org.freedesktop.DBus.Error.ObjectPathInUse': | 
 | 267 |                 abort(503, str(e)) | 
| Brad Bishop | b103d2d | 2016-03-04 16:19:14 -0500 | [diff] [blame] | 268 |             if e.get_dbus_name() != obmc.mapper.MAPPER_NOT_FOUND: | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 269 |                 raise | 
 | 270 |             if callback is None: | 
 | 271 |                 def callback(e, **kw): | 
 | 272 |                     abort(404, str(e)) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 273 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 274 |             callback(e, **kw) | 
 | 275 |  | 
 | 276 |     @staticmethod | 
 | 277 |     def try_properties_interface(f, *a): | 
 | 278 |         try: | 
 | 279 |             return f(*a) | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 280 |         except dbus.exceptions.DBusException as e: | 
| Adriana Kobylak | f92cf4d | 2017-12-13 11:46:50 -0600 | [diff] [blame] | 281 |             if DBUS_UNKNOWN_INTERFACE in e.get_dbus_name(): | 
| Brad Bishop | f4e7498 | 2016-04-01 14:53:05 -0400 | [diff] [blame] | 282 |                 # interface doesn't have any properties | 
 | 283 |                 return None | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 284 |             if DBUS_UNKNOWN_METHOD == e.get_dbus_name(): | 
 | 285 |                 # properties interface not implemented at all | 
 | 286 |                 return None | 
 | 287 |             raise | 
 | 288 |  | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 289 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 290 | class DirectoryHandler(RouteHandler): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 291 |     verbs = 'GET' | 
 | 292 |     rules = '<path:path>/' | 
| Deepak Kodihalli | 6e1ca53 | 2018-09-04 03:51:04 -0500 | [diff] [blame] | 293 |     suppress_logging = True | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 294 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 295 |     def __init__(self, app, bus): | 
 | 296 |         super(DirectoryHandler, self).__init__( | 
| Brad Bishop | c431e1a | 2017-07-10 16:44:51 -0400 | [diff] [blame] | 297 |             app, bus, self.verbs, self.rules) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 298 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 299 |     def find(self, path='/'): | 
 | 300 |         return self.try_mapper_call( | 
 | 301 |             self.mapper.get_subtree_paths, path=path, depth=1) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 302 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 303 |     def setup(self, path='/'): | 
 | 304 |         request.route_data['map'] = self.find(path) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 305 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 306 |     def do_get(self, path='/'): | 
 | 307 |         return request.route_data['map'] | 
 | 308 |  | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 309 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 310 | class ListNamesHandler(RouteHandler): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 311 |     verbs = 'GET' | 
 | 312 |     rules = ['/list', '<path:path>/list'] | 
| Deepak Kodihalli | 6e1ca53 | 2018-09-04 03:51:04 -0500 | [diff] [blame] | 313 |     suppress_logging = True | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 314 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 315 |     def __init__(self, app, bus): | 
 | 316 |         super(ListNamesHandler, self).__init__( | 
| Brad Bishop | c431e1a | 2017-07-10 16:44:51 -0400 | [diff] [blame] | 317 |             app, bus, self.verbs, self.rules) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 318 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 319 |     def find(self, path='/'): | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 320 |         return list(self.try_mapper_call( | 
 | 321 |             self.mapper.get_subtree, path=path).keys()) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 322 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 323 |     def setup(self, path='/'): | 
 | 324 |         request.route_data['map'] = self.find(path) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 325 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 326 |     def do_get(self, path='/'): | 
 | 327 |         return request.route_data['map'] | 
 | 328 |  | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 329 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 330 | class ListHandler(RouteHandler): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 331 |     verbs = 'GET' | 
 | 332 |     rules = ['/enumerate', '<path:path>/enumerate'] | 
| Deepak Kodihalli | 6e1ca53 | 2018-09-04 03:51:04 -0500 | [diff] [blame] | 333 |     suppress_logging = True | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 334 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 335 |     def __init__(self, app, bus): | 
 | 336 |         super(ListHandler, self).__init__( | 
| Brad Bishop | c431e1a | 2017-07-10 16:44:51 -0400 | [diff] [blame] | 337 |             app, bus, self.verbs, self.rules) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 338 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 339 |     def find(self, path='/'): | 
 | 340 |         return self.try_mapper_call( | 
 | 341 |             self.mapper.get_subtree, path=path) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 342 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 343 |     def setup(self, path='/'): | 
 | 344 |         request.route_data['map'] = self.find(path) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 345 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 346 |     def do_get(self, path='/'): | 
| Brad Bishop | 71527b4 | 2016-04-01 14:51:14 -0400 | [diff] [blame] | 347 |         return {x: y for x, y in self.mapper.enumerate_subtree( | 
 | 348 |                 path, | 
 | 349 |                 mapper_data=request.route_data['map']).dataitems()} | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 350 |  | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 351 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 352 | class MethodHandler(RouteHandler): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 353 |     verbs = 'POST' | 
 | 354 |     rules = '<path:path>/action/<method>' | 
 | 355 |     request_type = list | 
| Deepak Kodihalli | 83afbaf | 2017-04-10 06:37:19 -0500 | [diff] [blame] | 356 |     content_type = 'application/json' | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 357 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 358 |     def __init__(self, app, bus): | 
 | 359 |         super(MethodHandler, self).__init__( | 
| Deepak Kodihalli | 83afbaf | 2017-04-10 06:37:19 -0500 | [diff] [blame] | 360 |             app, bus, self.verbs, self.rules, self.content_type) | 
| Ratan Gupta | a6a8a4c | 2017-08-07 08:18:44 +0530 | [diff] [blame] | 361 |         self.service = '' | 
 | 362 |         self.interface = '' | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 363 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 364 |     def find(self, path, method): | 
| Saqib Khan | 3a00b1f | 2017-11-04 15:56:21 -0500 | [diff] [blame] | 365 |         method_list = [] | 
| Gunnar Mills | 313aadb | 2018-04-08 14:50:09 -0500 | [diff] [blame] | 366 |         buses = self.try_mapper_call( | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 367 |             self.mapper.get_object, path=path) | 
| Gunnar Mills | 313aadb | 2018-04-08 14:50:09 -0500 | [diff] [blame] | 368 |         for items in buses.items(): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 369 |             m = self.find_method_on_bus(path, method, *items) | 
 | 370 |             if m: | 
| Saqib Khan | 3a00b1f | 2017-11-04 15:56:21 -0500 | [diff] [blame] | 371 |                 method_list.append(m) | 
| Nagaraju Goruganti | 765c2c8 | 2017-11-13 06:17:13 -0600 | [diff] [blame] | 372 |         if method_list: | 
 | 373 |             return method_list | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 374 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 375 |         abort(404, _4034_msg % ('method', 'found', method)) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 376 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 377 |     def setup(self, path, method): | 
| Saqib Khan | 3a00b1f | 2017-11-04 15:56:21 -0500 | [diff] [blame] | 378 |         request.route_data['map'] = self.find(path, method) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 379 |  | 
| Marri Devender Rao | bc0c673 | 2017-11-20 00:15:47 -0600 | [diff] [blame] | 380 |     def do_post(self, path, method, retry=True): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 381 |         try: | 
| Nagaraju Goruganti | 765c2c8 | 2017-11-13 06:17:13 -0600 | [diff] [blame] | 382 |             args = [] | 
 | 383 |             if request.parameter_list: | 
 | 384 |                 args = request.parameter_list | 
 | 385 |             # To see if the return type is capable of being merged | 
 | 386 |             if len(request.route_data['map']) > 1: | 
 | 387 |                 results = None | 
 | 388 |                 for item in request.route_data['map']: | 
 | 389 |                     tmp = item(*args) | 
 | 390 |                     if not results: | 
 | 391 |                         if tmp is not None: | 
 | 392 |                             results = type(tmp)() | 
 | 393 |                     if isinstance(results, dict): | 
 | 394 |                         results = results.update(tmp) | 
 | 395 |                     elif isinstance(results, list): | 
 | 396 |                         results = results + tmp | 
 | 397 |                     elif isinstance(results, type(None)): | 
 | 398 |                         results = None | 
 | 399 |                     else: | 
 | 400 |                         abort(501, 'Don\'t know how to merge method call ' | 
 | 401 |                                    'results of {}'.format(type(tmp))) | 
 | 402 |                 return results | 
 | 403 |             # There is only one method | 
 | 404 |             return request.route_data['map'][0](*args) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 405 |  | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 406 |         except dbus.exceptions.DBusException as e: | 
| Ratan Gupta | a6a8a4c | 2017-08-07 08:18:44 +0530 | [diff] [blame] | 407 |             paramlist = [] | 
| Brad Bishop | b7fca9b | 2018-01-23 12:16:50 -0500 | [diff] [blame] | 408 |             if e.get_dbus_name() == DBUS_INVALID_ARGS and retry: | 
| Ratan Gupta | a6a8a4c | 2017-08-07 08:18:44 +0530 | [diff] [blame] | 409 |  | 
 | 410 |                 signature_list = get_method_signature(self.bus, self.service, | 
 | 411 |                                                       path, self.interface, | 
 | 412 |                                                       method) | 
 | 413 |                 if not signature_list: | 
 | 414 |                     abort(400, "Failed to get method signature: %s" % str(e)) | 
 | 415 |                 if len(signature_list) != len(request.parameter_list): | 
 | 416 |                     abort(400, "Invalid number of args") | 
 | 417 |                 converted_value = None | 
 | 418 |                 try: | 
 | 419 |                     for index, expected_type in enumerate(signature_list): | 
 | 420 |                         value = request.parameter_list[index] | 
 | 421 |                         converted_value = convert_type(expected_type, value) | 
 | 422 |                         paramlist.append(converted_value) | 
 | 423 |                     request.parameter_list = paramlist | 
| Marri Devender Rao | bc0c673 | 2017-11-20 00:15:47 -0600 | [diff] [blame] | 424 |                     self.do_post(path, method, False) | 
| Ratan Gupta | a6a8a4c | 2017-08-07 08:18:44 +0530 | [diff] [blame] | 425 |                     return | 
 | 426 |                 except Exception as ex: | 
| Nagaraju Goruganti | ab404fa | 2017-12-14 10:24:40 -0600 | [diff] [blame] | 427 |                     abort(400, "Bad Request/Invalid Args given") | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 428 |                 abort(400, str(e)) | 
| Ratan Gupta | a6a8a4c | 2017-08-07 08:18:44 +0530 | [diff] [blame] | 429 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 430 |             if e.get_dbus_name() == DBUS_TYPE_ERROR: | 
 | 431 |                 abort(400, str(e)) | 
 | 432 |             raise | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 433 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 434 |     @staticmethod | 
 | 435 |     def find_method_in_interface(method, obj, interface, methods): | 
 | 436 |         if methods is None: | 
 | 437 |             return None | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 438 |  | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 439 |         method = obmc.utils.misc.find_case_insensitive(method, list(methods.keys())) | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 440 |         if method is not None: | 
 | 441 |             iface = dbus.Interface(obj, interface) | 
 | 442 |             return iface.get_dbus_method(method) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 443 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 444 |     def find_method_on_bus(self, path, method, bus, interfaces): | 
 | 445 |         obj = self.bus.get_object(bus, path, introspect=False) | 
 | 446 |         iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE) | 
 | 447 |         data = iface.Introspect() | 
 | 448 |         parser = IntrospectionNodeParser( | 
 | 449 |             ElementTree.fromstring(data), | 
| Brad Bishop | aeb995d | 2018-04-04 22:28:42 -0400 | [diff] [blame] | 450 |             intf_match=lambda x: x in interfaces) | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 451 |         for x, y in parser.get_interfaces().items(): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 452 |             m = self.find_method_in_interface( | 
 | 453 |                 method, obj, x, y.get('method')) | 
 | 454 |             if m: | 
| Ratan Gupta | a6a8a4c | 2017-08-07 08:18:44 +0530 | [diff] [blame] | 455 |                 self.service = bus | 
 | 456 |                 self.interface = x | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 457 |                 return m | 
 | 458 |  | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 459 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 460 | class PropertyHandler(RouteHandler): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 461 |     verbs = ['PUT', 'GET'] | 
 | 462 |     rules = '<path:path>/attr/<prop>' | 
| Deepak Kodihalli | 83afbaf | 2017-04-10 06:37:19 -0500 | [diff] [blame] | 463 |     content_type = 'application/json' | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 464 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 465 |     def __init__(self, app, bus): | 
 | 466 |         super(PropertyHandler, self).__init__( | 
| Deepak Kodihalli | 83afbaf | 2017-04-10 06:37:19 -0500 | [diff] [blame] | 467 |             app, bus, self.verbs, self.rules, self.content_type) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 468 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 469 |     def find(self, path, prop): | 
 | 470 |         self.app.instance_handler.setup(path) | 
 | 471 |         obj = self.app.instance_handler.do_get(path) | 
| Brad Bishop | 56ad87f | 2017-02-21 23:33:29 -0500 | [diff] [blame] | 472 |         real_name = obmc.utils.misc.find_case_insensitive( | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 473 |             prop, list(obj.keys())) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 474 |  | 
| Brad Bishop | 56ad87f | 2017-02-21 23:33:29 -0500 | [diff] [blame] | 475 |         if not real_name: | 
 | 476 |             if request.method == 'PUT': | 
 | 477 |                 abort(403, _4034_msg % ('property', 'created', prop)) | 
 | 478 |             else: | 
 | 479 |                 abort(404, _4034_msg % ('property', 'found', prop)) | 
 | 480 |         return real_name, {path: obj} | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 481 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 482 |     def setup(self, path, prop): | 
| Brad Bishop | 56ad87f | 2017-02-21 23:33:29 -0500 | [diff] [blame] | 483 |         name, obj = self.find(path, prop) | 
 | 484 |         request.route_data['obj'] = obj | 
 | 485 |         request.route_data['name'] = name | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 486 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 487 |     def do_get(self, path, prop): | 
| Brad Bishop | 56ad87f | 2017-02-21 23:33:29 -0500 | [diff] [blame] | 488 |         name = request.route_data['name'] | 
 | 489 |         return request.route_data['obj'][path][name] | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 490 |  | 
| Marri Devender Rao | bc0c673 | 2017-11-20 00:15:47 -0600 | [diff] [blame] | 491 |     def do_put(self, path, prop, value=None, retry=True): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 492 |         if value is None: | 
 | 493 |             value = request.parameter_list | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 494 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 495 |         prop, iface, properties_iface = self.get_host_interface( | 
 | 496 |             path, prop, request.route_data['map'][path]) | 
 | 497 |         try: | 
 | 498 |             properties_iface.Set(iface, prop, value) | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 499 |         except ValueError as e: | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 500 |             abort(400, str(e)) | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 501 |         except dbus.exceptions.DBusException as e: | 
| Adriana Kobylak | a8b05d1 | 2018-08-23 10:44:07 -0500 | [diff] [blame] | 502 |             if e.get_dbus_name() == DBUS_PROPERTY_READONLY: | 
 | 503 |                 abort(403, str(e)) | 
| Brad Bishop | b7fca9b | 2018-01-23 12:16:50 -0500 | [diff] [blame] | 504 |             if e.get_dbus_name() == DBUS_INVALID_ARGS and retry: | 
| Leonel Gonzalez | 0bdef95 | 2017-04-18 08:17:49 -0500 | [diff] [blame] | 505 |                 bus_name = properties_iface.bus_name | 
 | 506 |                 expected_type = get_type_signature_by_introspection(self.bus, | 
 | 507 |                                                                     bus_name, | 
 | 508 |                                                                     path, | 
 | 509 |                                                                     prop) | 
 | 510 |                 if not expected_type: | 
 | 511 |                     abort(403, "Failed to get expected type: %s" % str(e)) | 
 | 512 |                 converted_value = None | 
 | 513 |                 try: | 
 | 514 |                     converted_value = convert_type(expected_type, value) | 
| Leonel Gonzalez | 0bdef95 | 2017-04-18 08:17:49 -0500 | [diff] [blame] | 515 |                 except Exception as ex: | 
 | 516 |                     abort(403, "Failed to convert %s to type %s" % | 
 | 517 |                           (value, expected_type)) | 
| Lei YU | 1eea5c3 | 2018-07-12 15:32:37 +0800 | [diff] [blame] | 518 |                 try: | 
 | 519 |                     self.do_put(path, prop, converted_value, False) | 
 | 520 |                     return | 
 | 521 |                 except Exception as ex: | 
 | 522 |                     abort(403, str(ex)) | 
 | 523 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 524 |                 abort(403, str(e)) | 
 | 525 |             raise | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 526 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 527 |     def get_host_interface(self, path, prop, bus_info): | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 528 |         for bus, interfaces in bus_info.items(): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 529 |             obj = self.bus.get_object(bus, path, introspect=True) | 
 | 530 |             properties_iface = dbus.Interface( | 
 | 531 |                 obj, dbus_interface=dbus.PROPERTIES_IFACE) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 532 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 533 |             info = self.get_host_interface_on_bus( | 
 | 534 |                 path, prop, properties_iface, bus, interfaces) | 
 | 535 |             if info is not None: | 
 | 536 |                 prop, iface = info | 
 | 537 |                 return prop, iface, properties_iface | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 538 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 539 |     def get_host_interface_on_bus(self, path, prop, iface, bus, interfaces): | 
 | 540 |         for i in interfaces: | 
 | 541 |             properties = self.try_properties_interface(iface.GetAll, i) | 
| Brad Bishop | 69cb6d1 | 2017-02-21 12:01:52 -0500 | [diff] [blame] | 542 |             if not properties: | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 543 |                 continue | 
| Leonel Gonzalez | 409f671 | 2017-05-24 09:51:55 -0500 | [diff] [blame] | 544 |             match = obmc.utils.misc.find_case_insensitive( | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 545 |                 prop, list(properties.keys())) | 
| Leonel Gonzalez | 409f671 | 2017-05-24 09:51:55 -0500 | [diff] [blame] | 546 |             if match is None: | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 547 |                 continue | 
| Leonel Gonzalez | 409f671 | 2017-05-24 09:51:55 -0500 | [diff] [blame] | 548 |             prop = match | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 549 |             return prop, i | 
 | 550 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 551 |  | 
| Brad Bishop | 2503bd6 | 2015-12-16 17:56:12 -0500 | [diff] [blame] | 552 | class SchemaHandler(RouteHandler): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 553 |     verbs = ['GET'] | 
 | 554 |     rules = '<path:path>/schema' | 
| Deepak Kodihalli | 6e1ca53 | 2018-09-04 03:51:04 -0500 | [diff] [blame] | 555 |     suppress_logging = True | 
| Brad Bishop | 2503bd6 | 2015-12-16 17:56:12 -0500 | [diff] [blame] | 556 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 557 |     def __init__(self, app, bus): | 
 | 558 |         super(SchemaHandler, self).__init__( | 
| Brad Bishop | 529029b | 2017-07-10 16:46:01 -0400 | [diff] [blame] | 559 |             app, bus, self.verbs, self.rules) | 
| Brad Bishop | 2503bd6 | 2015-12-16 17:56:12 -0500 | [diff] [blame] | 560 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 561 |     def find(self, path): | 
 | 562 |         return self.try_mapper_call( | 
 | 563 |             self.mapper.get_object, | 
 | 564 |             path=path) | 
| Brad Bishop | 2503bd6 | 2015-12-16 17:56:12 -0500 | [diff] [blame] | 565 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 566 |     def setup(self, path): | 
 | 567 |         request.route_data['map'] = self.find(path) | 
| Brad Bishop | 2503bd6 | 2015-12-16 17:56:12 -0500 | [diff] [blame] | 568 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 569 |     def do_get(self, path): | 
 | 570 |         schema = {} | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 571 |         for x in request.route_data['map'].keys(): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 572 |             obj = self.bus.get_object(x, path, introspect=False) | 
 | 573 |             iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE) | 
 | 574 |             data = iface.Introspect() | 
 | 575 |             parser = IntrospectionNodeParser( | 
 | 576 |                 ElementTree.fromstring(data)) | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 577 |             for x, y in parser.get_interfaces().items(): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 578 |                 schema[x] = y | 
| Brad Bishop | 2503bd6 | 2015-12-16 17:56:12 -0500 | [diff] [blame] | 579 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 580 |         return schema | 
 | 581 |  | 
| Brad Bishop | 2503bd6 | 2015-12-16 17:56:12 -0500 | [diff] [blame] | 582 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 583 | class InstanceHandler(RouteHandler): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 584 |     verbs = ['GET', 'PUT', 'DELETE'] | 
 | 585 |     rules = '<path:path>' | 
 | 586 |     request_type = dict | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 587 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 588 |     def __init__(self, app, bus): | 
 | 589 |         super(InstanceHandler, self).__init__( | 
| Brad Bishop | 529029b | 2017-07-10 16:46:01 -0400 | [diff] [blame] | 590 |             app, bus, self.verbs, self.rules) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 591 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 592 |     def find(self, path, callback=None): | 
 | 593 |         return {path: self.try_mapper_call( | 
 | 594 |             self.mapper.get_object, | 
 | 595 |             callback, | 
 | 596 |             path=path)} | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 597 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 598 |     def setup(self, path): | 
 | 599 |         callback = None | 
 | 600 |         if request.method == 'PUT': | 
 | 601 |             def callback(e, **kw): | 
 | 602 |                 abort(403, _4034_msg % ('resource', 'created', path)) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 603 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 604 |         if request.route_data.get('map') is None: | 
 | 605 |             request.route_data['map'] = self.find(path, callback) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 606 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 607 |     def do_get(self, path): | 
| Brad Bishop | 71527b4 | 2016-04-01 14:51:14 -0400 | [diff] [blame] | 608 |         return self.mapper.enumerate_object( | 
 | 609 |             path, | 
 | 610 |             mapper_data=request.route_data['map']) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 611 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 612 |     def do_put(self, path): | 
 | 613 |         # make sure all properties exist in the request | 
 | 614 |         obj = set(self.do_get(path).keys()) | 
 | 615 |         req = set(request.parameter_list.keys()) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 616 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 617 |         diff = list(obj.difference(req)) | 
 | 618 |         if diff: | 
 | 619 |             abort(403, _4034_msg % ( | 
 | 620 |                 'resource', 'removed', '%s/attr/%s' % (path, diff[0]))) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 621 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 622 |         diff = list(req.difference(obj)) | 
 | 623 |         if diff: | 
 | 624 |             abort(403, _4034_msg % ( | 
 | 625 |                 'resource', 'created', '%s/attr/%s' % (path, diff[0]))) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 626 |  | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 627 |         for p, v in request.parameter_list.items(): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 628 |             self.app.property_handler.do_put( | 
 | 629 |                 path, p, v) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 630 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 631 |     def do_delete(self, path): | 
| Matt Spinler | b1f6a2c | 2018-05-14 12:25:21 -0500 | [diff] [blame] | 632 |         deleted = False | 
 | 633 |         for bus, interfaces in request.route_data['map'][path].items(): | 
 | 634 |             if self.bus_has_delete(interfaces): | 
 | 635 |                 self.delete_on_bus(path, bus) | 
 | 636 |                 deleted = True | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 637 |  | 
| Matt Spinler | b1f6a2c | 2018-05-14 12:25:21 -0500 | [diff] [blame] | 638 |         #It's OK if some objects didn't have a Delete, but not all | 
 | 639 |         if not deleted: | 
 | 640 |             abort(403, _4034_msg % ('resource', 'removed', path)) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 641 |  | 
| Matt Spinler | b1f6a2c | 2018-05-14 12:25:21 -0500 | [diff] [blame] | 642 |     def bus_has_delete(self, interfaces): | 
 | 643 |         return DELETE_IFACE in interfaces | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 644 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 645 |     def delete_on_bus(self, path, bus): | 
 | 646 |         obj = self.bus.get_object(bus, path, introspect=False) | 
 | 647 |         delete_iface = dbus.Interface( | 
 | 648 |             obj, dbus_interface=DELETE_IFACE) | 
 | 649 |         delete_iface.Delete() | 
 | 650 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 651 |  | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 652 | class SessionHandler(MethodHandler): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 653 |     ''' Handles the /login and /logout routes, manages | 
 | 654 |     server side session store and session cookies.  ''' | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 655 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 656 |     rules = ['/login', '/logout'] | 
 | 657 |     login_str = "User '%s' logged %s" | 
 | 658 |     bad_passwd_str = "Invalid username or password" | 
 | 659 |     no_user_str = "No user logged in" | 
 | 660 |     bad_json_str = "Expecting request format { 'data': " \ | 
 | 661 |         "[<username>, <password>] }, got '%s'" | 
| Alexander Filippov | d08a456 | 2018-03-20 12:02:23 +0300 | [diff] [blame] | 662 |     bmc_not_ready_str = "BMC is not ready (booting)" | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 663 |     _require_auth = None | 
 | 664 |     MAX_SESSIONS = 16 | 
| Alexander Filippov | d08a456 | 2018-03-20 12:02:23 +0300 | [diff] [blame] | 665 |     BMCSTATE_IFACE = 'xyz.openbmc_project.State.BMC' | 
 | 666 |     BMCSTATE_PATH = '/xyz/openbmc_project/state/bmc0' | 
 | 667 |     BMCSTATE_PROPERTY = 'CurrentBMCState' | 
 | 668 |     BMCSTATE_READY = 'xyz.openbmc_project.State.BMC.BMCState.Ready' | 
| Deepak Kodihalli | 6e1ca53 | 2018-09-04 03:51:04 -0500 | [diff] [blame] | 669 |     suppress_json_logging = True | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 670 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 671 |     def __init__(self, app, bus): | 
 | 672 |         super(SessionHandler, self).__init__( | 
 | 673 |             app, bus) | 
 | 674 |         self.hmac_key = os.urandom(128) | 
 | 675 |         self.session_store = [] | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 676 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 677 |     @staticmethod | 
 | 678 |     def authenticate(username, clear): | 
 | 679 |         try: | 
 | 680 |             encoded = spwd.getspnam(username)[1] | 
 | 681 |             return encoded == crypt.crypt(clear, encoded) | 
 | 682 |         except KeyError: | 
 | 683 |             return False | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 684 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 685 |     def invalidate_session(self, session): | 
 | 686 |         try: | 
 | 687 |             self.session_store.remove(session) | 
 | 688 |         except ValueError: | 
 | 689 |             pass | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 690 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 691 |     def new_session(self): | 
 | 692 |         sid = os.urandom(32) | 
 | 693 |         if self.MAX_SESSIONS <= len(self.session_store): | 
 | 694 |             self.session_store.pop() | 
 | 695 |         self.session_store.insert(0, {'sid': sid}) | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 696 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 697 |         return self.session_store[0] | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 698 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 699 |     def get_session(self, sid): | 
 | 700 |         sids = [x['sid'] for x in self.session_store] | 
 | 701 |         try: | 
 | 702 |             return self.session_store[sids.index(sid)] | 
 | 703 |         except ValueError: | 
 | 704 |             return None | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 705 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 706 |     def get_session_from_cookie(self): | 
 | 707 |         return self.get_session( | 
 | 708 |             request.get_cookie( | 
 | 709 |                 'sid', secret=self.hmac_key)) | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 710 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 711 |     def do_post(self, **kw): | 
 | 712 |         if request.path == '/login': | 
 | 713 |             return self.do_login(**kw) | 
 | 714 |         else: | 
 | 715 |             return self.do_logout(**kw) | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 716 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 717 |     def do_logout(self, **kw): | 
 | 718 |         session = self.get_session_from_cookie() | 
 | 719 |         if session is not None: | 
 | 720 |             user = session['user'] | 
 | 721 |             self.invalidate_session(session) | 
 | 722 |             response.delete_cookie('sid') | 
 | 723 |             return self.login_str % (user, 'out') | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 724 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 725 |         return self.no_user_str | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 726 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 727 |     def do_login(self, **kw): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 728 |         if len(request.parameter_list) != 2: | 
 | 729 |             abort(400, self.bad_json_str % (request.json)) | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 730 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 731 |         if not self.authenticate(*request.parameter_list): | 
| Brad Bishop | dc3fbfa | 2016-09-08 09:51:38 -0400 | [diff] [blame] | 732 |             abort(401, self.bad_passwd_str) | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 733 |  | 
| Alexander Filippov | d08a456 | 2018-03-20 12:02:23 +0300 | [diff] [blame] | 734 |         force = False | 
 | 735 |         try: | 
 | 736 |             force = request.json.get('force') | 
 | 737 |         except (ValueError, AttributeError, KeyError, TypeError): | 
 | 738 |             force = False | 
 | 739 |  | 
 | 740 |         if not force and not self.is_bmc_ready(): | 
 | 741 |             abort(503, self.bmc_not_ready_str) | 
 | 742 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 743 |         user = request.parameter_list[0] | 
 | 744 |         session = self.new_session() | 
 | 745 |         session['user'] = user | 
 | 746 |         response.set_cookie( | 
 | 747 |             'sid', session['sid'], secret=self.hmac_key, | 
 | 748 |             secure=True, | 
 | 749 |             httponly=True) | 
 | 750 |         return self.login_str % (user, 'in') | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 751 |  | 
| Alexander Filippov | d08a456 | 2018-03-20 12:02:23 +0300 | [diff] [blame] | 752 |     def is_bmc_ready(self): | 
 | 753 |         if not self.app.with_bmc_check: | 
 | 754 |             return True | 
 | 755 |  | 
 | 756 |         try: | 
 | 757 |             obj = self.bus.get_object(self.BMCSTATE_IFACE, self.BMCSTATE_PATH) | 
 | 758 |             iface = dbus.Interface(obj, dbus.PROPERTIES_IFACE) | 
 | 759 |             state = iface.Get(self.BMCSTATE_IFACE, self.BMCSTATE_PROPERTY) | 
 | 760 |             if state == self.BMCSTATE_READY: | 
 | 761 |                 return True | 
 | 762 |  | 
 | 763 |         except dbus.exceptions.DBusException: | 
 | 764 |             pass | 
 | 765 |  | 
 | 766 |         return False | 
 | 767 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 768 |     def find(self, **kw): | 
 | 769 |         pass | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 770 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 771 |     def setup(self, **kw): | 
 | 772 |         pass | 
 | 773 |  | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 774 |  | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 775 | class ImageUploadUtils: | 
 | 776 |     ''' Provides common utils for image upload. ''' | 
| Deepak Kodihalli | 1af301a | 2017-04-11 07:29:01 -0500 | [diff] [blame] | 777 |  | 
| Deepak Kodihalli | 1af301a | 2017-04-11 07:29:01 -0500 | [diff] [blame] | 778 |     file_loc = '/tmp/images' | 
 | 779 |     file_prefix = 'img' | 
 | 780 |     file_suffix = '' | 
| Adriana Kobylak | 5369389 | 2018-03-12 13:05:50 -0500 | [diff] [blame] | 781 |     signal = None | 
| Deepak Kodihalli | 1af301a | 2017-04-11 07:29:01 -0500 | [diff] [blame] | 782 |  | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 783 |     @classmethod | 
 | 784 |     def do_upload(cls, filename=''): | 
| Adriana Kobylak | 5369389 | 2018-03-12 13:05:50 -0500 | [diff] [blame] | 785 |         def cleanup(): | 
 | 786 |             os.close(handle) | 
 | 787 |             if cls.signal: | 
 | 788 |                 cls.signal.remove() | 
 | 789 |                 cls.signal = None | 
 | 790 |  | 
 | 791 |         def signal_callback(path, a, **kw): | 
 | 792 |             # Just interested on the first Version interface created which is | 
 | 793 |             # triggered when the file is uploaded. This helps avoid getting the | 
 | 794 |             # wrong information for multiple upload requests in a row. | 
 | 795 |             if "xyz.openbmc_project.Software.Version" in a and \ | 
 | 796 |                "xyz.openbmc_project.Software.Activation" not in a: | 
 | 797 |                 paths.append(path) | 
 | 798 |  | 
 | 799 |         while cls.signal: | 
 | 800 |             # Serialize uploads by waiting for the signal to be cleared. | 
 | 801 |             # This makes it easier to ensure that the version information | 
 | 802 |             # is the right one instead of the data from another upload request. | 
 | 803 |             gevent.sleep(1) | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 804 |         if not os.path.exists(cls.file_loc): | 
| Gunnar Mills | fb51579 | 2017-11-09 15:52:17 -0600 | [diff] [blame] | 805 |             abort(500, "Error Directory not found") | 
| Adriana Kobylak | 5369389 | 2018-03-12 13:05:50 -0500 | [diff] [blame] | 806 |         paths = [] | 
 | 807 |         bus = dbus.SystemBus() | 
 | 808 |         cls.signal = bus.add_signal_receiver( | 
 | 809 |             signal_callback, | 
 | 810 |             dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager', | 
 | 811 |             signal_name='InterfacesAdded', | 
 | 812 |             path=SOFTWARE_PATH) | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 813 |         if not filename: | 
 | 814 |             handle, filename = tempfile.mkstemp(cls.file_suffix, | 
 | 815 |                                                 cls.file_prefix, cls.file_loc) | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 816 |         else: | 
 | 817 |             filename = os.path.join(cls.file_loc, filename) | 
| Gunnar Mills | b66b18c | 2017-08-21 16:17:21 -0500 | [diff] [blame] | 818 |             handle = os.open(filename, os.O_WRONLY | os.O_CREAT) | 
| Leonel Gonzalez | 0b62edf | 2017-06-08 15:10:03 -0500 | [diff] [blame] | 819 |         try: | 
 | 820 |             file_contents = request.body.read() | 
 | 821 |             request.body.close() | 
| Gunnar Mills | b66b18c | 2017-08-21 16:17:21 -0500 | [diff] [blame] | 822 |             os.write(handle, file_contents) | 
| Adriana Kobylak | 5369389 | 2018-03-12 13:05:50 -0500 | [diff] [blame] | 823 |             # Close file after writing, the image manager process watches for | 
 | 824 |             # the close event to know the upload is complete. | 
| Gunnar Mills | b66b18c | 2017-08-21 16:17:21 -0500 | [diff] [blame] | 825 |             os.close(handle) | 
| Adriana Kobylak | 5369389 | 2018-03-12 13:05:50 -0500 | [diff] [blame] | 826 |         except (IOError, ValueError) as e: | 
 | 827 |             cleanup() | 
 | 828 |             abort(400, str(e)) | 
 | 829 |         except Exception: | 
 | 830 |             cleanup() | 
 | 831 |             abort(400, "Unexpected Error") | 
 | 832 |         loop = gobject.MainLoop() | 
 | 833 |         gcontext = loop.get_context() | 
 | 834 |         count = 0 | 
 | 835 |         version_id = '' | 
 | 836 |         while loop is not None: | 
 | 837 |             try: | 
 | 838 |                 if gcontext.pending(): | 
 | 839 |                     gcontext.iteration() | 
 | 840 |                 if not paths: | 
 | 841 |                     gevent.sleep(1) | 
 | 842 |                 else: | 
 | 843 |                     version_id = os.path.basename(paths.pop()) | 
 | 844 |                     break | 
 | 845 |                 count += 1 | 
 | 846 |                 if count == 10: | 
 | 847 |                     break | 
 | 848 |             except Exception: | 
 | 849 |                 break | 
 | 850 |         cls.signal.remove() | 
 | 851 |         cls.signal = None | 
| Adriana Kobylak | 97fe435 | 2018-04-10 10:44:11 -0500 | [diff] [blame] | 852 |         if version_id: | 
 | 853 |             return version_id | 
 | 854 |         else: | 
 | 855 |             abort(400, "Version already exists or failed to be extracted") | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 856 |  | 
 | 857 |  | 
 | 858 | class ImagePostHandler(RouteHandler): | 
 | 859 |     ''' Handles the /upload/image route. ''' | 
 | 860 |  | 
 | 861 |     verbs = ['POST'] | 
 | 862 |     rules = ['/upload/image'] | 
 | 863 |     content_type = 'application/octet-stream' | 
 | 864 |  | 
| Deepak Kodihalli | 1af301a | 2017-04-11 07:29:01 -0500 | [diff] [blame] | 865 |     def __init__(self, app, bus): | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 866 |         super(ImagePostHandler, self).__init__( | 
| Deepak Kodihalli | 1af301a | 2017-04-11 07:29:01 -0500 | [diff] [blame] | 867 |             app, bus, self.verbs, self.rules, self.content_type) | 
 | 868 |  | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 869 |     def do_post(self, filename=''): | 
| Adriana Kobylak | 5369389 | 2018-03-12 13:05:50 -0500 | [diff] [blame] | 870 |         return ImageUploadUtils.do_upload() | 
| Deepak Kodihalli | 1af301a | 2017-04-11 07:29:01 -0500 | [diff] [blame] | 871 |  | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 872 |     def find(self, **kw): | 
 | 873 |         pass | 
| Deepak Kodihalli | 1af301a | 2017-04-11 07:29:01 -0500 | [diff] [blame] | 874 |  | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 875 |     def setup(self, **kw): | 
 | 876 |         pass | 
 | 877 |  | 
 | 878 |  | 
| Deepak Kodihalli | 639b502 | 2017-10-13 06:40:26 -0500 | [diff] [blame] | 879 | class EventNotifier: | 
 | 880 |     keyNames = {} | 
 | 881 |     keyNames['event'] = 'event' | 
 | 882 |     keyNames['path'] = 'path' | 
 | 883 |     keyNames['intfMap'] = 'interfaces' | 
 | 884 |     keyNames['propMap'] = 'properties' | 
 | 885 |     keyNames['intf'] = 'interface' | 
 | 886 |  | 
 | 887 |     def __init__(self, wsock, filters): | 
 | 888 |         self.wsock = wsock | 
 | 889 |         self.paths = filters.get("paths", []) | 
 | 890 |         self.interfaces = filters.get("interfaces", []) | 
 | 891 |         if not self.paths: | 
 | 892 |             self.paths.append(None) | 
 | 893 |         bus = dbus.SystemBus() | 
 | 894 |         # Add a signal receiver for every path the client is interested in | 
 | 895 |         for path in self.paths: | 
 | 896 |             bus.add_signal_receiver( | 
 | 897 |                 self.interfaces_added_handler, | 
 | 898 |                 dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager', | 
 | 899 |                 signal_name='InterfacesAdded', | 
 | 900 |                 path=path) | 
 | 901 |             bus.add_signal_receiver( | 
 | 902 |                 self.properties_changed_handler, | 
 | 903 |                 dbus_interface=dbus.PROPERTIES_IFACE, | 
 | 904 |                 signal_name='PropertiesChanged', | 
 | 905 |                 path=path, | 
 | 906 |                 path_keyword='path') | 
 | 907 |         loop = gobject.MainLoop() | 
 | 908 |         # gobject's mainloop.run() will block the entire process, so the gevent | 
 | 909 |         # scheduler and hence greenlets won't execute. The while-loop below | 
 | 910 |         # works around this limitation by using gevent's sleep, instead of | 
 | 911 |         # calling loop.run() | 
 | 912 |         gcontext = loop.get_context() | 
 | 913 |         while loop is not None: | 
 | 914 |             try: | 
 | 915 |                 if gcontext.pending(): | 
 | 916 |                     gcontext.iteration() | 
 | 917 |                 else: | 
 | 918 |                     # gevent.sleep puts only the current greenlet to sleep, | 
 | 919 |                     # not the entire process. | 
 | 920 |                     gevent.sleep(5) | 
 | 921 |             except WebSocketError: | 
 | 922 |                 break | 
 | 923 |  | 
 | 924 |     def interfaces_added_handler(self, path, iprops, **kw): | 
 | 925 |         ''' If the client is interested in these changes, respond to the | 
 | 926 |             client. This handles d-bus interface additions.''' | 
 | 927 |         if (not self.interfaces) or \ | 
 | 928 |            (not set(iprops).isdisjoint(self.interfaces)): | 
 | 929 |             response = {} | 
 | 930 |             response[self.keyNames['event']] = "InterfacesAdded" | 
 | 931 |             response[self.keyNames['path']] = path | 
 | 932 |             response[self.keyNames['intfMap']] = iprops | 
 | 933 |             try: | 
 | 934 |                 self.wsock.send(json.dumps(response)) | 
 | 935 |             except WebSocketError: | 
 | 936 |                 return | 
 | 937 |  | 
 | 938 |     def properties_changed_handler(self, interface, new, old, **kw): | 
 | 939 |         ''' If the client is interested in these changes, respond to the | 
 | 940 |             client. This handles d-bus property changes. ''' | 
 | 941 |         if (not self.interfaces) or (interface in self.interfaces): | 
 | 942 |             path = str(kw['path']) | 
 | 943 |             response = {} | 
 | 944 |             response[self.keyNames['event']] = "PropertiesChanged" | 
 | 945 |             response[self.keyNames['path']] = path | 
 | 946 |             response[self.keyNames['intf']] = interface | 
 | 947 |             response[self.keyNames['propMap']] = new | 
 | 948 |             try: | 
 | 949 |                 self.wsock.send(json.dumps(response)) | 
 | 950 |             except WebSocketError: | 
 | 951 |                 return | 
 | 952 |  | 
 | 953 |  | 
| Deepak Kodihalli | b209dd1 | 2017-10-11 01:19:17 -0500 | [diff] [blame] | 954 | class EventHandler(RouteHandler): | 
 | 955 |     ''' Handles the /subscribe route, for clients to be able | 
 | 956 |         to subscribe to BMC events. ''' | 
 | 957 |  | 
 | 958 |     verbs = ['GET'] | 
 | 959 |     rules = ['/subscribe'] | 
| Deepak Kodihalli | 6e1ca53 | 2018-09-04 03:51:04 -0500 | [diff] [blame] | 960 |     suppress_logging = True | 
| Deepak Kodihalli | b209dd1 | 2017-10-11 01:19:17 -0500 | [diff] [blame] | 961 |  | 
 | 962 |     def __init__(self, app, bus): | 
 | 963 |         super(EventHandler, self).__init__( | 
 | 964 |             app, bus, self.verbs, self.rules) | 
 | 965 |  | 
 | 966 |     def find(self, **kw): | 
 | 967 |         pass | 
 | 968 |  | 
 | 969 |     def setup(self, **kw): | 
 | 970 |         pass | 
 | 971 |  | 
 | 972 |     def do_get(self): | 
 | 973 |         wsock = request.environ.get('wsgi.websocket') | 
 | 974 |         if not wsock: | 
 | 975 |             abort(400, 'Expected WebSocket request.') | 
| Jayashankar Padath | bec10c2 | 2018-05-29 18:22:59 +0530 | [diff] [blame] | 976 |         ping_sender = Greenlet.spawn(send_ws_ping, wsock, WEBSOCKET_TIMEOUT) | 
| Deepak Kodihalli | 639b502 | 2017-10-13 06:40:26 -0500 | [diff] [blame] | 977 |         filters = wsock.receive() | 
 | 978 |         filters = json.loads(filters) | 
 | 979 |         notifier = EventNotifier(wsock, filters) | 
| Deepak Kodihalli | b209dd1 | 2017-10-11 01:19:17 -0500 | [diff] [blame] | 980 |  | 
| Deepak Kodihalli | 5c518f6 | 2018-04-23 03:26:38 -0500 | [diff] [blame] | 981 | class HostConsoleHandler(RouteHandler): | 
 | 982 |     ''' Handles the /console route, for clients to be able | 
 | 983 |         read/write the host serial console. The way this is | 
 | 984 |         done is by exposing a websocket that's mirrored to an | 
 | 985 |         abstract UNIX domain socket, which is the source for | 
 | 986 |         the console data. ''' | 
 | 987 |  | 
 | 988 |     verbs = ['GET'] | 
 | 989 |     # Naming the route console0, because the numbering will help | 
 | 990 |     # on multi-bmc/multi-host systems. | 
 | 991 |     rules = ['/console0'] | 
| Deepak Kodihalli | 6e1ca53 | 2018-09-04 03:51:04 -0500 | [diff] [blame] | 992 |     suppress_logging = True | 
| Deepak Kodihalli | 5c518f6 | 2018-04-23 03:26:38 -0500 | [diff] [blame] | 993 |  | 
 | 994 |     def __init__(self, app, bus): | 
 | 995 |         super(HostConsoleHandler, self).__init__( | 
 | 996 |             app, bus, self.verbs, self.rules) | 
 | 997 |  | 
 | 998 |     def find(self, **kw): | 
 | 999 |         pass | 
 | 1000 |  | 
 | 1001 |     def setup(self, **kw): | 
 | 1002 |         pass | 
 | 1003 |  | 
 | 1004 |     def read_wsock(self, wsock, sock): | 
 | 1005 |         while True: | 
 | 1006 |             try: | 
 | 1007 |                 incoming = wsock.receive() | 
 | 1008 |                 if incoming: | 
 | 1009 |                     # Read websocket, write to UNIX socket | 
 | 1010 |                     sock.send(incoming) | 
 | 1011 |             except Exception as e: | 
 | 1012 |                 sock.close() | 
 | 1013 |                 return | 
 | 1014 |  | 
 | 1015 |     def read_sock(self, sock, wsock): | 
 | 1016 |         max_sock_read_len = 4096 | 
 | 1017 |         while True: | 
 | 1018 |             try: | 
 | 1019 |                 outgoing = sock.recv(max_sock_read_len) | 
 | 1020 |                 if outgoing: | 
 | 1021 |                     # Read UNIX socket, write to websocket | 
 | 1022 |                     wsock.send(outgoing) | 
 | 1023 |             except Exception as e: | 
 | 1024 |                 wsock.close() | 
 | 1025 |                 return | 
 | 1026 |  | 
| Deepak Kodihalli | 5c518f6 | 2018-04-23 03:26:38 -0500 | [diff] [blame] | 1027 |     def do_get(self): | 
 | 1028 |         wsock = request.environ.get('wsgi.websocket') | 
 | 1029 |         if not wsock: | 
 | 1030 |             abort(400, 'Expected WebSocket based request.') | 
 | 1031 |  | 
 | 1032 |         # A UNIX domain socket structure defines a 108-byte pathname. The | 
 | 1033 |         # server in this case, obmc-console-server, expects a 108-byte path. | 
 | 1034 |         socket_name = "\0obmc-console" | 
 | 1035 |         trailing_bytes = "\0" * (108 - len(socket_name)) | 
 | 1036 |         socket_path = socket_name + trailing_bytes | 
 | 1037 |         sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) | 
 | 1038 |  | 
 | 1039 |         try: | 
 | 1040 |             sock.connect(socket_path) | 
 | 1041 |         except Exception as e: | 
 | 1042 |             abort(500, str(e)) | 
 | 1043 |  | 
 | 1044 |         wsock_reader = Greenlet.spawn(self.read_wsock, wsock, sock) | 
 | 1045 |         sock_reader = Greenlet.spawn(self.read_sock, sock, wsock) | 
| Jayashankar Padath | bec10c2 | 2018-05-29 18:22:59 +0530 | [diff] [blame] | 1046 |         ping_sender = Greenlet.spawn(send_ws_ping, wsock, WEBSOCKET_TIMEOUT) | 
| Deepak Kodihalli | 5c518f6 | 2018-04-23 03:26:38 -0500 | [diff] [blame] | 1047 |         gevent.joinall([wsock_reader, sock_reader, ping_sender]) | 
 | 1048 |  | 
 | 1049 |  | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 1050 | class ImagePutHandler(RouteHandler): | 
 | 1051 |     ''' Handles the /upload/image/<filename> route. ''' | 
 | 1052 |  | 
 | 1053 |     verbs = ['PUT'] | 
 | 1054 |     rules = ['/upload/image/<filename>'] | 
 | 1055 |     content_type = 'application/octet-stream' | 
 | 1056 |  | 
 | 1057 |     def __init__(self, app, bus): | 
 | 1058 |         super(ImagePutHandler, self).__init__( | 
 | 1059 |             app, bus, self.verbs, self.rules, self.content_type) | 
 | 1060 |  | 
 | 1061 |     def do_put(self, filename=''): | 
| Adriana Kobylak | 5369389 | 2018-03-12 13:05:50 -0500 | [diff] [blame] | 1062 |         return ImageUploadUtils.do_upload(filename) | 
| Deepak Kodihalli | 1af301a | 2017-04-11 07:29:01 -0500 | [diff] [blame] | 1063 |  | 
 | 1064 |     def find(self, **kw): | 
 | 1065 |         pass | 
 | 1066 |  | 
 | 1067 |     def setup(self, **kw): | 
 | 1068 |         pass | 
 | 1069 |  | 
 | 1070 |  | 
| Jayanth Othayoth | 9bc9499 | 2017-06-29 06:30:40 -0500 | [diff] [blame] | 1071 | class DownloadDumpHandler(RouteHandler): | 
 | 1072 |     ''' Handles the /download/dump route. ''' | 
 | 1073 |  | 
 | 1074 |     verbs = 'GET' | 
 | 1075 |     rules = ['/download/dump/<dumpid>'] | 
 | 1076 |     content_type = 'application/octet-stream' | 
| Jayanth Othayoth | 18c3a24 | 2017-08-02 08:16:11 -0500 | [diff] [blame] | 1077 |     dump_loc = '/var/lib/phosphor-debug-collector/dumps' | 
| Brad Bishop | 944cd04 | 2017-07-10 16:42:41 -0400 | [diff] [blame] | 1078 |     suppress_json_resp = True | 
| Deepak Kodihalli | 6e1ca53 | 2018-09-04 03:51:04 -0500 | [diff] [blame] | 1079 |     suppress_logging = True | 
| Jayanth Othayoth | 9bc9499 | 2017-06-29 06:30:40 -0500 | [diff] [blame] | 1080 |  | 
 | 1081 |     def __init__(self, app, bus): | 
 | 1082 |         super(DownloadDumpHandler, self).__init__( | 
 | 1083 |             app, bus, self.verbs, self.rules, self.content_type) | 
 | 1084 |  | 
 | 1085 |     def do_get(self, dumpid): | 
 | 1086 |         return self.do_download(dumpid) | 
 | 1087 |  | 
 | 1088 |     def find(self, **kw): | 
 | 1089 |         pass | 
 | 1090 |  | 
 | 1091 |     def setup(self, **kw): | 
 | 1092 |         pass | 
 | 1093 |  | 
 | 1094 |     def do_download(self, dumpid): | 
 | 1095 |         dump_loc = os.path.join(self.dump_loc, dumpid) | 
 | 1096 |         if not os.path.exists(dump_loc): | 
 | 1097 |             abort(404, "Path not found") | 
 | 1098 |  | 
 | 1099 |         files = os.listdir(dump_loc) | 
 | 1100 |         num_files = len(files) | 
 | 1101 |         if num_files == 0: | 
 | 1102 |             abort(404, "Dump not found") | 
 | 1103 |  | 
 | 1104 |         return static_file(os.path.basename(files[0]), root=dump_loc, | 
 | 1105 |                            download=True, mimetype=self.content_type) | 
 | 1106 |  | 
 | 1107 |  | 
| Matt Spinler | d41643e | 2018-02-02 13:51:38 -0600 | [diff] [blame] | 1108 | class WebHandler(RouteHandler): | 
 | 1109 |     ''' Handles the routes for the web UI files. ''' | 
 | 1110 |  | 
 | 1111 |     verbs = 'GET' | 
 | 1112 |  | 
 | 1113 |     # Match only what we know are web files, so everything else | 
 | 1114 |     # can get routed to the REST handlers. | 
 | 1115 |     rules = ['//', '/<filename:re:.+\.js>', '/<filename:re:.+\.svg>', | 
 | 1116 |              '/<filename:re:.+\.css>', '/<filename:re:.+\.ttf>', | 
 | 1117 |              '/<filename:re:.+\.eot>', '/<filename:re:.+\.woff>', | 
 | 1118 |              '/<filename:re:.+\.woff2>', '/<filename:re:.+\.map>', | 
 | 1119 |              '/<filename:re:.+\.png>', '/<filename:re:.+\.html>', | 
 | 1120 |              '/<filename:re:.+\.ico>'] | 
 | 1121 |  | 
 | 1122 |     # The mimetypes module knows about most types, but not these | 
 | 1123 |     content_types = { | 
 | 1124 |         '.eot': 'application/vnd.ms-fontobject', | 
 | 1125 |         '.woff': 'application/x-font-woff', | 
 | 1126 |         '.woff2': 'application/x-font-woff2', | 
 | 1127 |         '.ttf': 'application/x-font-ttf', | 
 | 1128 |         '.map': 'application/json' | 
 | 1129 |     } | 
 | 1130 |  | 
 | 1131 |     _require_auth = None | 
 | 1132 |     suppress_json_resp = True | 
| Deepak Kodihalli | 6e1ca53 | 2018-09-04 03:51:04 -0500 | [diff] [blame] | 1133 |     suppress_logging = True | 
| Matt Spinler | d41643e | 2018-02-02 13:51:38 -0600 | [diff] [blame] | 1134 |  | 
 | 1135 |     def __init__(self, app, bus): | 
 | 1136 |         super(WebHandler, self).__init__( | 
 | 1137 |             app, bus, self.verbs, self.rules) | 
 | 1138 |  | 
 | 1139 |     def get_type(self, filename): | 
 | 1140 |         ''' Returns the content type and encoding for a file ''' | 
 | 1141 |  | 
 | 1142 |         content_type, encoding = mimetypes.guess_type(filename) | 
 | 1143 |  | 
 | 1144 |         # Try our own list if mimetypes didn't recognize it | 
 | 1145 |         if content_type is None: | 
 | 1146 |             if filename[-3:] == '.gz': | 
 | 1147 |                 filename = filename[:-3] | 
 | 1148 |             extension = filename[filename.rfind('.'):] | 
 | 1149 |             content_type = self.content_types.get(extension, None) | 
 | 1150 |  | 
 | 1151 |         return content_type, encoding | 
 | 1152 |  | 
 | 1153 |     def do_get(self, filename='index.html'): | 
 | 1154 |  | 
 | 1155 |         # If a gzipped version exists, use that instead. | 
 | 1156 |         # Possible future enhancement: if the client doesn't | 
 | 1157 |         # accept compressed files, unzip it ourselves before sending. | 
 | 1158 |         if not os.path.exists(os.path.join(www_base_path, filename)): | 
 | 1159 |             filename = filename + '.gz' | 
 | 1160 |  | 
 | 1161 |         # Though bottle should protect us, ensure path is valid | 
 | 1162 |         realpath = os.path.realpath(filename) | 
 | 1163 |         if realpath[0] == '/': | 
 | 1164 |             realpath = realpath[1:] | 
 | 1165 |         if not os.path.exists(os.path.join(www_base_path, realpath)): | 
 | 1166 |             abort(404, "Path not found") | 
 | 1167 |  | 
 | 1168 |         mimetype, encoding = self.get_type(filename) | 
 | 1169 |  | 
 | 1170 |         # Couldn't find the type - let static_file() deal with it, | 
 | 1171 |         # though this should never happen. | 
 | 1172 |         if mimetype is None: | 
 | 1173 |             print("Can't figure out content-type for %s" % filename) | 
 | 1174 |             mimetype = 'auto' | 
 | 1175 |  | 
 | 1176 |         # This call will set several header fields for us, | 
 | 1177 |         # including the charset if the type is text. | 
 | 1178 |         response = static_file(filename, www_base_path, mimetype) | 
 | 1179 |  | 
 | 1180 |         # static_file() will only set the encoding if the | 
 | 1181 |         # mimetype was auto, so set it here. | 
 | 1182 |         if encoding is not None: | 
 | 1183 |             response.set_header('Content-Encoding', encoding) | 
 | 1184 |  | 
 | 1185 |         return response | 
 | 1186 |  | 
 | 1187 |     def find(self, **kw): | 
 | 1188 |         pass | 
 | 1189 |  | 
 | 1190 |     def setup(self, **kw): | 
 | 1191 |         pass | 
 | 1192 |  | 
 | 1193 |  | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 1194 | class AuthorizationPlugin(object): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1195 |     ''' Invokes an optional list of authorization callbacks. ''' | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 1196 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1197 |     name = 'authorization' | 
 | 1198 |     api = 2 | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 1199 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1200 |     class Compose: | 
 | 1201 |         def __init__(self, validators, callback, session_mgr): | 
 | 1202 |             self.validators = validators | 
 | 1203 |             self.callback = callback | 
 | 1204 |             self.session_mgr = session_mgr | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 1205 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1206 |         def __call__(self, *a, **kw): | 
 | 1207 |             sid = request.get_cookie('sid', secret=self.session_mgr.hmac_key) | 
 | 1208 |             session = self.session_mgr.get_session(sid) | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 1209 |             if request.method != 'OPTIONS': | 
 | 1210 |                 for x in self.validators: | 
 | 1211 |                     x(session, *a, **kw) | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 1212 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1213 |             return self.callback(*a, **kw) | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 1214 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1215 |     def apply(self, callback, route): | 
 | 1216 |         undecorated = route.get_undecorated_callback() | 
 | 1217 |         if not isinstance(undecorated, RouteHandler): | 
 | 1218 |             return callback | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 1219 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1220 |         auth_types = getattr( | 
 | 1221 |             undecorated, '_require_auth', None) | 
 | 1222 |         if not auth_types: | 
 | 1223 |             return callback | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 1224 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1225 |         return self.Compose( | 
 | 1226 |             auth_types, callback, undecorated.app.session_handler) | 
 | 1227 |  | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 1228 |  | 
| Brad Bishop | d0c404a | 2017-02-21 09:23:25 -0500 | [diff] [blame] | 1229 | class CorsPlugin(object): | 
 | 1230 |     ''' Add CORS headers. ''' | 
 | 1231 |  | 
 | 1232 |     name = 'cors' | 
 | 1233 |     api = 2 | 
 | 1234 |  | 
 | 1235 |     @staticmethod | 
 | 1236 |     def process_origin(): | 
 | 1237 |         origin = request.headers.get('Origin') | 
 | 1238 |         if origin: | 
 | 1239 |             response.add_header('Access-Control-Allow-Origin', origin) | 
 | 1240 |             response.add_header( | 
 | 1241 |                 'Access-Control-Allow-Credentials', 'true') | 
 | 1242 |  | 
 | 1243 |     @staticmethod | 
 | 1244 |     def process_method_and_headers(verbs): | 
 | 1245 |         method = request.headers.get('Access-Control-Request-Method') | 
 | 1246 |         headers = request.headers.get('Access-Control-Request-Headers') | 
 | 1247 |         if headers: | 
 | 1248 |             headers = [x.lower() for x in headers.split(',')] | 
 | 1249 |  | 
 | 1250 |         if method in verbs \ | 
 | 1251 |                 and headers == ['content-type']: | 
 | 1252 |             response.add_header('Access-Control-Allow-Methods', method) | 
 | 1253 |             response.add_header( | 
 | 1254 |                 'Access-Control-Allow-Headers', 'Content-Type') | 
| Ratan Gupta | 91b46f8 | 2018-01-14 12:52:23 +0530 | [diff] [blame] | 1255 |             response.add_header('X-Frame-Options', 'deny') | 
 | 1256 |             response.add_header('X-Content-Type-Options', 'nosniff') | 
 | 1257 |             response.add_header('X-XSS-Protection', '1; mode=block') | 
 | 1258 |             response.add_header( | 
 | 1259 |                 'Content-Security-Policy', "default-src 'self'") | 
 | 1260 |             response.add_header( | 
 | 1261 |                 'Strict-Transport-Security', | 
 | 1262 |                 'max-age=31536000; includeSubDomains; preload') | 
| Brad Bishop | d0c404a | 2017-02-21 09:23:25 -0500 | [diff] [blame] | 1263 |  | 
 | 1264 |     def __init__(self, app): | 
 | 1265 |         app.install_error_callback(self.error_callback) | 
 | 1266 |  | 
 | 1267 |     def apply(self, callback, route): | 
 | 1268 |         undecorated = route.get_undecorated_callback() | 
 | 1269 |         if not isinstance(undecorated, RouteHandler): | 
 | 1270 |             return callback | 
 | 1271 |  | 
 | 1272 |         if not getattr(undecorated, '_enable_cors', None): | 
 | 1273 |             return callback | 
 | 1274 |  | 
 | 1275 |         def wrap(*a, **kw): | 
 | 1276 |             self.process_origin() | 
 | 1277 |             self.process_method_and_headers(undecorated._verbs) | 
 | 1278 |             return callback(*a, **kw) | 
 | 1279 |  | 
 | 1280 |         return wrap | 
 | 1281 |  | 
 | 1282 |     def error_callback(self, **kw): | 
 | 1283 |         self.process_origin() | 
 | 1284 |  | 
 | 1285 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1286 | class JsonApiRequestPlugin(object): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1287 |     ''' Ensures request content satisfies the OpenBMC json api format. ''' | 
 | 1288 |     name = 'json_api_request' | 
 | 1289 |     api = 2 | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1290 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1291 |     error_str = "Expecting request format { 'data': <value> }, got '%s'" | 
 | 1292 |     type_error_str = "Unsupported Content-Type: '%s'" | 
 | 1293 |     json_type = "application/json" | 
 | 1294 |     request_methods = ['PUT', 'POST', 'PATCH'] | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1295 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1296 |     @staticmethod | 
 | 1297 |     def content_expected(): | 
 | 1298 |         return request.method in JsonApiRequestPlugin.request_methods | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1299 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1300 |     def validate_request(self): | 
 | 1301 |         if request.content_length > 0 and \ | 
 | 1302 |                 request.content_type != self.json_type: | 
 | 1303 |             abort(415, self.type_error_str % request.content_type) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1304 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1305 |         try: | 
 | 1306 |             request.parameter_list = request.json.get('data') | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 1307 |         except ValueError as e: | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1308 |             abort(400, str(e)) | 
 | 1309 |         except (AttributeError, KeyError, TypeError): | 
 | 1310 |             abort(400, self.error_str % request.json) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1311 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1312 |     def apply(self, callback, route): | 
| Deepak Kodihalli | fb6cd48 | 2017-04-10 07:27:09 -0500 | [diff] [blame] | 1313 |         content_type = getattr( | 
 | 1314 |             route.get_undecorated_callback(), '_content_type', None) | 
 | 1315 |         if self.json_type != content_type: | 
 | 1316 |             return callback | 
 | 1317 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1318 |         verbs = getattr( | 
 | 1319 |             route.get_undecorated_callback(), '_verbs', None) | 
 | 1320 |         if verbs is None: | 
 | 1321 |             return callback | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1322 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1323 |         if not set(self.request_methods).intersection(verbs): | 
 | 1324 |             return callback | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1325 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1326 |         def wrap(*a, **kw): | 
 | 1327 |             if self.content_expected(): | 
 | 1328 |                 self.validate_request() | 
 | 1329 |             return callback(*a, **kw) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1330 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1331 |         return wrap | 
 | 1332 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1333 |  | 
 | 1334 | class JsonApiRequestTypePlugin(object): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1335 |     ''' Ensures request content type satisfies the OpenBMC json api format. ''' | 
 | 1336 |     name = 'json_api_method_request' | 
 | 1337 |     api = 2 | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1338 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1339 |     error_str = "Expecting request format { 'data': %s }, got '%s'" | 
| Deepak Kodihalli | fb6cd48 | 2017-04-10 07:27:09 -0500 | [diff] [blame] | 1340 |     json_type = "application/json" | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1341 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1342 |     def apply(self, callback, route): | 
| Deepak Kodihalli | fb6cd48 | 2017-04-10 07:27:09 -0500 | [diff] [blame] | 1343 |         content_type = getattr( | 
 | 1344 |             route.get_undecorated_callback(), '_content_type', None) | 
 | 1345 |         if self.json_type != content_type: | 
 | 1346 |             return callback | 
 | 1347 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1348 |         request_type = getattr( | 
 | 1349 |             route.get_undecorated_callback(), 'request_type', None) | 
 | 1350 |         if request_type is None: | 
 | 1351 |             return callback | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1352 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1353 |         def validate_request(): | 
 | 1354 |             if not isinstance(request.parameter_list, request_type): | 
 | 1355 |                 abort(400, self.error_str % (str(request_type), request.json)) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1356 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1357 |         def wrap(*a, **kw): | 
 | 1358 |             if JsonApiRequestPlugin.content_expected(): | 
 | 1359 |                 validate_request() | 
 | 1360 |             return callback(*a, **kw) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1361 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1362 |         return wrap | 
 | 1363 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1364 |  | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 1365 | class JsonErrorsPlugin(JSONPlugin): | 
 | 1366 |     ''' Extend the Bottle JSONPlugin such that it also encodes error | 
 | 1367 |         responses. ''' | 
 | 1368 |  | 
 | 1369 |     def __init__(self, app, **kw): | 
 | 1370 |         super(JsonErrorsPlugin, self).__init__(**kw) | 
 | 1371 |         self.json_opts = { | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 1372 |             x: y for x, y in kw.items() | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 1373 |             if x in ['indent', 'sort_keys']} | 
 | 1374 |         app.install_error_callback(self.error_callback) | 
 | 1375 |  | 
 | 1376 |     def error_callback(self, response_object, response_body, **kw): | 
 | 1377 |         response_body['body'] = json.dumps(response_object, **self.json_opts) | 
 | 1378 |         response.content_type = 'application/json' | 
 | 1379 |  | 
 | 1380 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1381 | class JsonApiResponsePlugin(object): | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 1382 |     ''' Emits responses in the OpenBMC json api format. ''' | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1383 |     name = 'json_api_response' | 
 | 1384 |     api = 2 | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1385 |  | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 1386 |     @staticmethod | 
 | 1387 |     def has_body(): | 
 | 1388 |         return request.method not in ['OPTIONS'] | 
 | 1389 |  | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 1390 |     def __init__(self, app): | 
 | 1391 |         app.install_error_callback(self.error_callback) | 
 | 1392 |  | 
| Matt Spinler | 6691e7c | 2018-06-25 14:11:58 -0500 | [diff] [blame] | 1393 |     @staticmethod | 
 | 1394 |     def dbus_boolean_to_bool(data): | 
 | 1395 |         ''' Convert all dbus.Booleans to true/false instead of 1/0 as | 
 | 1396 |             the JSON encoder thinks they're ints.  Note that unlike | 
 | 1397 |             dicts and lists, tuples (from a dbus.Struct) are immutable | 
 | 1398 |             so they need special handling. ''' | 
 | 1399 |  | 
 | 1400 |         def walkdict(data): | 
 | 1401 |             for key, value in data.items(): | 
 | 1402 |                 if isinstance(value, dbus.Boolean): | 
 | 1403 |                     data[key] = bool(value) | 
 | 1404 |                 elif isinstance(value, tuple): | 
 | 1405 |                     data[key] = walktuple(value) | 
 | 1406 |                 else: | 
 | 1407 |                     JsonApiResponsePlugin.dbus_boolean_to_bool(value) | 
 | 1408 |  | 
 | 1409 |         def walklist(data): | 
 | 1410 |             for i in range(len(data)): | 
 | 1411 |                 if isinstance(data[i], dbus.Boolean): | 
 | 1412 |                     data[i] = bool(data[i]) | 
 | 1413 |                 elif isinstance(data[i], tuple): | 
 | 1414 |                     data[i] = walktuple(data[i]) | 
 | 1415 |                 else: | 
 | 1416 |                     JsonApiResponsePlugin.dbus_boolean_to_bool(data[i]) | 
 | 1417 |  | 
 | 1418 |         def walktuple(data): | 
 | 1419 |             new = [] | 
 | 1420 |             for item in data: | 
 | 1421 |                 if isinstance(item, dbus.Boolean): | 
 | 1422 |                     item = bool(item) | 
 | 1423 |                 else: | 
 | 1424 |                     JsonApiResponsePlugin.dbus_boolean_to_bool(item) | 
 | 1425 |                 new.append(item) | 
 | 1426 |             return tuple(new) | 
 | 1427 |  | 
 | 1428 |         if isinstance(data, dict): | 
 | 1429 |             walkdict(data) | 
 | 1430 |         elif isinstance(data, list): | 
 | 1431 |             walklist(data) | 
 | 1432 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1433 |     def apply(self, callback, route): | 
| Brad Bishop | 944cd04 | 2017-07-10 16:42:41 -0400 | [diff] [blame] | 1434 |         skip = getattr( | 
 | 1435 |             route.get_undecorated_callback(), 'suppress_json_resp', None) | 
 | 1436 |         if skip: | 
| Jayanth Othayoth | 1444fd8 | 2017-06-29 05:45:07 -0500 | [diff] [blame] | 1437 |             return callback | 
 | 1438 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1439 |         def wrap(*a, **kw): | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 1440 |             data = callback(*a, **kw) | 
| Matt Spinler | 6691e7c | 2018-06-25 14:11:58 -0500 | [diff] [blame] | 1441 |             JsonApiResponsePlugin.dbus_boolean_to_bool(data) | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 1442 |             if self.has_body(): | 
 | 1443 |                 resp = {'data': data} | 
 | 1444 |                 resp['status'] = 'ok' | 
 | 1445 |                 resp['message'] = response.status_line | 
 | 1446 |                 return resp | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1447 |         return wrap | 
 | 1448 |  | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 1449 |     def error_callback(self, error, response_object, **kw): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1450 |         response_object['message'] = error.status_line | 
| Brad Bishop | 9c2531e | 2017-03-07 10:22:40 -0500 | [diff] [blame] | 1451 |         response_object['status'] = 'error' | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 1452 |         response_object.setdefault('data', {})['description'] = str(error.body) | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1453 |         if error.status_code == 500: | 
 | 1454 |             response_object['data']['exception'] = repr(error.exception) | 
 | 1455 |             response_object['data']['traceback'] = error.traceback.splitlines() | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1456 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1457 |  | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 1458 | class JsonpPlugin(object): | 
| Brad Bishop | 80fe37a | 2016-03-29 10:54:54 -0400 | [diff] [blame] | 1459 |     ''' Json javascript wrapper. ''' | 
 | 1460 |     name = 'jsonp' | 
 | 1461 |     api = 2 | 
 | 1462 |  | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 1463 |     def __init__(self, app, **kw): | 
 | 1464 |         app.install_error_callback(self.error_callback) | 
| Brad Bishop | 80fe37a | 2016-03-29 10:54:54 -0400 | [diff] [blame] | 1465 |  | 
 | 1466 |     @staticmethod | 
 | 1467 |     def to_jsonp(json): | 
 | 1468 |         jwrapper = request.query.callback or None | 
 | 1469 |         if(jwrapper): | 
 | 1470 |             response.set_header('Content-Type', 'application/javascript') | 
 | 1471 |             json = jwrapper + '(' + json + ');' | 
 | 1472 |         return json | 
 | 1473 |  | 
 | 1474 |     def apply(self, callback, route): | 
 | 1475 |         def wrap(*a, **kw): | 
 | 1476 |             return self.to_jsonp(callback(*a, **kw)) | 
 | 1477 |         return wrap | 
 | 1478 |  | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 1479 |     def error_callback(self, response_body, **kw): | 
 | 1480 |         response_body['body'] = self.to_jsonp(response_body['body']) | 
| Brad Bishop | 80fe37a | 2016-03-29 10:54:54 -0400 | [diff] [blame] | 1481 |  | 
 | 1482 |  | 
| Deepak Kodihalli | 461367a | 2017-04-10 07:11:38 -0500 | [diff] [blame] | 1483 | class ContentCheckerPlugin(object): | 
 | 1484 |     ''' Ensures that a route is associated with the expected content-type | 
 | 1485 |         header. ''' | 
 | 1486 |     name = 'content_checker' | 
 | 1487 |     api = 2 | 
 | 1488 |  | 
 | 1489 |     class Checker: | 
 | 1490 |         def __init__(self, type, callback): | 
 | 1491 |             self.expected_type = type | 
 | 1492 |             self.callback = callback | 
 | 1493 |             self.error_str = "Expecting content type '%s', got '%s'" | 
 | 1494 |  | 
 | 1495 |         def __call__(self, *a, **kw): | 
| Deepak Kodihalli | db1a21e | 2017-04-27 06:30:11 -0500 | [diff] [blame] | 1496 |             if request.method in ['PUT', 'POST', 'PATCH'] and \ | 
 | 1497 |                     self.expected_type and \ | 
| Deepak Kodihalli | 461367a | 2017-04-10 07:11:38 -0500 | [diff] [blame] | 1498 |                     self.expected_type != request.content_type: | 
 | 1499 |                 abort(415, self.error_str % (self.expected_type, | 
 | 1500 |                       request.content_type)) | 
 | 1501 |  | 
 | 1502 |             return self.callback(*a, **kw) | 
 | 1503 |  | 
 | 1504 |     def apply(self, callback, route): | 
 | 1505 |         content_type = getattr( | 
 | 1506 |             route.get_undecorated_callback(), '_content_type', None) | 
 | 1507 |  | 
 | 1508 |         return self.Checker(content_type, callback) | 
 | 1509 |  | 
 | 1510 |  | 
| Deepak Kodihalli | 6e1ca53 | 2018-09-04 03:51:04 -0500 | [diff] [blame] | 1511 | class LoggingPlugin(object): | 
 | 1512 |     ''' Wraps a request in order to emit a log after the request is handled. ''' | 
 | 1513 |     name = 'loggingp' | 
 | 1514 |     api = 2 | 
 | 1515 |  | 
 | 1516 |     class Logger: | 
 | 1517 |         def __init__(self, suppress_json_logging, callback, app): | 
 | 1518 |             self.suppress_json_logging = suppress_json_logging | 
 | 1519 |             self.callback = callback | 
 | 1520 |             self.app = app | 
| Deepak Kodihalli | 9580368 | 2018-09-07 03:13:59 -0500 | [diff] [blame] | 1521 |             self.logging_enabled = None | 
 | 1522 |             self.bus = dbus.SystemBus() | 
 | 1523 |             self.dbus_path = '/xyz/openbmc_project/logging/rest_api_logs' | 
 | 1524 |             self.bus.add_signal_receiver( | 
 | 1525 |                 self.properties_changed_handler, | 
 | 1526 |                 dbus_interface=dbus.PROPERTIES_IFACE, | 
 | 1527 |                 signal_name='PropertiesChanged', | 
 | 1528 |                 path=self.dbus_path) | 
 | 1529 |             Greenlet.spawn(self.dbus_loop) | 
| Deepak Kodihalli | 6e1ca53 | 2018-09-04 03:51:04 -0500 | [diff] [blame] | 1530 |  | 
 | 1531 |         def __call__(self, *a, **kw): | 
 | 1532 |             resp = self.callback(*a, **kw) | 
| Deepak Kodihalli | 9580368 | 2018-09-07 03:13:59 -0500 | [diff] [blame] | 1533 |             if not self.enabled(): | 
| Deepak Kodihalli | 4aa1000 | 2018-09-13 11:48:45 -0500 | [diff] [blame] | 1534 |                 return resp | 
| Deepak Kodihalli | 6e1ca53 | 2018-09-04 03:51:04 -0500 | [diff] [blame] | 1535 |             if request.method == 'GET': | 
| Deepak Kodihalli | 4aa1000 | 2018-09-13 11:48:45 -0500 | [diff] [blame] | 1536 |                 return resp | 
| Deepak Kodihalli | 6e1ca53 | 2018-09-04 03:51:04 -0500 | [diff] [blame] | 1537 |             json = request.json | 
 | 1538 |             if self.suppress_json_logging: | 
 | 1539 |                 json = None | 
 | 1540 |             session = self.app.session_handler.get_session_from_cookie() | 
 | 1541 |             user = None | 
 | 1542 |             if "/login" in request.url: | 
 | 1543 |                 user = request.parameter_list[0] | 
 | 1544 |             elif session is not None: | 
 | 1545 |                 user = session['user'] | 
 | 1546 |             print("{remote} user:{user} {method} {url} json:{json} {status}" \ | 
 | 1547 |                 .format( | 
 | 1548 |                     user=user, | 
 | 1549 |                     remote=request.remote_addr, | 
 | 1550 |                     method=request.method, | 
 | 1551 |                     url=request.url, | 
 | 1552 |                     json=json, | 
 | 1553 |                     status=response.status)) | 
| Deepak Kodihalli | 4aa1000 | 2018-09-13 11:48:45 -0500 | [diff] [blame] | 1554 |             return resp | 
| Deepak Kodihalli | 6e1ca53 | 2018-09-04 03:51:04 -0500 | [diff] [blame] | 1555 |  | 
| Deepak Kodihalli | 9580368 | 2018-09-07 03:13:59 -0500 | [diff] [blame] | 1556 |         def enabled(self): | 
 | 1557 |             if self.logging_enabled is None: | 
 | 1558 |                 try: | 
 | 1559 |                     obj = self.bus.get_object( | 
 | 1560 |                               'xyz.openbmc_project.Settings', | 
 | 1561 |                               self.dbus_path) | 
 | 1562 |                     iface = dbus.Interface(obj, dbus.PROPERTIES_IFACE) | 
 | 1563 |                     logging_enabled = iface.Get( | 
 | 1564 |                                           'xyz.openbmc_project.Object.Enable', | 
 | 1565 |                                           'Enabled') | 
 | 1566 |                     self.logging_enabled = logging_enabled | 
 | 1567 |                 except dbus.exceptions.DBusException: | 
 | 1568 |                     self.logging_enabled = False | 
 | 1569 |             return self.logging_enabled | 
 | 1570 |  | 
 | 1571 |         def dbus_loop(self): | 
 | 1572 |             loop = gobject.MainLoop() | 
 | 1573 |             gcontext = loop.get_context() | 
 | 1574 |             while loop is not None: | 
 | 1575 |                 try: | 
 | 1576 |                     if gcontext.pending(): | 
 | 1577 |                         gcontext.iteration() | 
 | 1578 |                     else: | 
 | 1579 |                         gevent.sleep(5) | 
 | 1580 |                 except Exception as e: | 
 | 1581 |                     break | 
 | 1582 |  | 
 | 1583 |         def properties_changed_handler(self, interface, new, old, **kw): | 
 | 1584 |             self.logging_enabled = new.values()[0] | 
 | 1585 |  | 
| Deepak Kodihalli | 6e1ca53 | 2018-09-04 03:51:04 -0500 | [diff] [blame] | 1586 |     def apply(self, callback, route): | 
 | 1587 |         cb = route.get_undecorated_callback() | 
 | 1588 |         skip = getattr( | 
 | 1589 |             cb, 'suppress_logging', None) | 
 | 1590 |         if skip: | 
 | 1591 |             return callback | 
 | 1592 |  | 
 | 1593 |         suppress_json_logging = getattr( | 
 | 1594 |             cb, 'suppress_json_logging', None) | 
 | 1595 |         return self.Logger(suppress_json_logging, callback, cb.app) | 
 | 1596 |  | 
 | 1597 |  | 
| Brad Bishop | 2c6fc76 | 2016-08-29 15:53:25 -0400 | [diff] [blame] | 1598 | class App(Bottle): | 
| Deepak Kodihalli | 0fe213f | 2017-10-11 00:08:48 -0500 | [diff] [blame] | 1599 |     def __init__(self, **kw): | 
| Brad Bishop | 2c6fc76 | 2016-08-29 15:53:25 -0400 | [diff] [blame] | 1600 |         super(App, self).__init__(autojson=False) | 
| Deepak Kodihalli | b209dd1 | 2017-10-11 01:19:17 -0500 | [diff] [blame] | 1601 |  | 
 | 1602 |         self.have_wsock = kw.get('have_wsock', False) | 
| Alexander Filippov | d08a456 | 2018-03-20 12:02:23 +0300 | [diff] [blame] | 1603 |         self.with_bmc_check = '--with-bmc-check' in sys.argv | 
| Deepak Kodihalli | b209dd1 | 2017-10-11 01:19:17 -0500 | [diff] [blame] | 1604 |  | 
| Brad Bishop | 2ddfa00 | 2016-08-29 15:11:55 -0400 | [diff] [blame] | 1605 |         self.bus = dbus.SystemBus() | 
 | 1606 |         self.mapper = obmc.mapper.Mapper(self.bus) | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 1607 |         self.error_callbacks = [] | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1608 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1609 |         self.install_hooks() | 
 | 1610 |         self.install_plugins() | 
 | 1611 |         self.create_handlers() | 
 | 1612 |         self.install_handlers() | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1613 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1614 |     def install_plugins(self): | 
 | 1615 |         # install json api plugins | 
 | 1616 |         json_kw = {'indent': 2, 'sort_keys': True} | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1617 |         self.install(AuthorizationPlugin()) | 
| Brad Bishop | d0c404a | 2017-02-21 09:23:25 -0500 | [diff] [blame] | 1618 |         self.install(CorsPlugin(self)) | 
| Deepak Kodihalli | 461367a | 2017-04-10 07:11:38 -0500 | [diff] [blame] | 1619 |         self.install(ContentCheckerPlugin()) | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 1620 |         self.install(JsonpPlugin(self, **json_kw)) | 
 | 1621 |         self.install(JsonErrorsPlugin(self, **json_kw)) | 
 | 1622 |         self.install(JsonApiResponsePlugin(self)) | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1623 |         self.install(JsonApiRequestPlugin()) | 
 | 1624 |         self.install(JsonApiRequestTypePlugin()) | 
| Deepak Kodihalli | 6e1ca53 | 2018-09-04 03:51:04 -0500 | [diff] [blame] | 1625 |         self.install(LoggingPlugin()) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1626 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1627 |     def install_hooks(self): | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 1628 |         self.error_handler_type = type(self.default_error_handler) | 
 | 1629 |         self.original_error_handler = self.default_error_handler | 
 | 1630 |         self.default_error_handler = self.error_handler_type( | 
 | 1631 |             self.custom_error_handler, self, Bottle) | 
 | 1632 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1633 |         self.real_router_match = self.router.match | 
 | 1634 |         self.router.match = self.custom_router_match | 
 | 1635 |         self.add_hook('before_request', self.strip_extra_slashes) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1636 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1637 |     def create_handlers(self): | 
 | 1638 |         # create route handlers | 
 | 1639 |         self.session_handler = SessionHandler(self, self.bus) | 
| Matt Spinler | d41643e | 2018-02-02 13:51:38 -0600 | [diff] [blame] | 1640 |         self.web_handler = WebHandler(self, self.bus) | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1641 |         self.directory_handler = DirectoryHandler(self, self.bus) | 
 | 1642 |         self.list_names_handler = ListNamesHandler(self, self.bus) | 
 | 1643 |         self.list_handler = ListHandler(self, self.bus) | 
 | 1644 |         self.method_handler = MethodHandler(self, self.bus) | 
 | 1645 |         self.property_handler = PropertyHandler(self, self.bus) | 
 | 1646 |         self.schema_handler = SchemaHandler(self, self.bus) | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 1647 |         self.image_upload_post_handler = ImagePostHandler(self, self.bus) | 
 | 1648 |         self.image_upload_put_handler = ImagePutHandler(self, self.bus) | 
| Jayanth Othayoth | 9bc9499 | 2017-06-29 06:30:40 -0500 | [diff] [blame] | 1649 |         self.download_dump_get_handler = DownloadDumpHandler(self, self.bus) | 
| Deepak Kodihalli | b209dd1 | 2017-10-11 01:19:17 -0500 | [diff] [blame] | 1650 |         if self.have_wsock: | 
 | 1651 |             self.event_handler = EventHandler(self, self.bus) | 
| Deepak Kodihalli | 5c518f6 | 2018-04-23 03:26:38 -0500 | [diff] [blame] | 1652 |             self.host_console_handler = HostConsoleHandler(self, self.bus) | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1653 |         self.instance_handler = InstanceHandler(self, self.bus) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1654 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1655 |     def install_handlers(self): | 
 | 1656 |         self.session_handler.install() | 
| Matt Spinler | d41643e | 2018-02-02 13:51:38 -0600 | [diff] [blame] | 1657 |         self.web_handler.install() | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1658 |         self.directory_handler.install() | 
 | 1659 |         self.list_names_handler.install() | 
 | 1660 |         self.list_handler.install() | 
 | 1661 |         self.method_handler.install() | 
 | 1662 |         self.property_handler.install() | 
 | 1663 |         self.schema_handler.install() | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 1664 |         self.image_upload_post_handler.install() | 
 | 1665 |         self.image_upload_put_handler.install() | 
| Jayanth Othayoth | 9bc9499 | 2017-06-29 06:30:40 -0500 | [diff] [blame] | 1666 |         self.download_dump_get_handler.install() | 
| Deepak Kodihalli | b209dd1 | 2017-10-11 01:19:17 -0500 | [diff] [blame] | 1667 |         if self.have_wsock: | 
 | 1668 |             self.event_handler.install() | 
| Deepak Kodihalli | 5c518f6 | 2018-04-23 03:26:38 -0500 | [diff] [blame] | 1669 |             self.host_console_handler.install() | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1670 |         # this has to come last, since it matches everything | 
 | 1671 |         self.instance_handler.install() | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1672 |  | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 1673 |     def install_error_callback(self, callback): | 
 | 1674 |         self.error_callbacks.insert(0, callback) | 
 | 1675 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1676 |     def custom_router_match(self, environ): | 
 | 1677 |         ''' The built-in Bottle algorithm for figuring out if a 404 or 405 is | 
 | 1678 |             needed doesn't work for us since the instance rules match | 
 | 1679 |             everything. This monkey-patch lets the route handler figure | 
 | 1680 |             out which response is needed.  This could be accomplished | 
 | 1681 |             with a hook but that would require calling the router match | 
 | 1682 |             function twice. | 
 | 1683 |         ''' | 
 | 1684 |         route, args = self.real_router_match(environ) | 
 | 1685 |         if isinstance(route.callback, RouteHandler): | 
 | 1686 |             route.callback._setup(**args) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1687 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1688 |         return route, args | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 1689 |  | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 1690 |     def custom_error_handler(self, res, error): | 
| Gunnar Mills | f01d0ba | 2017-10-25 20:37:24 -0500 | [diff] [blame] | 1691 |         ''' Allow plugins to modify error responses too via this custom | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 1692 |             error handler. ''' | 
 | 1693 |  | 
 | 1694 |         response_object = {} | 
 | 1695 |         response_body = {} | 
 | 1696 |         for x in self.error_callbacks: | 
 | 1697 |             x(error=error, | 
 | 1698 |                 response_object=response_object, | 
 | 1699 |                 response_body=response_body) | 
 | 1700 |  | 
 | 1701 |         return response_body.get('body', "") | 
 | 1702 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1703 |     @staticmethod | 
 | 1704 |     def strip_extra_slashes(): | 
 | 1705 |         path = request.environ['PATH_INFO'] | 
 | 1706 |         trailing = ("", "/")[path[-1] == '/'] | 
| CamVan Nguyen | 249d132 | 2018-03-05 10:08:33 -0600 | [diff] [blame] | 1707 |         parts = list(filter(bool, path.split('/'))) | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 1708 |         request.environ['PATH_INFO'] = '/' + '/'.join(parts) + trailing |