Show More
@@ -21,10 +21,10 b'' | |||||
21 | import itertools |
|
21 | import itertools | |
22 | import logging |
|
22 | import logging | |
23 | import sys |
|
23 | import sys | |
24 | import types |
|
|||
25 | import fnmatch |
|
24 | import fnmatch | |
26 |
|
25 | |||
27 | import decorator |
|
26 | import decorator | |
|
27 | import typing | |||
28 | import venusian |
|
28 | import venusian | |
29 | from collections import OrderedDict |
|
29 | from collections import OrderedDict | |
30 |
|
30 | |||
@@ -39,7 +39,7 b' from rhodecode.apps._base import Templat' | |||||
39 | from rhodecode.lib.auth import AuthUser |
|
39 | from rhodecode.lib.auth import AuthUser | |
40 | from rhodecode.lib.base import get_ip_addr, attach_context_attributes |
|
40 | from rhodecode.lib.base import get_ip_addr, attach_context_attributes | |
41 | from rhodecode.lib.exc_tracking import store_exception |
|
41 | from rhodecode.lib.exc_tracking import store_exception | |
42 |
from rhodecode.lib |
|
42 | from rhodecode.lib import ext_json | |
43 | from rhodecode.lib.utils2 import safe_str |
|
43 | from rhodecode.lib.utils2 import safe_str | |
44 | from rhodecode.lib.plugins.utils import get_plugin_settings |
|
44 | from rhodecode.lib.plugins.utils import get_plugin_settings | |
45 | from rhodecode.model.db import User, UserApiKeys |
|
45 | from rhodecode.model.db import User, UserApiKeys | |
@@ -64,15 +64,12 b' def find_methods(jsonrpc_methods, patter' | |||||
64 |
|
64 | |||
65 | class ExtJsonRenderer(object): |
|
65 | class ExtJsonRenderer(object): | |
66 | """ |
|
66 | """ | |
67 |
Custom renderer that m |
|
67 | Custom renderer that makes use of our ext_json lib | |
68 |
|
68 | |||
69 | """ |
|
69 | """ | |
70 |
|
70 | |||
71 |
def __init__(self |
|
71 | def __init__(self): | |
72 | """ Any keyword arguments will be passed to the ``serializer`` |
|
72 | self.serializer = ext_json.formatted_json | |
73 | function.""" |
|
|||
74 | self.serializer = serializer |
|
|||
75 | self.kw = kw |
|
|||
76 |
|
73 | |||
77 | def __call__(self, info): |
|
74 | def __call__(self, info): | |
78 | """ Returns a plain JSON-encoded string with content-type |
|
75 | """ Returns a plain JSON-encoded string with content-type | |
@@ -87,25 +84,17 b' class ExtJsonRenderer(object):' | |||||
87 | if ct == response.default_content_type: |
|
84 | if ct == response.default_content_type: | |
88 | response.content_type = 'application/json' |
|
85 | response.content_type = 'application/json' | |
89 |
|
86 | |||
90 |
return self.serializer(value |
|
87 | return self.serializer(value) | |
91 |
|
88 | |||
92 | return _render |
|
89 | return _render | |
93 |
|
90 | |||
94 |
|
91 | |||
95 | def jsonrpc_response(request, result): |
|
92 | def jsonrpc_response(request, result): | |
96 | rpc_id = getattr(request, 'rpc_id', None) |
|
93 | rpc_id = getattr(request, 'rpc_id', None) | |
97 | response = request.response |
|
|||
98 |
|
||||
99 | # store content_type before render is called |
|
|||
100 | ct = response.content_type |
|
|||
101 |
|
94 | |||
102 | ret_value = '' |
|
95 | ret_value = '' | |
103 | if rpc_id: |
|
96 | if rpc_id: | |
104 | ret_value = { |
|
97 | ret_value = {'id': rpc_id, 'result': result, 'error': None} | |
105 | 'id': rpc_id, |
|
|||
106 | 'result': result, |
|
|||
107 | 'error': None, |
|
|||
108 | } |
|
|||
109 |
|
98 | |||
110 | # fetch deprecation warnings, and store it inside results |
|
99 | # fetch deprecation warnings, and store it inside results | |
111 | deprecation = getattr(request, 'rpc_deprecation', None) |
|
100 | deprecation = getattr(request, 'rpc_deprecation', None) | |
@@ -113,30 +102,36 b' def jsonrpc_response(request, result):' | |||||
113 | ret_value['DEPRECATION_WARNING'] = deprecation |
|
102 | ret_value['DEPRECATION_WARNING'] = deprecation | |
114 |
|
103 | |||
115 | raw_body = render(DEFAULT_RENDERER, ret_value, request=request) |
|
104 | raw_body = render(DEFAULT_RENDERER, ret_value, request=request) | |
116 | response.body = safe_str(raw_body, response.charset) |
|
105 | content_type = 'application/json' | |
117 |
|
106 | content_type_header = 'Content-Type' | ||
118 | if ct == response.default_content_type: |
|
107 | headers = { | |
119 | response.content_type = 'application/json' |
|
108 | content_type_header: content_type | |
120 |
|
109 | } | ||
121 |
return |
|
110 | return Response( | |
|
111 | body=raw_body, | |||
|
112 | content_type=content_type, | |||
|
113 | headerlist=[(k, v) for k, v in headers.items()] | |||
|
114 | ) | |||
122 |
|
115 | |||
123 |
|
116 | |||
124 | def jsonrpc_error(request, message, retid=None, code=None, headers=None): |
|
117 | def jsonrpc_error(request, message, retid=None, code: typing.Optional[int] = None, headers: typing.Optional[dict] = None): | |
125 | """ |
|
118 | """ | |
126 | Generate a Response object with a JSON-RPC error body |
|
119 | Generate a Response object with a JSON-RPC error body | |
|
120 | """ | |||
|
121 | headers = headers or {} | |||
|
122 | content_type = 'application/json' | |||
|
123 | content_type_header = 'Content-Type' | |||
|
124 | if content_type_header not in headers: | |||
|
125 | headers[content_type_header] = content_type | |||
127 |
|
126 | |||
128 | :param code: |
|
|||
129 | :param retid: |
|
|||
130 | :param message: |
|
|||
131 | """ |
|
|||
132 | err_dict = {'id': retid, 'result': None, 'error': message} |
|
127 | err_dict = {'id': retid, 'result': None, 'error': message} | |
133 |
body = render(DEFAULT_RENDERER, err_dict, request=request) |
|
128 | raw_body = render(DEFAULT_RENDERER, err_dict, request=request) | |
134 |
|
129 | |||
135 | return Response( |
|
130 | return Response( | |
136 | body=body, |
|
131 | body=raw_body, | |
137 | status=code, |
|
132 | status=code, | |
138 |
content_type= |
|
133 | content_type=content_type, | |
139 | headerlist=headers |
|
134 | headerlist=[(k, v) for k, v in headers.items()] | |
140 | ) |
|
135 | ) | |
141 |
|
136 | |||
142 |
|
137 | |||
@@ -158,11 +153,11 b' def exception_view(exc, request):' | |||||
158 | method = request.rpc_method |
|
153 | method = request.rpc_method | |
159 | log.debug('json-rpc method `%s` not found in list of ' |
|
154 | log.debug('json-rpc method `%s` not found in list of ' | |
160 | 'api calls: %s, rpc_id:%s', |
|
155 | 'api calls: %s, rpc_id:%s', | |
161 | method, request.registry.jsonrpc_methods.keys(), rpc_id) |
|
156 | method, list(request.registry.jsonrpc_methods.keys()), rpc_id) | |
162 |
|
157 | |||
163 | similar = 'none' |
|
158 | similar = 'none' | |
164 | try: |
|
159 | try: | |
165 |
similar_paterns = ['*{}*' |
|
160 | similar_paterns = [f'*{x}*' for x in method.split('_')] | |
166 | similar_found = find_methods( |
|
161 | similar_found = find_methods( | |
167 | request.registry.jsonrpc_methods, similar_paterns) |
|
162 | request.registry.jsonrpc_methods, similar_paterns) | |
168 | similar = ', '.join(similar_found.keys()) or similar |
|
163 | similar = ', '.join(similar_found.keys()) or similar | |
@@ -222,7 +217,7 b' def request_view(request):' | |||||
222 |
|
217 | |||
223 | # register our auth-user |
|
218 | # register our auth-user | |
224 | request.rpc_user = auth_u |
|
219 | request.rpc_user = auth_u | |
225 | request.environ['rc_auth_user_id'] = auth_u.user_id |
|
220 | request.environ['rc_auth_user_id'] = str(auth_u.user_id) | |
226 |
|
221 | |||
227 | # now check if token is valid for API |
|
222 | # now check if token is valid for API | |
228 | auth_token = request.rpc_api_key |
|
223 | auth_token = request.rpc_api_key | |
@@ -246,10 +241,12 b' def request_view(request):' | |||||
246 |
|
241 | |||
247 | # now that we have a method, add request._req_params to |
|
242 | # now that we have a method, add request._req_params to | |
248 | # self.kargs and dispatch control to WGIController |
|
243 | # self.kargs and dispatch control to WGIController | |
|
244 | ||||
249 | argspec = inspect.getargspec(func) |
|
245 | argspec = inspect.getargspec(func) | |
250 | arglist = argspec[0] |
|
246 | arglist = argspec[0] | |
251 |
def |
|
247 | defs = argspec[3] or [] | |
252 | default_empty = types.NotImplementedType |
|
248 | defaults = [type(a) for a in defs] | |
|
249 | default_empty = type(NotImplemented) | |||
253 |
|
250 | |||
254 | # kw arguments required by this method |
|
251 | # kw arguments required by this method | |
255 | func_kwargs = dict(itertools.zip_longest( |
|
252 | func_kwargs = dict(itertools.zip_longest( | |
@@ -285,7 +282,7 b' def request_view(request):' | |||||
285 | ) |
|
282 | ) | |
286 |
|
283 | |||
287 | # sanitize extra passed arguments |
|
284 | # sanitize extra passed arguments | |
288 |
for k in request.rpc_params.keys() |
|
285 | for k in list(request.rpc_params.keys()): | |
289 | if k not in func_kwargs: |
|
286 | if k not in func_kwargs: | |
290 | del request.rpc_params[k] |
|
287 | del request.rpc_params[k] | |
291 |
|
288 | |||
@@ -313,8 +310,10 b' def request_view(request):' | |||||
313 | exc_info = sys.exc_info() |
|
310 | exc_info = sys.exc_info() | |
314 | exc_id, exc_type_name = store_exception( |
|
311 | exc_id, exc_type_name = store_exception( | |
315 | id(exc_info), exc_info, prefix='rhodecode-api') |
|
312 | id(exc_info), exc_info, prefix='rhodecode-api') | |
316 | error_headers = [('RhodeCode-Exception-Id', str(exc_id)), |
|
313 | error_headers = { | |
317 |
|
|
314 | 'RhodeCode-Exception-Id': str(exc_id), | |
|
315 | 'RhodeCode-Exception-Type': str(exc_type_name) | |||
|
316 | } | |||
318 | err_resp = jsonrpc_error( |
|
317 | err_resp = jsonrpc_error( | |
319 | request, retid=request.rpc_id, message='Internal server error', |
|
318 | request, retid=request.rpc_id, message='Internal server error', | |
320 | headers=error_headers) |
|
319 | headers=error_headers) | |
@@ -355,7 +354,7 b' def setup_request(request):' | |||||
355 | raw_body = request.body |
|
354 | raw_body = request.body | |
356 | log.debug("Loading JSON body now") |
|
355 | log.debug("Loading JSON body now") | |
357 | try: |
|
356 | try: | |
358 | json_body = json.loads(raw_body) |
|
357 | json_body = ext_json.json.loads(raw_body) | |
359 | except ValueError as e: |
|
358 | except ValueError as e: | |
360 | # catch JSON errors Here |
|
359 | # catch JSON errors Here | |
361 | raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body)) |
|
360 | raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body)) | |
@@ -382,7 +381,7 b' def setup_request(request):' | |||||
382 |
|
381 | |||
383 | log.debug('method: %s, params: %.10240r', request.rpc_method, request.rpc_params) |
|
382 | log.debug('method: %s, params: %.10240r', request.rpc_method, request.rpc_params) | |
384 | except KeyError as e: |
|
383 | except KeyError as e: | |
385 |
raise JSONRPCError('Incorrect JSON data. Missing |
|
384 | raise JSONRPCError(f'Incorrect JSON data. Missing {e}') | |
386 |
|
385 | |||
387 | log.debug('setup complete, now handling method:%s rpcid:%s', |
|
386 | log.debug('setup complete, now handling method:%s rpcid:%s', | |
388 | request.rpc_method, request.rpc_id, ) |
|
387 | request.rpc_method, request.rpc_id, ) | |
@@ -561,8 +560,7 b' def includeme(config):' | |||||
561 | config.add_view_predicate('jsonrpc_method', MethodPredicate) |
|
560 | config.add_view_predicate('jsonrpc_method', MethodPredicate) | |
562 | config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate) |
|
561 | config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate) | |
563 |
|
562 | |||
564 | config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer( |
|
563 | config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer()) | |
565 | serializer=json.dumps, indent=4)) |
|
|||
566 | config.add_directive('add_jsonrpc_method', add_jsonrpc_method) |
|
564 | config.add_directive('add_jsonrpc_method', add_jsonrpc_method) | |
567 |
|
565 | |||
568 | config.add_route_predicate( |
|
566 | config.add_route_predicate( |
@@ -63,7 +63,7 b' class TestApi(object):' | |||||
63 |
|
63 | |||
64 | def test_api_missing_non_optional_param_args_null(self): |
|
64 | def test_api_missing_non_optional_param_args_null(self): | |
65 | id_, params = build_data(self.apikey, 'get_repo') |
|
65 | id_, params = build_data(self.apikey, 'get_repo') | |
66 | params = params.replace('"args": {}', '"args": null') |
|
66 | params = params.replace(b'"args": {}', b'"args": null') | |
67 | response = api_call(self.app, params) |
|
67 | response = api_call(self.app, params) | |
68 |
|
68 | |||
69 | expected = 'Missing non optional `repoid` arg in JSON DATA' |
|
69 | expected = 'Missing non optional `repoid` arg in JSON DATA' | |
@@ -71,7 +71,7 b' class TestApi(object):' | |||||
71 |
|
71 | |||
72 | def test_api_missing_non_optional_param_args_bad(self): |
|
72 | def test_api_missing_non_optional_param_args_bad(self): | |
73 | id_, params = build_data(self.apikey, 'get_repo') |
|
73 | id_, params = build_data(self.apikey, 'get_repo') | |
74 | params = params.replace('"args": {}', '"args": 1') |
|
74 | params = params.replace(b'"args": {}', b'"args": 1') | |
75 | response = api_call(self.app, params) |
|
75 | response = api_call(self.app, params) | |
76 |
|
76 | |||
77 | expected = 'Missing non optional `repoid` arg in JSON DATA' |
|
77 | expected = 'Missing non optional `repoid` arg in JSON DATA' | |
@@ -111,13 +111,13 b' class TestApi(object):' | |||||
111 |
|
111 | |||
112 | def test_api_args_is_null(self): |
|
112 | def test_api_args_is_null(self): | |
113 | __, params = build_data(self.apikey, 'get_users', ) |
|
113 | __, params = build_data(self.apikey, 'get_users', ) | |
114 | params = params.replace('"args": {}', '"args": null') |
|
114 | params = params.replace(b'"args": {}', b'"args": null') | |
115 | response = api_call(self.app, params) |
|
115 | response = api_call(self.app, params) | |
116 | assert response.status == '200 OK' |
|
116 | assert response.status == '200 OK' | |
117 |
|
117 | |||
118 | def test_api_args_is_bad(self): |
|
118 | def test_api_args_is_bad(self): | |
119 | __, params = build_data(self.apikey, 'get_users', ) |
|
119 | __, params = build_data(self.apikey, 'get_users', ) | |
120 | params = params.replace('"args": {}', '"args": 1') |
|
120 | params = params.replace(b'"args": {}', b'"args": 1') | |
121 | response = api_call(self.app, params) |
|
121 | response = api_call(self.app, params) | |
122 | assert response.status == '200 OK' |
|
122 | assert response.status == '200 OK' | |
123 |
|
123 |
@@ -86,7 +86,8 b' def build_data(apikey, method, **kw):' | |||||
86 |
|
86 | |||
87 | def api_call(app, params, status=None): |
|
87 | def api_call(app, params, status=None): | |
88 | response = app.post( |
|
88 | response = app.post( | |
89 |
API_URL, content_type='application/json', params=params, status=status |
|
89 | API_URL, content_type='application/json', params=params, status=status, | |
|
90 | headers=[('Content-Type', 'application/json')]) | |||
90 | return response |
|
91 | return response | |
91 |
|
92 | |||
92 |
|
93 |
@@ -470,6 +470,8 b' def get_repo_nodes(request, apiuser, rep' | |||||
470 |
|
470 | |||
471 | ret_type = Optional.extract(ret_type) |
|
471 | ret_type = Optional.extract(ret_type) | |
472 | details = Optional.extract(details) |
|
472 | details = Optional.extract(details) | |
|
473 | max_file_bytes = Optional.extract(max_file_bytes) | |||
|
474 | ||||
473 | _extended_types = ['basic', 'full'] |
|
475 | _extended_types = ['basic', 'full'] | |
474 | if details not in _extended_types: |
|
476 | if details not in _extended_types: | |
475 | raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types))) |
|
477 | raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types))) | |
@@ -492,6 +494,7 b' def get_repo_nodes(request, apiuser, rep' | |||||
492 | repo, revision, root_path, flat=False, |
|
494 | repo, revision, root_path, flat=False, | |
493 | extended_info=extended_info, content=content, |
|
495 | extended_info=extended_info, content=content, | |
494 | max_file_bytes=max_file_bytes) |
|
496 | max_file_bytes=max_file_bytes) | |
|
497 | ||||
495 | _map = { |
|
498 | _map = { | |
496 | 'all': _d + _f, |
|
499 | 'all': _d + _f, | |
497 | 'files': _f, |
|
500 | 'files': _f, |
@@ -347,7 +347,7 b' def get_method(request, apiuser, pattern' | |||||
347 |
|
347 | |||
348 | argspec = inspect.getargspec(func) |
|
348 | argspec = inspect.getargspec(func) | |
349 | arglist = argspec[0] |
|
349 | arglist = argspec[0] | |
350 | defaults = map(repr, argspec[3] or []) |
|
350 | defaults = list(map(repr, argspec[3] or [])) | |
351 |
|
351 | |||
352 | default_empty = '<RequiredType>' |
|
352 | default_empty = '<RequiredType>' | |
353 |
|
353 |
@@ -39,7 +39,7 b' def test(request, apiuser, args):' | |||||
39 | @jsonrpc_method() |
|
39 | @jsonrpc_method() | |
40 | def test_ok(request, apiuser): |
|
40 | def test_ok(request, apiuser): | |
41 | return { |
|
41 | return { | |
42 |
'who': |
|
42 | 'who': f'hello {apiuser}', | |
43 | 'obj': { |
|
43 | 'obj': { | |
44 | 'time': time.time(), |
|
44 | 'time': time.time(), | |
45 | 'dt': datetime.datetime.now(), |
|
45 | 'dt': datetime.datetime.now(), | |
@@ -55,12 +55,15 b' def test_error(request, apiuser):' | |||||
55 |
|
55 | |||
56 | @jsonrpc_method() |
|
56 | @jsonrpc_method() | |
57 | def test_exception(request, apiuser): |
|
57 | def test_exception(request, apiuser): | |
58 |
raise Exception('something unhand |
|
58 | raise Exception('something unhandled') | |
59 |
|
59 | |||
60 |
|
60 | |||
61 | @jsonrpc_method() |
|
61 | @jsonrpc_method() | |
62 | def test_params(request, apiuser, params): |
|
62 | def test_params(request, apiuser, params): | |
63 | return u'hello apiuser:{} params:{}'.format(apiuser, params) |
|
63 | return { | |
|
64 | 'who': f'hello {apiuser}', | |||
|
65 | 'params': params | |||
|
66 | } | |||
64 |
|
67 | |||
65 |
|
68 | |||
66 | @jsonrpc_method() |
|
69 | @jsonrpc_method() | |
@@ -69,16 +72,20 b' def test_params_opt(' | |||||
69 | opt3=Optional(OAttr('apiuser'))): |
|
72 | opt3=Optional(OAttr('apiuser'))): | |
70 | opt2 = Optional.extract(opt2) |
|
73 | opt2 = Optional.extract(opt2) | |
71 | opt3 = Optional.extract(opt3, evaluate_locals=locals()) |
|
74 | opt3 = Optional.extract(opt3, evaluate_locals=locals()) | |
72 |
|
75 | return { | ||
73 | return u'hello apiuser:{} params:{}, opt:[{},{},{}]'.format( |
|
76 | 'who': f'hello {apiuser}', | |
74 | apiuser, params, opt1, opt2, opt3) |
|
77 | 'params': params, | |
|
78 | 'opts': [ | |||
|
79 | opt1, opt2, opt3 | |||
|
80 | ] | |||
|
81 | } | |||
75 |
|
82 | |||
76 |
|
83 | |||
77 | @jsonrpc_method() |
|
84 | @jsonrpc_method() | |
78 | @jsonrpc_deprecated_method( |
|
85 | @jsonrpc_deprecated_method( | |
79 | use_method='test_ok', deprecated_at_version='4.0.0') |
|
86 | use_method='test_ok', deprecated_at_version='4.0.0') | |
80 | def test_deprecated_method(request, apiuser): |
|
87 | def test_deprecated_method(request, apiuser): | |
81 |
return |
|
88 | return 'value' | |
82 |
|
89 | |||
83 |
|
90 | |||
84 | @jsonrpc_method() |
|
91 | @jsonrpc_method() |
General Comments 0
You need to be logged in to leave comments.
Login now