comparison kallithea/controllers/api/__init__.py @ 6376:dc94e662ee74

tg: refactor API JSON RPC error handling to prepare for TG
author Mads Kiilerich <mads@kiilerich.com>
date Fri, 23 Dec 2016 23:39:36 +0100
parents 8be0633ff852
children 3dcf1f82311a
comparison
equal deleted inserted replaced
6375:692dddf298e2 6376:dc94e662ee74
32 import time 32 import time
33 import itertools 33 import itertools
34 34
35 from paste.response import replace_header 35 from paste.response import replace_header
36 from pylons.controllers import WSGIController 36 from pylons.controllers import WSGIController
37 from pylons.controllers.util import Response
37 from pylons import request 38 from pylons import request
38 39
39 from webob.exc import HTTPError 40 from webob.exc import HTTPError
40 41
41 from kallithea.model.db import User 42 from kallithea.model.db import User
56 57
57 def __str__(self): 58 def __str__(self):
58 return safe_str(self.message) 59 return safe_str(self.message)
59 60
60 61
61 def jsonrpc_error(message, retid=None, code=None): 62 class JSONRPCErrorResponse(Response, Exception):
62 """ 63 """
63 Generate a Response object with a JSON-RPC error body 64 Generate a Response object with a JSON-RPC error body
64
65 :param code:
66 :param retid:
67 :param message:
68 """ 65 """
69 from pylons.controllers.util import Response 66
70 return Response( 67 def __init__(self, message=None, retid=None, code=None):
71 body=json.dumps(dict(id=retid, result=None, error=message)), 68 Response.__init__(self,
72 status=code, 69 body=json.dumps(dict(id=retid, result=None, error=message)),
73 content_type='application/json' 70 status=code,
74 ) 71 content_type='application/json')
75 72
76 73
77 class JSONRPCController(WSGIController): 74 class JSONRPCController(WSGIController):
78 """ 75 """
79 A WSGI-speaking JSON-RPC controller class 76 A WSGI-speaking JSON-RPC controller class
103 Parse the request body as JSON, look up the method on the 100 Parse the request body as JSON, look up the method on the
104 controller and if it exists, dispatch to it. 101 controller and if it exists, dispatch to it.
105 """ 102 """
106 try: 103 try:
107 return self._handle_request(environ, start_response) 104 return self._handle_request(environ, start_response)
105 except JSONRPCErrorResponse as e:
106 return e
108 finally: 107 finally:
109 meta.Session.remove() 108 meta.Session.remove()
110 109
111 def _handle_request(self, environ, start_response): 110 def _handle_request(self, environ, start_response):
112 start = time.time() 111 start = time.time()
113 ip_addr = self.ip_addr = self._get_ip_addr(environ) 112 ip_addr = self.ip_addr = self._get_ip_addr(environ)
114 self._req_id = None 113 self._req_id = None
115 if 'CONTENT_LENGTH' not in environ: 114 if 'CONTENT_LENGTH' not in environ:
116 log.debug("No Content-Length") 115 log.debug("No Content-Length")
117 return jsonrpc_error(retid=self._req_id, 116 raise JSONRPCErrorResponse(retid=self._req_id,
118 message="No Content-Length in request") 117 message="No Content-Length in request")
119 else: 118 else:
120 length = environ['CONTENT_LENGTH'] or 0 119 length = environ['CONTENT_LENGTH'] or 0
121 length = int(environ['CONTENT_LENGTH']) 120 length = int(environ['CONTENT_LENGTH'])
122 log.debug('Content-Length: %s', length) 121 log.debug('Content-Length: %s', length)
123 122
124 if length == 0: 123 if length == 0:
125 return jsonrpc_error(retid=self._req_id, 124 raise JSONRPCErrorResponse(retid=self._req_id,
126 message="Content-Length is 0") 125 message="Content-Length is 0")
127 126
128 raw_body = environ['wsgi.input'].read(length) 127 raw_body = environ['wsgi.input'].read(length)
129 128
130 try: 129 try:
131 json_body = json.loads(raw_body) 130 json_body = json.loads(raw_body)
132 except ValueError as e: 131 except ValueError as e:
133 # catch JSON errors Here 132 # catch JSON errors Here
134 return jsonrpc_error(retid=self._req_id, 133 raise JSONRPCErrorResponse(retid=self._req_id,
135 message="JSON parse error ERR:%s RAW:%r" 134 message="JSON parse error ERR:%s RAW:%r"
136 % (e, raw_body)) 135 % (e, raw_body))
137 136
138 # check AUTH based on API key 137 # check AUTH based on API key
139 try: 138 try:
140 self._req_api_key = json_body['api_key'] 139 self._req_api_key = json_body['api_key']
141 self._req_id = json_body['id'] 140 self._req_id = json_body['id']
142 self._req_method = json_body['method'] 141 self._req_method = json_body['method']
143 self._request_params = json_body['args'] 142 self._request_params = json_body['args']
144 if not isinstance(self._request_params, dict): 143 if not isinstance(self._request_params, dict):
145 self._request_params = {} 144 self._request_params = {}
146 145
147 log.debug( 146 log.debug('method: %s, params: %s',
148 'method: %s, params: %s', self._req_method, 147 self._req_method, self._request_params)
149 self._request_params
150 )
151 except KeyError as e: 148 except KeyError as e:
152 return jsonrpc_error(retid=self._req_id, 149 raise JSONRPCErrorResponse(retid=self._req_id,
153 message='Incorrect JSON query missing %s' % e) 150 message='Incorrect JSON query missing %s' % e)
154 151
155 # check if we can find this session using api_key 152 # check if we can find this session using api_key
156 try: 153 try:
157 u = User.get_by_api_key(self._req_api_key) 154 u = User.get_by_api_key(self._req_api_key)
158 if u is None: 155 if u is None:
159 return jsonrpc_error(retid=self._req_id, 156 raise JSONRPCErrorResponse(retid=self._req_id,
160 message='Invalid API key') 157 message='Invalid API key')
161 158
162 auth_u = AuthUser(dbuser=u) 159 auth_u = AuthUser(dbuser=u)
163 if not AuthUser.check_ip_allowed(auth_u, ip_addr): 160 if not AuthUser.check_ip_allowed(auth_u, ip_addr):
164 return jsonrpc_error(retid=self._req_id, 161 raise JSONRPCErrorResponse(retid=self._req_id,
165 message='request from IP:%s not allowed' % (ip_addr,)) 162 message='request from IP:%s not allowed' % (ip_addr,))
166 else: 163 else:
167 log.info('Access for IP:%s allowed', ip_addr) 164 log.info('Access for IP:%s allowed', ip_addr)
168 165
169 except Exception as e: 166 except Exception as e:
170 return jsonrpc_error(retid=self._req_id, 167 raise JSONRPCErrorResponse(retid=self._req_id,
171 message='Invalid API key') 168 message='Invalid API key')
172 169
173 self._error = None 170 self._error = None
174 try: 171 try:
175 self._func = self._find_method() 172 self._func = self._find_method()
176 except AttributeError as e: 173 except AttributeError as e:
177 return jsonrpc_error(retid=self._req_id, 174 raise JSONRPCErrorResponse(retid=self._req_id,
178 message=str(e)) 175 message=str(e))
179 176
180 # now that we have a method, add self._req_params to 177 # now that we have a method, add self._req_params to
181 # self.kargs and dispatch control to WGIController 178 # self.kargs and dispatch control to WGIController
182 argspec = inspect.getargspec(self._func) 179 argspec = inspect.getargspec(self._func)
183 arglist = argspec[0][1:] 180 arglist = argspec[0][1:]
205 continue 202 continue
206 203
207 # skip the required param check if it's default value is 204 # skip the required param check if it's default value is
208 # NotImplementedType (default_empty) 205 # NotImplementedType (default_empty)
209 if default == default_empty and arg not in self._request_params: 206 if default == default_empty and arg not in self._request_params:
210 return jsonrpc_error( 207 raise JSONRPCErrorResponse(
211 retid=self._req_id, 208 retid=self._req_id,
212 message=( 209 message='Missing non optional `%s` arg in JSON DATA' % arg,
213 'Missing non optional `%s` arg in JSON DATA' % arg
214 )
215 ) 210 )
216 211
217 extra = set(self._request_params).difference(func_kwargs) 212 extra = set(self._request_params).difference(func_kwargs)
218 if extra: 213 if extra:
219 return jsonrpc_error( 214 raise JSONRPCErrorResponse(
220 retid=self._req_id, 215 retid=self._req_id,
221 message=('Unknown %s arg in JSON DATA' % 216 message='Unknown %s arg in JSON DATA' %
222 ', '.join('`%s`' % arg for arg in extra)), 217 ', '.join('`%s`' % arg for arg in extra),
223 ) 218 )
224 219
225 self._rpc_args = {} 220 self._rpc_args = {}
226 221
227 self._rpc_args.update(self._request_params) 222 self._rpc_args.update(self._request_params)