| 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 | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 18 | import dbus | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 19 | import dbus.exceptions | 
|  | 20 | import json | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 21 | from xml.etree import ElementTree | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 22 | from bottle import Bottle, abort, request, response, JSONPlugin, HTTPError | 
| Brad Bishop | b103d2d | 2016-03-04 16:19:14 -0500 | [diff] [blame] | 23 | import obmc.utils.misc | 
| Brad Bishop | b103d2d | 2016-03-04 16:19:14 -0500 | [diff] [blame] | 24 | from obmc.dbuslib.introspection import IntrospectionNodeParser | 
|  | 25 | import obmc.mapper | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 26 | import spwd | 
|  | 27 | import grp | 
|  | 28 | import crypt | 
| Deepak Kodihalli | 1af301a | 2017-04-11 07:29:01 -0500 | [diff] [blame] | 29 | import tempfile | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 30 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 31 | DBUS_UNKNOWN_INTERFACE = 'org.freedesktop.UnknownInterface' | 
| Brad Bishop | f4e7498 | 2016-04-01 14:53:05 -0400 | [diff] [blame] | 32 | DBUS_UNKNOWN_INTERFACE_ERROR = 'org.freedesktop.DBus.Error.UnknownInterface' | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 33 | DBUS_UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod' | 
|  | 34 | DBUS_INVALID_ARGS = 'org.freedesktop.DBus.Error.InvalidArgs' | 
| Brad Bishop | d457892 | 2015-12-02 11:10:36 -0500 | [diff] [blame] | 35 | DBUS_TYPE_ERROR = 'org.freedesktop.DBus.Python.TypeError' | 
| Deepak Kodihalli | 6075bb4 | 2017-04-04 05:49:17 -0500 | [diff] [blame] | 36 | DELETE_IFACE = 'xyz.openbmc_project.Object.Delete' | 
| Brad Bishop | 9ee57c4 | 2015-11-03 14:59:29 -0500 | [diff] [blame] | 37 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 38 | _4034_msg = "The specified %s cannot be %s: '%s'" | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 39 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 40 |  | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 41 | def valid_user(session, *a, **kw): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 42 | ''' Authorization plugin callback that checks | 
|  | 43 | that the user is logged in. ''' | 
|  | 44 | if session is None: | 
| Brad Bishop | dc3fbfa | 2016-09-08 09:51:38 -0400 | [diff] [blame] | 45 | abort(401, 'Login required') | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 46 |  | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 47 |  | 
|  | 48 | class UserInGroup: | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 49 | ''' Authorization plugin callback that checks that the user is logged in | 
|  | 50 | and a member of a group. ''' | 
|  | 51 | def __init__(self, group): | 
|  | 52 | self.group = group | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 53 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 54 | def __call__(self, session, *a, **kw): | 
|  | 55 | valid_user(session, *a, **kw) | 
|  | 56 | res = False | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 57 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 58 | try: | 
|  | 59 | res = session['user'] in grp.getgrnam(self.group)[3] | 
|  | 60 | except KeyError: | 
|  | 61 | pass | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 62 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 63 | if not res: | 
|  | 64 | abort(403, 'Insufficient access') | 
|  | 65 |  | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 66 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 67 | class RouteHandler(object): | 
| Brad Bishop | 6d19060 | 2016-04-15 13:09:39 -0400 | [diff] [blame] | 68 | _require_auth = obmc.utils.misc.makelist(valid_user) | 
| Brad Bishop | d0c404a | 2017-02-21 09:23:25 -0500 | [diff] [blame] | 69 | _enable_cors = True | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 70 |  | 
| Deepak Kodihalli | 83afbaf | 2017-04-10 06:37:19 -0500 | [diff] [blame] | 71 | def __init__(self, app, bus, verbs, rules, content_type=''): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 72 | self.app = app | 
|  | 73 | self.bus = bus | 
| Brad Bishop | b103d2d | 2016-03-04 16:19:14 -0500 | [diff] [blame] | 74 | self.mapper = obmc.mapper.Mapper(bus) | 
| Brad Bishop | 6d19060 | 2016-04-15 13:09:39 -0400 | [diff] [blame] | 75 | self._verbs = obmc.utils.misc.makelist(verbs) | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 76 | self._rules = rules | 
| Deepak Kodihalli | 83afbaf | 2017-04-10 06:37:19 -0500 | [diff] [blame] | 77 | self._content_type = content_type | 
| Brad Bishop | 0f79e52 | 2016-03-18 13:33:17 -0400 | [diff] [blame] | 78 | self.intf_match = obmc.utils.misc.org_dot_openbmc_match | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 79 |  | 
| Brad Bishop | 88c76a4 | 2017-02-21 00:02:02 -0500 | [diff] [blame] | 80 | if 'GET' in self._verbs: | 
|  | 81 | self._verbs = list(set(self._verbs + ['HEAD'])) | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 82 | if 'OPTIONS' not in self._verbs: | 
|  | 83 | self._verbs.append('OPTIONS') | 
| Brad Bishop | 88c76a4 | 2017-02-21 00:02:02 -0500 | [diff] [blame] | 84 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 85 | def _setup(self, **kw): | 
|  | 86 | request.route_data = {} | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 87 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 88 | if request.method in self._verbs: | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 89 | if request.method != 'OPTIONS': | 
|  | 90 | return self.setup(**kw) | 
| Brad Bishop | 88c76a4 | 2017-02-21 00:02:02 -0500 | [diff] [blame] | 91 |  | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 92 | # Javascript implementations will not send credentials | 
|  | 93 | # with an OPTIONS request.  Don't help malicious clients | 
|  | 94 | # by checking the path here and returning a 404 if the | 
|  | 95 | # path doesn't exist. | 
|  | 96 | return None | 
| Brad Bishop | 88c76a4 | 2017-02-21 00:02:02 -0500 | [diff] [blame] | 97 |  | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 98 | # Return 405 | 
| Brad Bishop | 88c76a4 | 2017-02-21 00:02:02 -0500 | [diff] [blame] | 99 | raise HTTPError( | 
|  | 100 | 405, "Method not allowed.", Allow=','.join(self._verbs)) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 101 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 102 | def __call__(self, **kw): | 
|  | 103 | return getattr(self, 'do_' + request.method.lower())(**kw) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 104 |  | 
| Brad Bishop | 88c76a4 | 2017-02-21 00:02:02 -0500 | [diff] [blame] | 105 | def do_head(self, **kw): | 
|  | 106 | return self.do_get(**kw) | 
|  | 107 |  | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 108 | def do_options(self, **kw): | 
|  | 109 | for v in self._verbs: | 
|  | 110 | response.set_header( | 
|  | 111 | 'Allow', | 
|  | 112 | ','.join(self._verbs)) | 
|  | 113 | return None | 
|  | 114 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 115 | def install(self): | 
|  | 116 | self.app.route( | 
|  | 117 | self._rules, callback=self, | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 118 | method=['OPTIONS', 'GET', 'PUT', 'PATCH', 'POST', 'DELETE']) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 119 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 120 | @staticmethod | 
|  | 121 | def try_mapper_call(f, callback=None, **kw): | 
|  | 122 | try: | 
|  | 123 | return f(**kw) | 
|  | 124 | except dbus.exceptions.DBusException, e: | 
| Brad Bishop | fce7756 | 2016-11-28 15:44:18 -0500 | [diff] [blame] | 125 | if e.get_dbus_name() == \ | 
|  | 126 | 'org.freedesktop.DBus.Error.ObjectPathInUse': | 
|  | 127 | abort(503, str(e)) | 
| Brad Bishop | b103d2d | 2016-03-04 16:19:14 -0500 | [diff] [blame] | 128 | if e.get_dbus_name() != obmc.mapper.MAPPER_NOT_FOUND: | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 129 | raise | 
|  | 130 | if callback is None: | 
|  | 131 | def callback(e, **kw): | 
|  | 132 | abort(404, str(e)) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 133 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 134 | callback(e, **kw) | 
|  | 135 |  | 
|  | 136 | @staticmethod | 
|  | 137 | def try_properties_interface(f, *a): | 
|  | 138 | try: | 
|  | 139 | return f(*a) | 
|  | 140 | except dbus.exceptions.DBusException, e: | 
|  | 141 | if DBUS_UNKNOWN_INTERFACE in e.get_dbus_message(): | 
|  | 142 | # interface doesn't have any properties | 
|  | 143 | return None | 
| Brad Bishop | f4e7498 | 2016-04-01 14:53:05 -0400 | [diff] [blame] | 144 | if DBUS_UNKNOWN_INTERFACE_ERROR in e.get_dbus_name(): | 
|  | 145 | # interface doesn't have any properties | 
|  | 146 | return None | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 147 | if DBUS_UNKNOWN_METHOD == e.get_dbus_name(): | 
|  | 148 | # properties interface not implemented at all | 
|  | 149 | return None | 
|  | 150 | raise | 
|  | 151 |  | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 152 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 153 | class DirectoryHandler(RouteHandler): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 154 | verbs = 'GET' | 
|  | 155 | rules = '<path:path>/' | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 156 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 157 | def __init__(self, app, bus): | 
|  | 158 | super(DirectoryHandler, self).__init__( | 
|  | 159 | app, bus, self.verbs, self.rules) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 160 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 161 | def find(self, path='/'): | 
|  | 162 | return self.try_mapper_call( | 
|  | 163 | self.mapper.get_subtree_paths, path=path, depth=1) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 164 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 165 | def setup(self, path='/'): | 
|  | 166 | request.route_data['map'] = self.find(path) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 167 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 168 | def do_get(self, path='/'): | 
|  | 169 | return request.route_data['map'] | 
|  | 170 |  | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 171 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 172 | class ListNamesHandler(RouteHandler): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 173 | verbs = 'GET' | 
|  | 174 | rules = ['/list', '<path:path>/list'] | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 175 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 176 | def __init__(self, app, bus): | 
|  | 177 | super(ListNamesHandler, self).__init__( | 
|  | 178 | app, bus, self.verbs, self.rules) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 179 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 180 | def find(self, path='/'): | 
|  | 181 | return self.try_mapper_call( | 
|  | 182 | self.mapper.get_subtree, path=path).keys() | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 183 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 184 | def setup(self, path='/'): | 
|  | 185 | request.route_data['map'] = self.find(path) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 186 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 187 | def do_get(self, path='/'): | 
|  | 188 | return request.route_data['map'] | 
|  | 189 |  | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 190 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 191 | class ListHandler(RouteHandler): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 192 | verbs = 'GET' | 
|  | 193 | rules = ['/enumerate', '<path:path>/enumerate'] | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 194 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 195 | def __init__(self, app, bus): | 
|  | 196 | super(ListHandler, self).__init__( | 
|  | 197 | app, bus, self.verbs, self.rules) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 198 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 199 | def find(self, path='/'): | 
|  | 200 | return self.try_mapper_call( | 
|  | 201 | self.mapper.get_subtree, path=path) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 202 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 203 | def setup(self, path='/'): | 
|  | 204 | request.route_data['map'] = self.find(path) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 205 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 206 | def do_get(self, path='/'): | 
| Brad Bishop | 71527b4 | 2016-04-01 14:51:14 -0400 | [diff] [blame] | 207 | return {x: y for x, y in self.mapper.enumerate_subtree( | 
|  | 208 | path, | 
|  | 209 | mapper_data=request.route_data['map']).dataitems()} | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 210 |  | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 211 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 212 | class MethodHandler(RouteHandler): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 213 | verbs = 'POST' | 
|  | 214 | rules = '<path:path>/action/<method>' | 
|  | 215 | request_type = list | 
| Deepak Kodihalli | 83afbaf | 2017-04-10 06:37:19 -0500 | [diff] [blame] | 216 | content_type = 'application/json' | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 217 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 218 | def __init__(self, app, bus): | 
|  | 219 | super(MethodHandler, self).__init__( | 
| Deepak Kodihalli | 83afbaf | 2017-04-10 06:37:19 -0500 | [diff] [blame] | 220 | app, bus, self.verbs, self.rules, self.content_type) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 221 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 222 | def find(self, path, method): | 
|  | 223 | busses = self.try_mapper_call( | 
|  | 224 | self.mapper.get_object, path=path) | 
|  | 225 | for items in busses.iteritems(): | 
|  | 226 | m = self.find_method_on_bus(path, method, *items) | 
|  | 227 | if m: | 
|  | 228 | return m | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 229 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 230 | abort(404, _4034_msg % ('method', 'found', method)) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 231 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 232 | def setup(self, path, method): | 
|  | 233 | request.route_data['method'] = self.find(path, method) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 234 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 235 | def do_post(self, path, method): | 
|  | 236 | try: | 
|  | 237 | if request.parameter_list: | 
|  | 238 | return request.route_data['method'](*request.parameter_list) | 
|  | 239 | else: | 
|  | 240 | return request.route_data['method']() | 
| 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 | except dbus.exceptions.DBusException, e: | 
|  | 243 | if e.get_dbus_name() == DBUS_INVALID_ARGS: | 
|  | 244 | abort(400, str(e)) | 
|  | 245 | if e.get_dbus_name() == DBUS_TYPE_ERROR: | 
|  | 246 | abort(400, str(e)) | 
|  | 247 | raise | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 248 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 249 | @staticmethod | 
|  | 250 | def find_method_in_interface(method, obj, interface, methods): | 
|  | 251 | if methods is None: | 
|  | 252 | return None | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 253 |  | 
| Brad Bishop | 6d19060 | 2016-04-15 13:09:39 -0400 | [diff] [blame] | 254 | method = obmc.utils.misc.find_case_insensitive(method, methods.keys()) | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 255 | if method is not None: | 
|  | 256 | iface = dbus.Interface(obj, interface) | 
|  | 257 | return iface.get_dbus_method(method) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 258 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 259 | def find_method_on_bus(self, path, method, bus, interfaces): | 
|  | 260 | obj = self.bus.get_object(bus, path, introspect=False) | 
|  | 261 | iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE) | 
|  | 262 | data = iface.Introspect() | 
|  | 263 | parser = IntrospectionNodeParser( | 
|  | 264 | ElementTree.fromstring(data), | 
| Brad Bishop | b103d2d | 2016-03-04 16:19:14 -0500 | [diff] [blame] | 265 | intf_match=obmc.utils.misc.ListMatch(interfaces)) | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 266 | for x, y in parser.get_interfaces().iteritems(): | 
|  | 267 | m = self.find_method_in_interface( | 
|  | 268 | method, obj, x, y.get('method')) | 
|  | 269 | if m: | 
|  | 270 | return m | 
|  | 271 |  | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 272 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 273 | class PropertyHandler(RouteHandler): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 274 | verbs = ['PUT', 'GET'] | 
|  | 275 | rules = '<path:path>/attr/<prop>' | 
| Deepak Kodihalli | 83afbaf | 2017-04-10 06:37:19 -0500 | [diff] [blame] | 276 | content_type = 'application/json' | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 277 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 278 | def __init__(self, app, bus): | 
|  | 279 | super(PropertyHandler, self).__init__( | 
| Deepak Kodihalli | 83afbaf | 2017-04-10 06:37:19 -0500 | [diff] [blame] | 280 | app, bus, self.verbs, self.rules, self.content_type) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 281 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 282 | def find(self, path, prop): | 
|  | 283 | self.app.instance_handler.setup(path) | 
|  | 284 | obj = self.app.instance_handler.do_get(path) | 
| Brad Bishop | 56ad87f | 2017-02-21 23:33:29 -0500 | [diff] [blame] | 285 | real_name = obmc.utils.misc.find_case_insensitive( | 
|  | 286 | prop, obj.keys()) | 
| Brad Bishop | aa65f6e | 2015-10-27 16:28:51 -0400 | [diff] [blame] | 287 |  | 
| Brad Bishop | 56ad87f | 2017-02-21 23:33:29 -0500 | [diff] [blame] | 288 | if not real_name: | 
|  | 289 | if request.method == 'PUT': | 
|  | 290 | abort(403, _4034_msg % ('property', 'created', prop)) | 
|  | 291 | else: | 
|  | 292 | abort(404, _4034_msg % ('property', 'found', prop)) | 
|  | 293 | return real_name, {path: obj} | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 294 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 295 | def setup(self, path, prop): | 
| Brad Bishop | 56ad87f | 2017-02-21 23:33:29 -0500 | [diff] [blame] | 296 | name, obj = self.find(path, prop) | 
|  | 297 | request.route_data['obj'] = obj | 
|  | 298 | request.route_data['name'] = name | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 299 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 300 | def do_get(self, path, prop): | 
| Brad Bishop | 56ad87f | 2017-02-21 23:33:29 -0500 | [diff] [blame] | 301 | name = request.route_data['name'] | 
|  | 302 | return request.route_data['obj'][path][name] | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 303 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 304 | def do_put(self, path, prop, value=None): | 
|  | 305 | if value is None: | 
|  | 306 | value = request.parameter_list | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 307 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 308 | prop, iface, properties_iface = self.get_host_interface( | 
|  | 309 | path, prop, request.route_data['map'][path]) | 
|  | 310 | try: | 
|  | 311 | properties_iface.Set(iface, prop, value) | 
|  | 312 | except ValueError, e: | 
|  | 313 | abort(400, str(e)) | 
|  | 314 | except dbus.exceptions.DBusException, e: | 
|  | 315 | if e.get_dbus_name() == DBUS_INVALID_ARGS: | 
|  | 316 | abort(403, str(e)) | 
|  | 317 | raise | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 318 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 319 | def get_host_interface(self, path, prop, bus_info): | 
|  | 320 | for bus, interfaces in bus_info.iteritems(): | 
|  | 321 | obj = self.bus.get_object(bus, path, introspect=True) | 
|  | 322 | properties_iface = dbus.Interface( | 
|  | 323 | obj, dbus_interface=dbus.PROPERTIES_IFACE) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 324 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 325 | info = self.get_host_interface_on_bus( | 
|  | 326 | path, prop, properties_iface, bus, interfaces) | 
|  | 327 | if info is not None: | 
|  | 328 | prop, iface = info | 
|  | 329 | return prop, iface, properties_iface | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 330 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 331 | def get_host_interface_on_bus(self, path, prop, iface, bus, interfaces): | 
|  | 332 | for i in interfaces: | 
|  | 333 | properties = self.try_properties_interface(iface.GetAll, i) | 
| Brad Bishop | 69cb6d1 | 2017-02-21 12:01:52 -0500 | [diff] [blame] | 334 | if not properties: | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 335 | continue | 
| Leonel Gonzalez | 409f671 | 2017-05-24 09:51:55 -0500 | [diff] [blame^] | 336 | match = obmc.utils.misc.find_case_insensitive( | 
| Brad Bishop | 8b0d3fa | 2016-11-28 15:41:47 -0500 | [diff] [blame] | 337 | prop, properties.keys()) | 
| Leonel Gonzalez | 409f671 | 2017-05-24 09:51:55 -0500 | [diff] [blame^] | 338 | if match is None: | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 339 | continue | 
| Leonel Gonzalez | 409f671 | 2017-05-24 09:51:55 -0500 | [diff] [blame^] | 340 | prop = match | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 341 | return prop, i | 
|  | 342 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 343 |  | 
| Brad Bishop | 2503bd6 | 2015-12-16 17:56:12 -0500 | [diff] [blame] | 344 | class SchemaHandler(RouteHandler): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 345 | verbs = ['GET'] | 
|  | 346 | rules = '<path:path>/schema' | 
| Brad Bishop | 2503bd6 | 2015-12-16 17:56:12 -0500 | [diff] [blame] | 347 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 348 | def __init__(self, app, bus): | 
|  | 349 | super(SchemaHandler, self).__init__( | 
|  | 350 | app, bus, self.verbs, self.rules) | 
| Brad Bishop | 2503bd6 | 2015-12-16 17:56:12 -0500 | [diff] [blame] | 351 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 352 | def find(self, path): | 
|  | 353 | return self.try_mapper_call( | 
|  | 354 | self.mapper.get_object, | 
|  | 355 | path=path) | 
| Brad Bishop | 2503bd6 | 2015-12-16 17:56:12 -0500 | [diff] [blame] | 356 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 357 | def setup(self, path): | 
|  | 358 | request.route_data['map'] = self.find(path) | 
| Brad Bishop | 2503bd6 | 2015-12-16 17:56:12 -0500 | [diff] [blame] | 359 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 360 | def do_get(self, path): | 
|  | 361 | schema = {} | 
|  | 362 | for x in request.route_data['map'].iterkeys(): | 
|  | 363 | obj = self.bus.get_object(x, path, introspect=False) | 
|  | 364 | iface = dbus.Interface(obj, dbus.INTROSPECTABLE_IFACE) | 
|  | 365 | data = iface.Introspect() | 
|  | 366 | parser = IntrospectionNodeParser( | 
|  | 367 | ElementTree.fromstring(data)) | 
|  | 368 | for x, y in parser.get_interfaces().iteritems(): | 
|  | 369 | schema[x] = y | 
| Brad Bishop | 2503bd6 | 2015-12-16 17:56:12 -0500 | [diff] [blame] | 370 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 371 | return schema | 
|  | 372 |  | 
| Brad Bishop | 2503bd6 | 2015-12-16 17:56:12 -0500 | [diff] [blame] | 373 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 374 | class InstanceHandler(RouteHandler): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 375 | verbs = ['GET', 'PUT', 'DELETE'] | 
|  | 376 | rules = '<path:path>' | 
|  | 377 | request_type = dict | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 378 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 379 | def __init__(self, app, bus): | 
|  | 380 | super(InstanceHandler, self).__init__( | 
|  | 381 | app, bus, self.verbs, self.rules) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 382 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 383 | def find(self, path, callback=None): | 
|  | 384 | return {path: self.try_mapper_call( | 
|  | 385 | self.mapper.get_object, | 
|  | 386 | callback, | 
|  | 387 | path=path)} | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 388 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 389 | def setup(self, path): | 
|  | 390 | callback = None | 
|  | 391 | if request.method == 'PUT': | 
|  | 392 | def callback(e, **kw): | 
|  | 393 | abort(403, _4034_msg % ('resource', 'created', path)) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 394 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 395 | if request.route_data.get('map') is None: | 
|  | 396 | request.route_data['map'] = self.find(path, callback) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 397 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 398 | def do_get(self, path): | 
| Brad Bishop | 71527b4 | 2016-04-01 14:51:14 -0400 | [diff] [blame] | 399 | return self.mapper.enumerate_object( | 
|  | 400 | path, | 
|  | 401 | mapper_data=request.route_data['map']) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 402 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 403 | def do_put(self, path): | 
|  | 404 | # make sure all properties exist in the request | 
|  | 405 | obj = set(self.do_get(path).keys()) | 
|  | 406 | req = set(request.parameter_list.keys()) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 407 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 408 | diff = list(obj.difference(req)) | 
|  | 409 | if diff: | 
|  | 410 | abort(403, _4034_msg % ( | 
|  | 411 | 'resource', 'removed', '%s/attr/%s' % (path, diff[0]))) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 412 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 413 | diff = list(req.difference(obj)) | 
|  | 414 | if diff: | 
|  | 415 | abort(403, _4034_msg % ( | 
|  | 416 | 'resource', 'created', '%s/attr/%s' % (path, diff[0]))) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 417 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 418 | for p, v in request.parameter_list.iteritems(): | 
|  | 419 | self.app.property_handler.do_put( | 
|  | 420 | path, p, v) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 421 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 422 | def do_delete(self, path): | 
|  | 423 | for bus_info in request.route_data['map'][path].iteritems(): | 
|  | 424 | if self.bus_missing_delete(path, *bus_info): | 
|  | 425 | abort(403, _4034_msg % ('resource', 'removed', path)) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 426 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 427 | for bus in request.route_data['map'][path].iterkeys(): | 
|  | 428 | self.delete_on_bus(path, bus) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 429 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 430 | def bus_missing_delete(self, path, bus, interfaces): | 
|  | 431 | return DELETE_IFACE not in interfaces | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 432 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 433 | def delete_on_bus(self, path, bus): | 
|  | 434 | obj = self.bus.get_object(bus, path, introspect=False) | 
|  | 435 | delete_iface = dbus.Interface( | 
|  | 436 | obj, dbus_interface=DELETE_IFACE) | 
|  | 437 | delete_iface.Delete() | 
|  | 438 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 439 |  | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 440 | class SessionHandler(MethodHandler): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 441 | ''' Handles the /login and /logout routes, manages | 
|  | 442 | server side session store and session cookies.  ''' | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 443 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 444 | rules = ['/login', '/logout'] | 
|  | 445 | login_str = "User '%s' logged %s" | 
|  | 446 | bad_passwd_str = "Invalid username or password" | 
|  | 447 | no_user_str = "No user logged in" | 
|  | 448 | bad_json_str = "Expecting request format { 'data': " \ | 
|  | 449 | "[<username>, <password>] }, got '%s'" | 
|  | 450 | _require_auth = None | 
|  | 451 | MAX_SESSIONS = 16 | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 452 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 453 | def __init__(self, app, bus): | 
|  | 454 | super(SessionHandler, self).__init__( | 
|  | 455 | app, bus) | 
|  | 456 | self.hmac_key = os.urandom(128) | 
|  | 457 | self.session_store = [] | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 458 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 459 | @staticmethod | 
|  | 460 | def authenticate(username, clear): | 
|  | 461 | try: | 
|  | 462 | encoded = spwd.getspnam(username)[1] | 
|  | 463 | return encoded == crypt.crypt(clear, encoded) | 
|  | 464 | except KeyError: | 
|  | 465 | return False | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 466 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 467 | def invalidate_session(self, session): | 
|  | 468 | try: | 
|  | 469 | self.session_store.remove(session) | 
|  | 470 | except ValueError: | 
|  | 471 | pass | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 472 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 473 | def new_session(self): | 
|  | 474 | sid = os.urandom(32) | 
|  | 475 | if self.MAX_SESSIONS <= len(self.session_store): | 
|  | 476 | self.session_store.pop() | 
|  | 477 | self.session_store.insert(0, {'sid': sid}) | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 478 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 479 | return self.session_store[0] | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 480 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 481 | def get_session(self, sid): | 
|  | 482 | sids = [x['sid'] for x in self.session_store] | 
|  | 483 | try: | 
|  | 484 | return self.session_store[sids.index(sid)] | 
|  | 485 | except ValueError: | 
|  | 486 | return None | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 487 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 488 | def get_session_from_cookie(self): | 
|  | 489 | return self.get_session( | 
|  | 490 | request.get_cookie( | 
|  | 491 | 'sid', secret=self.hmac_key)) | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 492 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 493 | def do_post(self, **kw): | 
|  | 494 | if request.path == '/login': | 
|  | 495 | return self.do_login(**kw) | 
|  | 496 | else: | 
|  | 497 | return self.do_logout(**kw) | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 498 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 499 | def do_logout(self, **kw): | 
|  | 500 | session = self.get_session_from_cookie() | 
|  | 501 | if session is not None: | 
|  | 502 | user = session['user'] | 
|  | 503 | self.invalidate_session(session) | 
|  | 504 | response.delete_cookie('sid') | 
|  | 505 | return self.login_str % (user, 'out') | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 506 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 507 | return self.no_user_str | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 508 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 509 | def do_login(self, **kw): | 
|  | 510 | session = self.get_session_from_cookie() | 
|  | 511 | if session is not None: | 
|  | 512 | return self.login_str % (session['user'], 'in') | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 513 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 514 | if len(request.parameter_list) != 2: | 
|  | 515 | abort(400, self.bad_json_str % (request.json)) | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 516 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 517 | if not self.authenticate(*request.parameter_list): | 
| Brad Bishop | dc3fbfa | 2016-09-08 09:51:38 -0400 | [diff] [blame] | 518 | abort(401, self.bad_passwd_str) | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 519 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 520 | user = request.parameter_list[0] | 
|  | 521 | session = self.new_session() | 
|  | 522 | session['user'] = user | 
|  | 523 | response.set_cookie( | 
|  | 524 | 'sid', session['sid'], secret=self.hmac_key, | 
|  | 525 | secure=True, | 
|  | 526 | httponly=True) | 
|  | 527 | return self.login_str % (user, 'in') | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 528 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 529 | def find(self, **kw): | 
|  | 530 | pass | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 531 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 532 | def setup(self, **kw): | 
|  | 533 | pass | 
|  | 534 |  | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 535 |  | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 536 | class ImageUploadUtils: | 
|  | 537 | ''' Provides common utils for image upload. ''' | 
| Deepak Kodihalli | 1af301a | 2017-04-11 07:29:01 -0500 | [diff] [blame] | 538 |  | 
| Deepak Kodihalli | 1af301a | 2017-04-11 07:29:01 -0500 | [diff] [blame] | 539 | file_loc = '/tmp/images' | 
|  | 540 | file_prefix = 'img' | 
|  | 541 | file_suffix = '' | 
|  | 542 |  | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 543 | @classmethod | 
|  | 544 | def do_upload(cls, filename=''): | 
|  | 545 | if not os.path.exists(cls.file_loc): | 
|  | 546 | os.makedirs(cls.file_loc) | 
|  | 547 | if not filename: | 
|  | 548 | handle, filename = tempfile.mkstemp(cls.file_suffix, | 
|  | 549 | cls.file_prefix, cls.file_loc) | 
|  | 550 | os.close(handle) | 
|  | 551 | else: | 
|  | 552 | filename = os.path.join(cls.file_loc, filename) | 
|  | 553 |  | 
|  | 554 | with open(filename, "w") as fd: | 
|  | 555 | fd.write(request.body.read()) | 
|  | 556 |  | 
|  | 557 |  | 
|  | 558 | class ImagePostHandler(RouteHandler): | 
|  | 559 | ''' Handles the /upload/image route. ''' | 
|  | 560 |  | 
|  | 561 | verbs = ['POST'] | 
|  | 562 | rules = ['/upload/image'] | 
|  | 563 | content_type = 'application/octet-stream' | 
|  | 564 |  | 
| Deepak Kodihalli | 1af301a | 2017-04-11 07:29:01 -0500 | [diff] [blame] | 565 | def __init__(self, app, bus): | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 566 | super(ImagePostHandler, self).__init__( | 
| Deepak Kodihalli | 1af301a | 2017-04-11 07:29:01 -0500 | [diff] [blame] | 567 | app, bus, self.verbs, self.rules, self.content_type) | 
|  | 568 |  | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 569 | def do_post(self, filename=''): | 
|  | 570 | ImageUploadUtils.do_upload() | 
| Deepak Kodihalli | 1af301a | 2017-04-11 07:29:01 -0500 | [diff] [blame] | 571 |  | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 572 | def find(self, **kw): | 
|  | 573 | pass | 
| Deepak Kodihalli | 1af301a | 2017-04-11 07:29:01 -0500 | [diff] [blame] | 574 |  | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 575 | def setup(self, **kw): | 
|  | 576 | pass | 
|  | 577 |  | 
|  | 578 |  | 
|  | 579 | class ImagePutHandler(RouteHandler): | 
|  | 580 | ''' Handles the /upload/image/<filename> route. ''' | 
|  | 581 |  | 
|  | 582 | verbs = ['PUT'] | 
|  | 583 | rules = ['/upload/image/<filename>'] | 
|  | 584 | content_type = 'application/octet-stream' | 
|  | 585 |  | 
|  | 586 | def __init__(self, app, bus): | 
|  | 587 | super(ImagePutHandler, self).__init__( | 
|  | 588 | app, bus, self.verbs, self.rules, self.content_type) | 
|  | 589 |  | 
|  | 590 | def do_put(self, filename=''): | 
|  | 591 | ImageUploadUtils.do_upload(filename) | 
| Deepak Kodihalli | 1af301a | 2017-04-11 07:29:01 -0500 | [diff] [blame] | 592 |  | 
|  | 593 | def find(self, **kw): | 
|  | 594 | pass | 
|  | 595 |  | 
|  | 596 | def setup(self, **kw): | 
|  | 597 | pass | 
|  | 598 |  | 
|  | 599 |  | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 600 | class AuthorizationPlugin(object): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 601 | ''' Invokes an optional list of authorization callbacks. ''' | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 602 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 603 | name = 'authorization' | 
|  | 604 | api = 2 | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 605 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 606 | class Compose: | 
|  | 607 | def __init__(self, validators, callback, session_mgr): | 
|  | 608 | self.validators = validators | 
|  | 609 | self.callback = callback | 
|  | 610 | self.session_mgr = session_mgr | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 611 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 612 | def __call__(self, *a, **kw): | 
|  | 613 | sid = request.get_cookie('sid', secret=self.session_mgr.hmac_key) | 
|  | 614 | session = self.session_mgr.get_session(sid) | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 615 | if request.method != 'OPTIONS': | 
|  | 616 | for x in self.validators: | 
|  | 617 | x(session, *a, **kw) | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 618 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 619 | return self.callback(*a, **kw) | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 620 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 621 | def apply(self, callback, route): | 
|  | 622 | undecorated = route.get_undecorated_callback() | 
|  | 623 | if not isinstance(undecorated, RouteHandler): | 
|  | 624 | return callback | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 625 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 626 | auth_types = getattr( | 
|  | 627 | undecorated, '_require_auth', None) | 
|  | 628 | if not auth_types: | 
|  | 629 | return callback | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 630 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 631 | return self.Compose( | 
|  | 632 | auth_types, callback, undecorated.app.session_handler) | 
|  | 633 |  | 
| Brad Bishop | 2f42858 | 2015-12-02 10:56:11 -0500 | [diff] [blame] | 634 |  | 
| Brad Bishop | d0c404a | 2017-02-21 09:23:25 -0500 | [diff] [blame] | 635 | class CorsPlugin(object): | 
|  | 636 | ''' Add CORS headers. ''' | 
|  | 637 |  | 
|  | 638 | name = 'cors' | 
|  | 639 | api = 2 | 
|  | 640 |  | 
|  | 641 | @staticmethod | 
|  | 642 | def process_origin(): | 
|  | 643 | origin = request.headers.get('Origin') | 
|  | 644 | if origin: | 
|  | 645 | response.add_header('Access-Control-Allow-Origin', origin) | 
|  | 646 | response.add_header( | 
|  | 647 | 'Access-Control-Allow-Credentials', 'true') | 
|  | 648 |  | 
|  | 649 | @staticmethod | 
|  | 650 | def process_method_and_headers(verbs): | 
|  | 651 | method = request.headers.get('Access-Control-Request-Method') | 
|  | 652 | headers = request.headers.get('Access-Control-Request-Headers') | 
|  | 653 | if headers: | 
|  | 654 | headers = [x.lower() for x in headers.split(',')] | 
|  | 655 |  | 
|  | 656 | if method in verbs \ | 
|  | 657 | and headers == ['content-type']: | 
|  | 658 | response.add_header('Access-Control-Allow-Methods', method) | 
|  | 659 | response.add_header( | 
|  | 660 | 'Access-Control-Allow-Headers', 'Content-Type') | 
|  | 661 |  | 
|  | 662 | def __init__(self, app): | 
|  | 663 | app.install_error_callback(self.error_callback) | 
|  | 664 |  | 
|  | 665 | def apply(self, callback, route): | 
|  | 666 | undecorated = route.get_undecorated_callback() | 
|  | 667 | if not isinstance(undecorated, RouteHandler): | 
|  | 668 | return callback | 
|  | 669 |  | 
|  | 670 | if not getattr(undecorated, '_enable_cors', None): | 
|  | 671 | return callback | 
|  | 672 |  | 
|  | 673 | def wrap(*a, **kw): | 
|  | 674 | self.process_origin() | 
|  | 675 | self.process_method_and_headers(undecorated._verbs) | 
|  | 676 | return callback(*a, **kw) | 
|  | 677 |  | 
|  | 678 | return wrap | 
|  | 679 |  | 
|  | 680 | def error_callback(self, **kw): | 
|  | 681 | self.process_origin() | 
|  | 682 |  | 
|  | 683 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 684 | class JsonApiRequestPlugin(object): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 685 | ''' Ensures request content satisfies the OpenBMC json api format. ''' | 
|  | 686 | name = 'json_api_request' | 
|  | 687 | api = 2 | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 688 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 689 | error_str = "Expecting request format { 'data': <value> }, got '%s'" | 
|  | 690 | type_error_str = "Unsupported Content-Type: '%s'" | 
|  | 691 | json_type = "application/json" | 
|  | 692 | request_methods = ['PUT', 'POST', 'PATCH'] | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 693 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 694 | @staticmethod | 
|  | 695 | def content_expected(): | 
|  | 696 | return request.method in JsonApiRequestPlugin.request_methods | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 697 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 698 | def validate_request(self): | 
|  | 699 | if request.content_length > 0 and \ | 
|  | 700 | request.content_type != self.json_type: | 
|  | 701 | abort(415, self.type_error_str % request.content_type) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 702 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 703 | try: | 
|  | 704 | request.parameter_list = request.json.get('data') | 
|  | 705 | except ValueError, e: | 
|  | 706 | abort(400, str(e)) | 
|  | 707 | except (AttributeError, KeyError, TypeError): | 
|  | 708 | abort(400, self.error_str % request.json) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 709 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 710 | def apply(self, callback, route): | 
| Deepak Kodihalli | fb6cd48 | 2017-04-10 07:27:09 -0500 | [diff] [blame] | 711 | content_type = getattr( | 
|  | 712 | route.get_undecorated_callback(), '_content_type', None) | 
|  | 713 | if self.json_type != content_type: | 
|  | 714 | return callback | 
|  | 715 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 716 | verbs = getattr( | 
|  | 717 | route.get_undecorated_callback(), '_verbs', None) | 
|  | 718 | if verbs is None: | 
|  | 719 | return callback | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 720 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 721 | if not set(self.request_methods).intersection(verbs): | 
|  | 722 | return callback | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 723 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 724 | def wrap(*a, **kw): | 
|  | 725 | if self.content_expected(): | 
|  | 726 | self.validate_request() | 
|  | 727 | return callback(*a, **kw) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 728 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 729 | return wrap | 
|  | 730 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 731 |  | 
|  | 732 | class JsonApiRequestTypePlugin(object): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 733 | ''' Ensures request content type satisfies the OpenBMC json api format. ''' | 
|  | 734 | name = 'json_api_method_request' | 
|  | 735 | api = 2 | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 736 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 737 | error_str = "Expecting request format { 'data': %s }, got '%s'" | 
| Deepak Kodihalli | fb6cd48 | 2017-04-10 07:27:09 -0500 | [diff] [blame] | 738 | json_type = "application/json" | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 739 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 740 | def apply(self, callback, route): | 
| Deepak Kodihalli | fb6cd48 | 2017-04-10 07:27:09 -0500 | [diff] [blame] | 741 | content_type = getattr( | 
|  | 742 | route.get_undecorated_callback(), '_content_type', None) | 
|  | 743 | if self.json_type != content_type: | 
|  | 744 | return callback | 
|  | 745 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 746 | request_type = getattr( | 
|  | 747 | route.get_undecorated_callback(), 'request_type', None) | 
|  | 748 | if request_type is None: | 
|  | 749 | return callback | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 750 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 751 | def validate_request(): | 
|  | 752 | if not isinstance(request.parameter_list, request_type): | 
|  | 753 | abort(400, self.error_str % (str(request_type), request.json)) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 754 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 755 | def wrap(*a, **kw): | 
|  | 756 | if JsonApiRequestPlugin.content_expected(): | 
|  | 757 | validate_request() | 
|  | 758 | return callback(*a, **kw) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 759 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 760 | return wrap | 
|  | 761 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 762 |  | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 763 | class JsonErrorsPlugin(JSONPlugin): | 
|  | 764 | ''' Extend the Bottle JSONPlugin such that it also encodes error | 
|  | 765 | responses. ''' | 
|  | 766 |  | 
|  | 767 | def __init__(self, app, **kw): | 
|  | 768 | super(JsonErrorsPlugin, self).__init__(**kw) | 
|  | 769 | self.json_opts = { | 
|  | 770 | x: y for x, y in kw.iteritems() | 
|  | 771 | if x in ['indent', 'sort_keys']} | 
|  | 772 | app.install_error_callback(self.error_callback) | 
|  | 773 |  | 
|  | 774 | def error_callback(self, response_object, response_body, **kw): | 
|  | 775 | response_body['body'] = json.dumps(response_object, **self.json_opts) | 
|  | 776 | response.content_type = 'application/json' | 
|  | 777 |  | 
|  | 778 |  | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 779 | class JsonApiResponsePlugin(object): | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 780 | ''' Emits responses in the OpenBMC json api format. ''' | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 781 | name = 'json_api_response' | 
|  | 782 | api = 2 | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 783 |  | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 784 | @staticmethod | 
|  | 785 | def has_body(): | 
|  | 786 | return request.method not in ['OPTIONS'] | 
|  | 787 |  | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 788 | def __init__(self, app): | 
|  | 789 | app.install_error_callback(self.error_callback) | 
|  | 790 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 791 | def apply(self, callback, route): | 
|  | 792 | def wrap(*a, **kw): | 
| Brad Bishop | d4c1c55 | 2017-02-21 00:07:28 -0500 | [diff] [blame] | 793 | data = callback(*a, **kw) | 
|  | 794 | if self.has_body(): | 
|  | 795 | resp = {'data': data} | 
|  | 796 | resp['status'] = 'ok' | 
|  | 797 | resp['message'] = response.status_line | 
|  | 798 | return resp | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 799 | return wrap | 
|  | 800 |  | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 801 | def error_callback(self, error, response_object, **kw): | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 802 | response_object['message'] = error.status_line | 
| Brad Bishop | 9c2531e | 2017-03-07 10:22:40 -0500 | [diff] [blame] | 803 | response_object['status'] = 'error' | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 804 | response_object.setdefault('data', {})['description'] = str(error.body) | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 805 | if error.status_code == 500: | 
|  | 806 | response_object['data']['exception'] = repr(error.exception) | 
|  | 807 | response_object['data']['traceback'] = error.traceback.splitlines() | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 808 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 809 |  | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 810 | class JsonpPlugin(object): | 
| Brad Bishop | 80fe37a | 2016-03-29 10:54:54 -0400 | [diff] [blame] | 811 | ''' Json javascript wrapper. ''' | 
|  | 812 | name = 'jsonp' | 
|  | 813 | api = 2 | 
|  | 814 |  | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 815 | def __init__(self, app, **kw): | 
|  | 816 | app.install_error_callback(self.error_callback) | 
| Brad Bishop | 80fe37a | 2016-03-29 10:54:54 -0400 | [diff] [blame] | 817 |  | 
|  | 818 | @staticmethod | 
|  | 819 | def to_jsonp(json): | 
|  | 820 | jwrapper = request.query.callback or None | 
|  | 821 | if(jwrapper): | 
|  | 822 | response.set_header('Content-Type', 'application/javascript') | 
|  | 823 | json = jwrapper + '(' + json + ');' | 
|  | 824 | return json | 
|  | 825 |  | 
|  | 826 | def apply(self, callback, route): | 
|  | 827 | def wrap(*a, **kw): | 
|  | 828 | return self.to_jsonp(callback(*a, **kw)) | 
|  | 829 | return wrap | 
|  | 830 |  | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 831 | def error_callback(self, response_body, **kw): | 
|  | 832 | response_body['body'] = self.to_jsonp(response_body['body']) | 
| Brad Bishop | 80fe37a | 2016-03-29 10:54:54 -0400 | [diff] [blame] | 833 |  | 
|  | 834 |  | 
| Deepak Kodihalli | 461367a | 2017-04-10 07:11:38 -0500 | [diff] [blame] | 835 | class ContentCheckerPlugin(object): | 
|  | 836 | ''' Ensures that a route is associated with the expected content-type | 
|  | 837 | header. ''' | 
|  | 838 | name = 'content_checker' | 
|  | 839 | api = 2 | 
|  | 840 |  | 
|  | 841 | class Checker: | 
|  | 842 | def __init__(self, type, callback): | 
|  | 843 | self.expected_type = type | 
|  | 844 | self.callback = callback | 
|  | 845 | self.error_str = "Expecting content type '%s', got '%s'" | 
|  | 846 |  | 
|  | 847 | def __call__(self, *a, **kw): | 
| Deepak Kodihalli | db1a21e | 2017-04-27 06:30:11 -0500 | [diff] [blame] | 848 | if request.method in ['PUT', 'POST', 'PATCH'] and \ | 
|  | 849 | self.expected_type and \ | 
| Deepak Kodihalli | 461367a | 2017-04-10 07:11:38 -0500 | [diff] [blame] | 850 | self.expected_type != request.content_type: | 
|  | 851 | abort(415, self.error_str % (self.expected_type, | 
|  | 852 | request.content_type)) | 
|  | 853 |  | 
|  | 854 | return self.callback(*a, **kw) | 
|  | 855 |  | 
|  | 856 | def apply(self, callback, route): | 
|  | 857 | content_type = getattr( | 
|  | 858 | route.get_undecorated_callback(), '_content_type', None) | 
|  | 859 |  | 
|  | 860 | return self.Checker(content_type, callback) | 
|  | 861 |  | 
|  | 862 |  | 
| Brad Bishop | 2c6fc76 | 2016-08-29 15:53:25 -0400 | [diff] [blame] | 863 | class App(Bottle): | 
| Brad Bishop | 2ddfa00 | 2016-08-29 15:11:55 -0400 | [diff] [blame] | 864 | def __init__(self): | 
| Brad Bishop | 2c6fc76 | 2016-08-29 15:53:25 -0400 | [diff] [blame] | 865 | super(App, self).__init__(autojson=False) | 
| Brad Bishop | 2ddfa00 | 2016-08-29 15:11:55 -0400 | [diff] [blame] | 866 | self.bus = dbus.SystemBus() | 
|  | 867 | self.mapper = obmc.mapper.Mapper(self.bus) | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 868 | self.error_callbacks = [] | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 869 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 870 | self.install_hooks() | 
|  | 871 | self.install_plugins() | 
|  | 872 | self.create_handlers() | 
|  | 873 | self.install_handlers() | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 874 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 875 | def install_plugins(self): | 
|  | 876 | # install json api plugins | 
|  | 877 | json_kw = {'indent': 2, 'sort_keys': True} | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 878 | self.install(AuthorizationPlugin()) | 
| Brad Bishop | d0c404a | 2017-02-21 09:23:25 -0500 | [diff] [blame] | 879 | self.install(CorsPlugin(self)) | 
| Deepak Kodihalli | 461367a | 2017-04-10 07:11:38 -0500 | [diff] [blame] | 880 | self.install(ContentCheckerPlugin()) | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 881 | self.install(JsonpPlugin(self, **json_kw)) | 
|  | 882 | self.install(JsonErrorsPlugin(self, **json_kw)) | 
|  | 883 | self.install(JsonApiResponsePlugin(self)) | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 884 | self.install(JsonApiRequestPlugin()) | 
|  | 885 | self.install(JsonApiRequestTypePlugin()) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 886 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 887 | def install_hooks(self): | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 888 | self.error_handler_type = type(self.default_error_handler) | 
|  | 889 | self.original_error_handler = self.default_error_handler | 
|  | 890 | self.default_error_handler = self.error_handler_type( | 
|  | 891 | self.custom_error_handler, self, Bottle) | 
|  | 892 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 893 | self.real_router_match = self.router.match | 
|  | 894 | self.router.match = self.custom_router_match | 
|  | 895 | self.add_hook('before_request', self.strip_extra_slashes) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 896 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 897 | def create_handlers(self): | 
|  | 898 | # create route handlers | 
|  | 899 | self.session_handler = SessionHandler(self, self.bus) | 
|  | 900 | self.directory_handler = DirectoryHandler(self, self.bus) | 
|  | 901 | self.list_names_handler = ListNamesHandler(self, self.bus) | 
|  | 902 | self.list_handler = ListHandler(self, self.bus) | 
|  | 903 | self.method_handler = MethodHandler(self, self.bus) | 
|  | 904 | self.property_handler = PropertyHandler(self, self.bus) | 
|  | 905 | self.schema_handler = SchemaHandler(self, self.bus) | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 906 | self.image_upload_post_handler = ImagePostHandler(self, self.bus) | 
|  | 907 | self.image_upload_put_handler = ImagePutHandler(self, self.bus) | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 908 | self.instance_handler = InstanceHandler(self, self.bus) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 909 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 910 | def install_handlers(self): | 
|  | 911 | self.session_handler.install() | 
|  | 912 | self.directory_handler.install() | 
|  | 913 | self.list_names_handler.install() | 
|  | 914 | self.list_handler.install() | 
|  | 915 | self.method_handler.install() | 
|  | 916 | self.property_handler.install() | 
|  | 917 | self.schema_handler.install() | 
| Deepak Kodihalli | 7ec0a4f | 2017-04-11 07:50:27 -0500 | [diff] [blame] | 918 | self.image_upload_post_handler.install() | 
|  | 919 | self.image_upload_put_handler.install() | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 920 | # this has to come last, since it matches everything | 
|  | 921 | self.instance_handler.install() | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 922 |  | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 923 | def install_error_callback(self, callback): | 
|  | 924 | self.error_callbacks.insert(0, callback) | 
|  | 925 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 926 | def custom_router_match(self, environ): | 
|  | 927 | ''' The built-in Bottle algorithm for figuring out if a 404 or 405 is | 
|  | 928 | needed doesn't work for us since the instance rules match | 
|  | 929 | everything. This monkey-patch lets the route handler figure | 
|  | 930 | out which response is needed.  This could be accomplished | 
|  | 931 | with a hook but that would require calling the router match | 
|  | 932 | function twice. | 
|  | 933 | ''' | 
|  | 934 | route, args = self.real_router_match(environ) | 
|  | 935 | if isinstance(route.callback, RouteHandler): | 
|  | 936 | route.callback._setup(**args) | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 937 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 938 | return route, args | 
| Brad Bishop | b1cbdaf | 2015-11-13 21:28:16 -0500 | [diff] [blame] | 939 |  | 
| Brad Bishop | 080a48e | 2017-02-21 22:34:43 -0500 | [diff] [blame] | 940 | def custom_error_handler(self, res, error): | 
|  | 941 | ''' Allow plugins to modify error reponses too via this custom | 
|  | 942 | error handler. ''' | 
|  | 943 |  | 
|  | 944 | response_object = {} | 
|  | 945 | response_body = {} | 
|  | 946 | for x in self.error_callbacks: | 
|  | 947 | x(error=error, | 
|  | 948 | response_object=response_object, | 
|  | 949 | response_body=response_body) | 
|  | 950 |  | 
|  | 951 | return response_body.get('body', "") | 
|  | 952 |  | 
| Brad Bishop | 87b63c1 | 2016-03-18 14:47:51 -0400 | [diff] [blame] | 953 | @staticmethod | 
|  | 954 | def strip_extra_slashes(): | 
|  | 955 | path = request.environ['PATH_INFO'] | 
|  | 956 | trailing = ("", "/")[path[-1] == '/'] | 
|  | 957 | parts = filter(bool, path.split('/')) | 
|  | 958 | request.environ['PATH_INFO'] = '/' + '/'.join(parts) + trailing |