Mercurial > kallithea
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) |