##// END OF EJS Templates
api: attach the call context variables to request for later usage...
dan -
r1794:1c6b274b default
parent child Browse files
Show More
@@ -1,536 +1,542 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import inspect
21 import inspect
22 import itertools
22 import itertools
23 import logging
23 import logging
24 import types
24 import types
25 import fnmatch
25 import fnmatch
26
26
27 import decorator
27 import decorator
28 import venusian
28 import venusian
29 from collections import OrderedDict
29 from collections import OrderedDict
30
30
31 from pyramid.exceptions import ConfigurationError
31 from pyramid.exceptions import ConfigurationError
32 from pyramid.renderers import render
32 from pyramid.renderers import render
33 from pyramid.response import Response
33 from pyramid.response import Response
34 from pyramid.httpexceptions import HTTPNotFound
34 from pyramid.httpexceptions import HTTPNotFound
35
35
36 from rhodecode.api.exc import (
36 from rhodecode.api.exc import (
37 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
37 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
38 from rhodecode.apps._base import TemplateArgs
38 from rhodecode.lib.auth import AuthUser
39 from rhodecode.lib.auth import AuthUser
39 from rhodecode.lib.base import get_ip_addr
40 from rhodecode.lib.base import get_ip_addr, attach_context_attributes
40 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.utils2 import safe_str
42 from rhodecode.lib.utils2 import safe_str
42 from rhodecode.lib.plugins.utils import get_plugin_settings
43 from rhodecode.lib.plugins.utils import get_plugin_settings
43 from rhodecode.model.db import User, UserApiKeys
44 from rhodecode.model.db import User, UserApiKeys
44
45
45 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
46
47
47 DEFAULT_RENDERER = 'jsonrpc_renderer'
48 DEFAULT_RENDERER = 'jsonrpc_renderer'
48 DEFAULT_URL = '/_admin/apiv2'
49 DEFAULT_URL = '/_admin/apiv2'
49
50
50
51
51 def find_methods(jsonrpc_methods, pattern):
52 def find_methods(jsonrpc_methods, pattern):
52 matches = OrderedDict()
53 matches = OrderedDict()
53 if not isinstance(pattern, (list, tuple)):
54 if not isinstance(pattern, (list, tuple)):
54 pattern = [pattern]
55 pattern = [pattern]
55
56
56 for single_pattern in pattern:
57 for single_pattern in pattern:
57 for method_name, method in jsonrpc_methods.items():
58 for method_name, method in jsonrpc_methods.items():
58 if fnmatch.fnmatch(method_name, single_pattern):
59 if fnmatch.fnmatch(method_name, single_pattern):
59 matches[method_name] = method
60 matches[method_name] = method
60 return matches
61 return matches
61
62
62
63
63 class ExtJsonRenderer(object):
64 class ExtJsonRenderer(object):
64 """
65 """
65 Custom renderer that mkaes use of our ext_json lib
66 Custom renderer that mkaes use of our ext_json lib
66
67
67 """
68 """
68
69
69 def __init__(self, serializer=json.dumps, **kw):
70 def __init__(self, serializer=json.dumps, **kw):
70 """ Any keyword arguments will be passed to the ``serializer``
71 """ Any keyword arguments will be passed to the ``serializer``
71 function."""
72 function."""
72 self.serializer = serializer
73 self.serializer = serializer
73 self.kw = kw
74 self.kw = kw
74
75
75 def __call__(self, info):
76 def __call__(self, info):
76 """ Returns a plain JSON-encoded string with content-type
77 """ Returns a plain JSON-encoded string with content-type
77 ``application/json``. The content-type may be overridden by
78 ``application/json``. The content-type may be overridden by
78 setting ``request.response.content_type``."""
79 setting ``request.response.content_type``."""
79
80
80 def _render(value, system):
81 def _render(value, system):
81 request = system.get('request')
82 request = system.get('request')
82 if request is not None:
83 if request is not None:
83 response = request.response
84 response = request.response
84 ct = response.content_type
85 ct = response.content_type
85 if ct == response.default_content_type:
86 if ct == response.default_content_type:
86 response.content_type = 'application/json'
87 response.content_type = 'application/json'
87
88
88 return self.serializer(value, **self.kw)
89 return self.serializer(value, **self.kw)
89
90
90 return _render
91 return _render
91
92
92
93
93 def jsonrpc_response(request, result):
94 def jsonrpc_response(request, result):
94 rpc_id = getattr(request, 'rpc_id', None)
95 rpc_id = getattr(request, 'rpc_id', None)
95 response = request.response
96 response = request.response
96
97
97 # store content_type before render is called
98 # store content_type before render is called
98 ct = response.content_type
99 ct = response.content_type
99
100
100 ret_value = ''
101 ret_value = ''
101 if rpc_id:
102 if rpc_id:
102 ret_value = {
103 ret_value = {
103 'id': rpc_id,
104 'id': rpc_id,
104 'result': result,
105 'result': result,
105 'error': None,
106 'error': None,
106 }
107 }
107
108
108 # fetch deprecation warnings, and store it inside results
109 # fetch deprecation warnings, and store it inside results
109 deprecation = getattr(request, 'rpc_deprecation', None)
110 deprecation = getattr(request, 'rpc_deprecation', None)
110 if deprecation:
111 if deprecation:
111 ret_value['DEPRECATION_WARNING'] = deprecation
112 ret_value['DEPRECATION_WARNING'] = deprecation
112
113
113 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
114 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
114 response.body = safe_str(raw_body, response.charset)
115 response.body = safe_str(raw_body, response.charset)
115
116
116 if ct == response.default_content_type:
117 if ct == response.default_content_type:
117 response.content_type = 'application/json'
118 response.content_type = 'application/json'
118
119
119 return response
120 return response
120
121
121
122
122 def jsonrpc_error(request, message, retid=None, code=None):
123 def jsonrpc_error(request, message, retid=None, code=None):
123 """
124 """
124 Generate a Response object with a JSON-RPC error body
125 Generate a Response object with a JSON-RPC error body
125
126
126 :param code:
127 :param code:
127 :param retid:
128 :param retid:
128 :param message:
129 :param message:
129 """
130 """
130 err_dict = {'id': retid, 'result': None, 'error': message}
131 err_dict = {'id': retid, 'result': None, 'error': message}
131 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
132 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
132 return Response(
133 return Response(
133 body=body,
134 body=body,
134 status=code,
135 status=code,
135 content_type='application/json'
136 content_type='application/json'
136 )
137 )
137
138
138
139
139 def exception_view(exc, request):
140 def exception_view(exc, request):
140 rpc_id = getattr(request, 'rpc_id', None)
141 rpc_id = getattr(request, 'rpc_id', None)
141
142
142 fault_message = 'undefined error'
143 fault_message = 'undefined error'
143 if isinstance(exc, JSONRPCError):
144 if isinstance(exc, JSONRPCError):
144 fault_message = exc.message
145 fault_message = exc.message
145 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
146 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
146 elif isinstance(exc, JSONRPCValidationError):
147 elif isinstance(exc, JSONRPCValidationError):
147 colander_exc = exc.colander_exception
148 colander_exc = exc.colander_exception
148 # TODO(marcink): think maybe of nicer way to serialize errors ?
149 # TODO(marcink): think maybe of nicer way to serialize errors ?
149 fault_message = colander_exc.asdict()
150 fault_message = colander_exc.asdict()
150 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
151 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
151 elif isinstance(exc, JSONRPCForbidden):
152 elif isinstance(exc, JSONRPCForbidden):
152 fault_message = 'Access was denied to this resource.'
153 fault_message = 'Access was denied to this resource.'
153 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
154 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
154 elif isinstance(exc, HTTPNotFound):
155 elif isinstance(exc, HTTPNotFound):
155 method = request.rpc_method
156 method = request.rpc_method
156 log.debug('json-rpc method `%s` not found in list of '
157 log.debug('json-rpc method `%s` not found in list of '
157 'api calls: %s, rpc_id:%s',
158 'api calls: %s, rpc_id:%s',
158 method, request.registry.jsonrpc_methods.keys(), rpc_id)
159 method, request.registry.jsonrpc_methods.keys(), rpc_id)
159
160
160 similar = 'none'
161 similar = 'none'
161 try:
162 try:
162 similar_paterns = ['*{}*'.format(x) for x in method.split('_')]
163 similar_paterns = ['*{}*'.format(x) for x in method.split('_')]
163 similar_found = find_methods(
164 similar_found = find_methods(
164 request.registry.jsonrpc_methods, similar_paterns)
165 request.registry.jsonrpc_methods, similar_paterns)
165 similar = ', '.join(similar_found.keys()) or similar
166 similar = ', '.join(similar_found.keys()) or similar
166 except Exception:
167 except Exception:
167 # make the whole above block safe
168 # make the whole above block safe
168 pass
169 pass
169
170
170 fault_message = "No such method: {}. Similar methods: {}".format(
171 fault_message = "No such method: {}. Similar methods: {}".format(
171 method, similar)
172 method, similar)
172
173
173 return jsonrpc_error(request, fault_message, rpc_id)
174 return jsonrpc_error(request, fault_message, rpc_id)
174
175
175
176
176 def request_view(request):
177 def request_view(request):
177 """
178 """
178 Main request handling method. It handles all logic to call a specific
179 Main request handling method. It handles all logic to call a specific
179 exposed method
180 exposed method
180 """
181 """
181
182
182 # check if we can find this session using api_key, get_by_auth_token
183 # check if we can find this session using api_key, get_by_auth_token
183 # search not expired tokens only
184 # search not expired tokens only
184
185
185 try:
186 try:
186 api_user = User.get_by_auth_token(request.rpc_api_key)
187 api_user = User.get_by_auth_token(request.rpc_api_key)
187
188
188 if api_user is None:
189 if api_user is None:
189 return jsonrpc_error(
190 return jsonrpc_error(
190 request, retid=request.rpc_id, message='Invalid API KEY')
191 request, retid=request.rpc_id, message='Invalid API KEY')
191
192
192 if not api_user.active:
193 if not api_user.active:
193 return jsonrpc_error(
194 return jsonrpc_error(
194 request, retid=request.rpc_id,
195 request, retid=request.rpc_id,
195 message='Request from this user not allowed')
196 message='Request from this user not allowed')
196
197
197 # check if we are allowed to use this IP
198 # check if we are allowed to use this IP
198 auth_u = AuthUser(
199 auth_u = AuthUser(
199 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
200 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
200 if not auth_u.ip_allowed:
201 if not auth_u.ip_allowed:
201 return jsonrpc_error(
202 return jsonrpc_error(
202 request, retid=request.rpc_id,
203 request, retid=request.rpc_id,
203 message='Request from IP:%s not allowed' % (
204 message='Request from IP:%s not allowed' % (
204 request.rpc_ip_addr,))
205 request.rpc_ip_addr,))
205 else:
206 else:
206 log.info('Access for IP:%s allowed' % (request.rpc_ip_addr,))
207 log.info('Access for IP:%s allowed' % (request.rpc_ip_addr,))
207
208
208 # register our auth-user
209 # register our auth-user
209 request.rpc_user = auth_u
210 request.rpc_user = auth_u
210
211
211 # now check if token is valid for API
212 # now check if token is valid for API
212 auth_token = request.rpc_api_key
213 auth_token = request.rpc_api_key
213 token_match = api_user.authenticate_by_token(
214 token_match = api_user.authenticate_by_token(
214 auth_token, roles=[UserApiKeys.ROLE_API])
215 auth_token, roles=[UserApiKeys.ROLE_API])
215 invalid_token = not token_match
216 invalid_token = not token_match
216
217
217 log.debug('Checking if API KEY is valid with proper role')
218 log.debug('Checking if API KEY is valid with proper role')
218 if invalid_token:
219 if invalid_token:
219 return jsonrpc_error(
220 return jsonrpc_error(
220 request, retid=request.rpc_id,
221 request, retid=request.rpc_id,
221 message='API KEY invalid or, has bad role for an API call')
222 message='API KEY invalid or, has bad role for an API call')
222
223
223 except Exception:
224 except Exception:
224 log.exception('Error on API AUTH')
225 log.exception('Error on API AUTH')
225 return jsonrpc_error(
226 return jsonrpc_error(
226 request, retid=request.rpc_id, message='Invalid API KEY')
227 request, retid=request.rpc_id, message='Invalid API KEY')
227
228
228 method = request.rpc_method
229 method = request.rpc_method
229 func = request.registry.jsonrpc_methods[method]
230 func = request.registry.jsonrpc_methods[method]
230
231
231 # now that we have a method, add request._req_params to
232 # now that we have a method, add request._req_params to
232 # self.kargs and dispatch control to WGIController
233 # self.kargs and dispatch control to WGIController
233 argspec = inspect.getargspec(func)
234 argspec = inspect.getargspec(func)
234 arglist = argspec[0]
235 arglist = argspec[0]
235 defaults = map(type, argspec[3] or [])
236 defaults = map(type, argspec[3] or [])
236 default_empty = types.NotImplementedType
237 default_empty = types.NotImplementedType
237
238
238 # kw arguments required by this method
239 # kw arguments required by this method
239 func_kwargs = dict(itertools.izip_longest(
240 func_kwargs = dict(itertools.izip_longest(
240 reversed(arglist), reversed(defaults), fillvalue=default_empty))
241 reversed(arglist), reversed(defaults), fillvalue=default_empty))
241
242
242 # This attribute will need to be first param of a method that uses
243 # This attribute will need to be first param of a method that uses
243 # api_key, which is translated to instance of user at that name
244 # api_key, which is translated to instance of user at that name
244 user_var = 'apiuser'
245 user_var = 'apiuser'
245 request_var = 'request'
246 request_var = 'request'
246
247
247 for arg in [user_var, request_var]:
248 for arg in [user_var, request_var]:
248 if arg not in arglist:
249 if arg not in arglist:
249 return jsonrpc_error(
250 return jsonrpc_error(
250 request,
251 request,
251 retid=request.rpc_id,
252 retid=request.rpc_id,
252 message='This method [%s] does not support '
253 message='This method [%s] does not support '
253 'required parameter `%s`' % (func.__name__, arg))
254 'required parameter `%s`' % (func.__name__, arg))
254
255
255 # get our arglist and check if we provided them as args
256 # get our arglist and check if we provided them as args
256 for arg, default in func_kwargs.items():
257 for arg, default in func_kwargs.items():
257 if arg in [user_var, request_var]:
258 if arg in [user_var, request_var]:
258 # user_var and request_var are pre-hardcoded parameters and we
259 # user_var and request_var are pre-hardcoded parameters and we
259 # don't need to do any translation
260 # don't need to do any translation
260 continue
261 continue
261
262
262 # skip the required param check if it's default value is
263 # skip the required param check if it's default value is
263 # NotImplementedType (default_empty)
264 # NotImplementedType (default_empty)
264 if default == default_empty and arg not in request.rpc_params:
265 if default == default_empty and arg not in request.rpc_params:
265 return jsonrpc_error(
266 return jsonrpc_error(
266 request,
267 request,
267 retid=request.rpc_id,
268 retid=request.rpc_id,
268 message=('Missing non optional `%s` arg in JSON DATA' % arg)
269 message=('Missing non optional `%s` arg in JSON DATA' % arg)
269 )
270 )
270
271
271 # sanitize extra passed arguments
272 # sanitize extra passed arguments
272 for k in request.rpc_params.keys()[:]:
273 for k in request.rpc_params.keys()[:]:
273 if k not in func_kwargs:
274 if k not in func_kwargs:
274 del request.rpc_params[k]
275 del request.rpc_params[k]
275
276
276 call_params = request.rpc_params
277 call_params = request.rpc_params
277 call_params.update({
278 call_params.update({
278 'request': request,
279 'request': request,
279 'apiuser': auth_u
280 'apiuser': auth_u
280 })
281 })
282
283 # register some common functions for usage
284 attach_context_attributes(TemplateArgs(), request, request.rpc_user.user_id,
285 attach_to_request=True)
286
281 try:
287 try:
282 ret_value = func(**call_params)
288 ret_value = func(**call_params)
283 return jsonrpc_response(request, ret_value)
289 return jsonrpc_response(request, ret_value)
284 except JSONRPCBaseError:
290 except JSONRPCBaseError:
285 raise
291 raise
286 except Exception:
292 except Exception:
287 log.exception('Unhandled exception occurred on api call: %s', func)
293 log.exception('Unhandled exception occurred on api call: %s', func)
288 return jsonrpc_error(request, retid=request.rpc_id,
294 return jsonrpc_error(request, retid=request.rpc_id,
289 message='Internal server error')
295 message='Internal server error')
290
296
291
297
292 def setup_request(request):
298 def setup_request(request):
293 """
299 """
294 Parse a JSON-RPC request body. It's used inside the predicates method
300 Parse a JSON-RPC request body. It's used inside the predicates method
295 to validate and bootstrap requests for usage in rpc calls.
301 to validate and bootstrap requests for usage in rpc calls.
296
302
297 We need to raise JSONRPCError here if we want to return some errors back to
303 We need to raise JSONRPCError here if we want to return some errors back to
298 user.
304 user.
299 """
305 """
300
306
301 log.debug('Executing setup request: %r', request)
307 log.debug('Executing setup request: %r', request)
302 request.rpc_ip_addr = get_ip_addr(request.environ)
308 request.rpc_ip_addr = get_ip_addr(request.environ)
303 # TODO(marcink): deprecate GET at some point
309 # TODO(marcink): deprecate GET at some point
304 if request.method not in ['POST', 'GET']:
310 if request.method not in ['POST', 'GET']:
305 log.debug('unsupported request method "%s"', request.method)
311 log.debug('unsupported request method "%s"', request.method)
306 raise JSONRPCError(
312 raise JSONRPCError(
307 'unsupported request method "%s". Please use POST' % request.method)
313 'unsupported request method "%s". Please use POST' % request.method)
308
314
309 if 'CONTENT_LENGTH' not in request.environ:
315 if 'CONTENT_LENGTH' not in request.environ:
310 log.debug("No Content-Length")
316 log.debug("No Content-Length")
311 raise JSONRPCError("Empty body, No Content-Length in request")
317 raise JSONRPCError("Empty body, No Content-Length in request")
312
318
313 else:
319 else:
314 length = request.environ['CONTENT_LENGTH']
320 length = request.environ['CONTENT_LENGTH']
315 log.debug('Content-Length: %s', length)
321 log.debug('Content-Length: %s', length)
316
322
317 if length == 0:
323 if length == 0:
318 log.debug("Content-Length is 0")
324 log.debug("Content-Length is 0")
319 raise JSONRPCError("Content-Length is 0")
325 raise JSONRPCError("Content-Length is 0")
320
326
321 raw_body = request.body
327 raw_body = request.body
322 try:
328 try:
323 json_body = json.loads(raw_body)
329 json_body = json.loads(raw_body)
324 except ValueError as e:
330 except ValueError as e:
325 # catch JSON errors Here
331 # catch JSON errors Here
326 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
332 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
327
333
328 request.rpc_id = json_body.get('id')
334 request.rpc_id = json_body.get('id')
329 request.rpc_method = json_body.get('method')
335 request.rpc_method = json_body.get('method')
330
336
331 # check required base parameters
337 # check required base parameters
332 try:
338 try:
333 api_key = json_body.get('api_key')
339 api_key = json_body.get('api_key')
334 if not api_key:
340 if not api_key:
335 api_key = json_body.get('auth_token')
341 api_key = json_body.get('auth_token')
336
342
337 if not api_key:
343 if not api_key:
338 raise KeyError('api_key or auth_token')
344 raise KeyError('api_key or auth_token')
339
345
340 # TODO(marcink): support passing in token in request header
346 # TODO(marcink): support passing in token in request header
341
347
342 request.rpc_api_key = api_key
348 request.rpc_api_key = api_key
343 request.rpc_id = json_body['id']
349 request.rpc_id = json_body['id']
344 request.rpc_method = json_body['method']
350 request.rpc_method = json_body['method']
345 request.rpc_params = json_body['args'] \
351 request.rpc_params = json_body['args'] \
346 if isinstance(json_body['args'], dict) else {}
352 if isinstance(json_body['args'], dict) else {}
347
353
348 log.debug(
354 log.debug(
349 'method: %s, params: %s' % (request.rpc_method, request.rpc_params))
355 'method: %s, params: %s' % (request.rpc_method, request.rpc_params))
350 except KeyError as e:
356 except KeyError as e:
351 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
357 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
352
358
353 log.debug('setup complete, now handling method:%s rpcid:%s',
359 log.debug('setup complete, now handling method:%s rpcid:%s',
354 request.rpc_method, request.rpc_id, )
360 request.rpc_method, request.rpc_id, )
355
361
356
362
357 class RoutePredicate(object):
363 class RoutePredicate(object):
358 def __init__(self, val, config):
364 def __init__(self, val, config):
359 self.val = val
365 self.val = val
360
366
361 def text(self):
367 def text(self):
362 return 'jsonrpc route = %s' % self.val
368 return 'jsonrpc route = %s' % self.val
363
369
364 phash = text
370 phash = text
365
371
366 def __call__(self, info, request):
372 def __call__(self, info, request):
367 if self.val:
373 if self.val:
368 # potentially setup and bootstrap our call
374 # potentially setup and bootstrap our call
369 setup_request(request)
375 setup_request(request)
370
376
371 # Always return True so that even if it isn't a valid RPC it
377 # Always return True so that even if it isn't a valid RPC it
372 # will fall through to the underlaying handlers like notfound_view
378 # will fall through to the underlaying handlers like notfound_view
373 return True
379 return True
374
380
375
381
376 class NotFoundPredicate(object):
382 class NotFoundPredicate(object):
377 def __init__(self, val, config):
383 def __init__(self, val, config):
378 self.val = val
384 self.val = val
379 self.methods = config.registry.jsonrpc_methods
385 self.methods = config.registry.jsonrpc_methods
380
386
381 def text(self):
387 def text(self):
382 return 'jsonrpc method not found = {}.'.format(self.val)
388 return 'jsonrpc method not found = {}.'.format(self.val)
383
389
384 phash = text
390 phash = text
385
391
386 def __call__(self, info, request):
392 def __call__(self, info, request):
387 return hasattr(request, 'rpc_method')
393 return hasattr(request, 'rpc_method')
388
394
389
395
390 class MethodPredicate(object):
396 class MethodPredicate(object):
391 def __init__(self, val, config):
397 def __init__(self, val, config):
392 self.method = val
398 self.method = val
393
399
394 def text(self):
400 def text(self):
395 return 'jsonrpc method = %s' % self.method
401 return 'jsonrpc method = %s' % self.method
396
402
397 phash = text
403 phash = text
398
404
399 def __call__(self, context, request):
405 def __call__(self, context, request):
400 # we need to explicitly return False here, so pyramid doesn't try to
406 # we need to explicitly return False here, so pyramid doesn't try to
401 # execute our view directly. We need our main handler to execute things
407 # execute our view directly. We need our main handler to execute things
402 return getattr(request, 'rpc_method') == self.method
408 return getattr(request, 'rpc_method') == self.method
403
409
404
410
405 def add_jsonrpc_method(config, view, **kwargs):
411 def add_jsonrpc_method(config, view, **kwargs):
406 # pop the method name
412 # pop the method name
407 method = kwargs.pop('method', None)
413 method = kwargs.pop('method', None)
408
414
409 if method is None:
415 if method is None:
410 raise ConfigurationError(
416 raise ConfigurationError(
411 'Cannot register a JSON-RPC method without specifying the '
417 'Cannot register a JSON-RPC method without specifying the '
412 '"method"')
418 '"method"')
413
419
414 # we define custom predicate, to enable to detect conflicting methods,
420 # we define custom predicate, to enable to detect conflicting methods,
415 # those predicates are kind of "translation" from the decorator variables
421 # those predicates are kind of "translation" from the decorator variables
416 # to internal predicates names
422 # to internal predicates names
417
423
418 kwargs['jsonrpc_method'] = method
424 kwargs['jsonrpc_method'] = method
419
425
420 # register our view into global view store for validation
426 # register our view into global view store for validation
421 config.registry.jsonrpc_methods[method] = view
427 config.registry.jsonrpc_methods[method] = view
422
428
423 # we're using our main request_view handler, here, so each method
429 # we're using our main request_view handler, here, so each method
424 # has a unified handler for itself
430 # has a unified handler for itself
425 config.add_view(request_view, route_name='apiv2', **kwargs)
431 config.add_view(request_view, route_name='apiv2', **kwargs)
426
432
427
433
428 class jsonrpc_method(object):
434 class jsonrpc_method(object):
429 """
435 """
430 decorator that works similar to @add_view_config decorator,
436 decorator that works similar to @add_view_config decorator,
431 but tailored for our JSON RPC
437 but tailored for our JSON RPC
432 """
438 """
433
439
434 venusian = venusian # for testing injection
440 venusian = venusian # for testing injection
435
441
436 def __init__(self, method=None, **kwargs):
442 def __init__(self, method=None, **kwargs):
437 self.method = method
443 self.method = method
438 self.kwargs = kwargs
444 self.kwargs = kwargs
439
445
440 def __call__(self, wrapped):
446 def __call__(self, wrapped):
441 kwargs = self.kwargs.copy()
447 kwargs = self.kwargs.copy()
442 kwargs['method'] = self.method or wrapped.__name__
448 kwargs['method'] = self.method or wrapped.__name__
443 depth = kwargs.pop('_depth', 0)
449 depth = kwargs.pop('_depth', 0)
444
450
445 def callback(context, name, ob):
451 def callback(context, name, ob):
446 config = context.config.with_package(info.module)
452 config = context.config.with_package(info.module)
447 config.add_jsonrpc_method(view=ob, **kwargs)
453 config.add_jsonrpc_method(view=ob, **kwargs)
448
454
449 info = venusian.attach(wrapped, callback, category='pyramid',
455 info = venusian.attach(wrapped, callback, category='pyramid',
450 depth=depth + 1)
456 depth=depth + 1)
451 if info.scope == 'class':
457 if info.scope == 'class':
452 # ensure that attr is set if decorating a class method
458 # ensure that attr is set if decorating a class method
453 kwargs.setdefault('attr', wrapped.__name__)
459 kwargs.setdefault('attr', wrapped.__name__)
454
460
455 kwargs['_info'] = info.codeinfo # fbo action_method
461 kwargs['_info'] = info.codeinfo # fbo action_method
456 return wrapped
462 return wrapped
457
463
458
464
459 class jsonrpc_deprecated_method(object):
465 class jsonrpc_deprecated_method(object):
460 """
466 """
461 Marks method as deprecated, adds log.warning, and inject special key to
467 Marks method as deprecated, adds log.warning, and inject special key to
462 the request variable to mark method as deprecated.
468 the request variable to mark method as deprecated.
463 Also injects special docstring that extract_docs will catch to mark
469 Also injects special docstring that extract_docs will catch to mark
464 method as deprecated.
470 method as deprecated.
465
471
466 :param use_method: specify which method should be used instead of
472 :param use_method: specify which method should be used instead of
467 the decorated one
473 the decorated one
468
474
469 Use like::
475 Use like::
470
476
471 @jsonrpc_method()
477 @jsonrpc_method()
472 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
478 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
473 def old_func(request, apiuser, arg1, arg2):
479 def old_func(request, apiuser, arg1, arg2):
474 ...
480 ...
475 """
481 """
476
482
477 def __init__(self, use_method, deprecated_at_version):
483 def __init__(self, use_method, deprecated_at_version):
478 self.use_method = use_method
484 self.use_method = use_method
479 self.deprecated_at_version = deprecated_at_version
485 self.deprecated_at_version = deprecated_at_version
480 self.deprecated_msg = ''
486 self.deprecated_msg = ''
481
487
482 def __call__(self, func):
488 def __call__(self, func):
483 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
489 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
484 method=self.use_method)
490 method=self.use_method)
485
491
486 docstring = """\n
492 docstring = """\n
487 .. deprecated:: {version}
493 .. deprecated:: {version}
488
494
489 {deprecation_message}
495 {deprecation_message}
490
496
491 {original_docstring}
497 {original_docstring}
492 """
498 """
493 func.__doc__ = docstring.format(
499 func.__doc__ = docstring.format(
494 version=self.deprecated_at_version,
500 version=self.deprecated_at_version,
495 deprecation_message=self.deprecated_msg,
501 deprecation_message=self.deprecated_msg,
496 original_docstring=func.__doc__)
502 original_docstring=func.__doc__)
497 return decorator.decorator(self.__wrapper, func)
503 return decorator.decorator(self.__wrapper, func)
498
504
499 def __wrapper(self, func, *fargs, **fkwargs):
505 def __wrapper(self, func, *fargs, **fkwargs):
500 log.warning('DEPRECATED API CALL on function %s, please '
506 log.warning('DEPRECATED API CALL on function %s, please '
501 'use `%s` instead', func, self.use_method)
507 'use `%s` instead', func, self.use_method)
502 # alter function docstring to mark as deprecated, this is picked up
508 # alter function docstring to mark as deprecated, this is picked up
503 # via fabric file that generates API DOC.
509 # via fabric file that generates API DOC.
504 result = func(*fargs, **fkwargs)
510 result = func(*fargs, **fkwargs)
505
511
506 request = fargs[0]
512 request = fargs[0]
507 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
513 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
508 return result
514 return result
509
515
510
516
511 def includeme(config):
517 def includeme(config):
512 plugin_module = 'rhodecode.api'
518 plugin_module = 'rhodecode.api'
513 plugin_settings = get_plugin_settings(
519 plugin_settings = get_plugin_settings(
514 plugin_module, config.registry.settings)
520 plugin_module, config.registry.settings)
515
521
516 if not hasattr(config.registry, 'jsonrpc_methods'):
522 if not hasattr(config.registry, 'jsonrpc_methods'):
517 config.registry.jsonrpc_methods = OrderedDict()
523 config.registry.jsonrpc_methods = OrderedDict()
518
524
519 # match filter by given method only
525 # match filter by given method only
520 config.add_view_predicate('jsonrpc_method', MethodPredicate)
526 config.add_view_predicate('jsonrpc_method', MethodPredicate)
521
527
522 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
528 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
523 serializer=json.dumps, indent=4))
529 serializer=json.dumps, indent=4))
524 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
530 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
525
531
526 config.add_route_predicate(
532 config.add_route_predicate(
527 'jsonrpc_call', RoutePredicate)
533 'jsonrpc_call', RoutePredicate)
528
534
529 config.add_route(
535 config.add_route(
530 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
536 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
531
537
532 config.scan(plugin_module, ignore='rhodecode.api.tests')
538 config.scan(plugin_module, ignore='rhodecode.api.tests')
533 # register some exception handling view
539 # register some exception handling view
534 config.add_view(exception_view, context=JSONRPCBaseError)
540 config.add_view(exception_view, context=JSONRPCBaseError)
535 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
541 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
536 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
542 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
@@ -1,592 +1,596 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 The base Controller API
22 The base Controller API
23 Provides the BaseController class for subclassing. And usage in different
23 Provides the BaseController class for subclassing. And usage in different
24 controllers
24 controllers
25 """
25 """
26
26
27 import logging
27 import logging
28 import socket
28 import socket
29
29
30 import ipaddress
30 import ipaddress
31 import pyramid.threadlocal
31 import pyramid.threadlocal
32
32
33 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36 from pylons import config, tmpl_context as c, request, session, url
36 from pylons import config, tmpl_context as c, request, session, url
37 from pylons.controllers import WSGIController
37 from pylons.controllers import WSGIController
38 from pylons.controllers.util import redirect
38 from pylons.controllers.util import redirect
39 from pylons.i18n import translation
39 from pylons.i18n import translation
40 # marcink: don't remove this import
40 # marcink: don't remove this import
41 from pylons.templating import render_mako as render # noqa
41 from pylons.templating import render_mako as render # noqa
42 from pylons.i18n.translation import _
42 from pylons.i18n.translation import _
43 from webob.exc import HTTPFound
43 from webob.exc import HTTPFound
44
44
45
45
46 import rhodecode
46 import rhodecode
47 from rhodecode.authentication.base import VCS_TYPE
47 from rhodecode.authentication.base import VCS_TYPE
48 from rhodecode.lib import auth, utils2
48 from rhodecode.lib import auth, utils2
49 from rhodecode.lib import helpers as h
49 from rhodecode.lib import helpers as h
50 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
50 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
51 from rhodecode.lib.exceptions import UserCreationError
51 from rhodecode.lib.exceptions import UserCreationError
52 from rhodecode.lib.utils import (
52 from rhodecode.lib.utils import (
53 get_repo_slug, set_rhodecode_config, password_changed,
53 get_repo_slug, set_rhodecode_config, password_changed,
54 get_enabled_hook_classes)
54 get_enabled_hook_classes)
55 from rhodecode.lib.utils2 import (
55 from rhodecode.lib.utils2 import (
56 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
56 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
57 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
57 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
58 from rhodecode.model import meta
58 from rhodecode.model import meta
59 from rhodecode.model.db import Repository, User, ChangesetComment
59 from rhodecode.model.db import Repository, User, ChangesetComment
60 from rhodecode.model.notification import NotificationModel
60 from rhodecode.model.notification import NotificationModel
61 from rhodecode.model.scm import ScmModel
61 from rhodecode.model.scm import ScmModel
62 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
62 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
63
63
64
64
65 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
66
66
67
67
68 def _filter_proxy(ip):
68 def _filter_proxy(ip):
69 """
69 """
70 Passed in IP addresses in HEADERS can be in a special format of multiple
70 Passed in IP addresses in HEADERS can be in a special format of multiple
71 ips. Those comma separated IPs are passed from various proxies in the
71 ips. Those comma separated IPs are passed from various proxies in the
72 chain of request processing. The left-most being the original client.
72 chain of request processing. The left-most being the original client.
73 We only care about the first IP which came from the org. client.
73 We only care about the first IP which came from the org. client.
74
74
75 :param ip: ip string from headers
75 :param ip: ip string from headers
76 """
76 """
77 if ',' in ip:
77 if ',' in ip:
78 _ips = ip.split(',')
78 _ips = ip.split(',')
79 _first_ip = _ips[0].strip()
79 _first_ip = _ips[0].strip()
80 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
80 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
81 return _first_ip
81 return _first_ip
82 return ip
82 return ip
83
83
84
84
85 def _filter_port(ip):
85 def _filter_port(ip):
86 """
86 """
87 Removes a port from ip, there are 4 main cases to handle here.
87 Removes a port from ip, there are 4 main cases to handle here.
88 - ipv4 eg. 127.0.0.1
88 - ipv4 eg. 127.0.0.1
89 - ipv6 eg. ::1
89 - ipv6 eg. ::1
90 - ipv4+port eg. 127.0.0.1:8080
90 - ipv4+port eg. 127.0.0.1:8080
91 - ipv6+port eg. [::1]:8080
91 - ipv6+port eg. [::1]:8080
92
92
93 :param ip:
93 :param ip:
94 """
94 """
95 def is_ipv6(ip_addr):
95 def is_ipv6(ip_addr):
96 if hasattr(socket, 'inet_pton'):
96 if hasattr(socket, 'inet_pton'):
97 try:
97 try:
98 socket.inet_pton(socket.AF_INET6, ip_addr)
98 socket.inet_pton(socket.AF_INET6, ip_addr)
99 except socket.error:
99 except socket.error:
100 return False
100 return False
101 else:
101 else:
102 # fallback to ipaddress
102 # fallback to ipaddress
103 try:
103 try:
104 ipaddress.IPv6Address(ip_addr)
104 ipaddress.IPv6Address(ip_addr)
105 except Exception:
105 except Exception:
106 return False
106 return False
107 return True
107 return True
108
108
109 if ':' not in ip: # must be ipv4 pure ip
109 if ':' not in ip: # must be ipv4 pure ip
110 return ip
110 return ip
111
111
112 if '[' in ip and ']' in ip: # ipv6 with port
112 if '[' in ip and ']' in ip: # ipv6 with port
113 return ip.split(']')[0][1:].lower()
113 return ip.split(']')[0][1:].lower()
114
114
115 # must be ipv6 or ipv4 with port
115 # must be ipv6 or ipv4 with port
116 if is_ipv6(ip):
116 if is_ipv6(ip):
117 return ip
117 return ip
118 else:
118 else:
119 ip, _port = ip.split(':')[:2] # means ipv4+port
119 ip, _port = ip.split(':')[:2] # means ipv4+port
120 return ip
120 return ip
121
121
122
122
123 def get_ip_addr(environ):
123 def get_ip_addr(environ):
124 proxy_key = 'HTTP_X_REAL_IP'
124 proxy_key = 'HTTP_X_REAL_IP'
125 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
125 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
126 def_key = 'REMOTE_ADDR'
126 def_key = 'REMOTE_ADDR'
127 _filters = lambda x: _filter_port(_filter_proxy(x))
127 _filters = lambda x: _filter_port(_filter_proxy(x))
128
128
129 ip = environ.get(proxy_key)
129 ip = environ.get(proxy_key)
130 if ip:
130 if ip:
131 return _filters(ip)
131 return _filters(ip)
132
132
133 ip = environ.get(proxy_key2)
133 ip = environ.get(proxy_key2)
134 if ip:
134 if ip:
135 return _filters(ip)
135 return _filters(ip)
136
136
137 ip = environ.get(def_key, '0.0.0.0')
137 ip = environ.get(def_key, '0.0.0.0')
138 return _filters(ip)
138 return _filters(ip)
139
139
140
140
141 def get_server_ip_addr(environ, log_errors=True):
141 def get_server_ip_addr(environ, log_errors=True):
142 hostname = environ.get('SERVER_NAME')
142 hostname = environ.get('SERVER_NAME')
143 try:
143 try:
144 return socket.gethostbyname(hostname)
144 return socket.gethostbyname(hostname)
145 except Exception as e:
145 except Exception as e:
146 if log_errors:
146 if log_errors:
147 # in some cases this lookup is not possible, and we don't want to
147 # in some cases this lookup is not possible, and we don't want to
148 # make it an exception in logs
148 # make it an exception in logs
149 log.exception('Could not retrieve server ip address: %s', e)
149 log.exception('Could not retrieve server ip address: %s', e)
150 return hostname
150 return hostname
151
151
152
152
153 def get_server_port(environ):
153 def get_server_port(environ):
154 return environ.get('SERVER_PORT')
154 return environ.get('SERVER_PORT')
155
155
156
156
157 def get_access_path(environ):
157 def get_access_path(environ):
158 path = environ.get('PATH_INFO')
158 path = environ.get('PATH_INFO')
159 org_req = environ.get('pylons.original_request')
159 org_req = environ.get('pylons.original_request')
160 if org_req:
160 if org_req:
161 path = org_req.environ.get('PATH_INFO')
161 path = org_req.environ.get('PATH_INFO')
162 return path
162 return path
163
163
164
164
165 def get_user_agent(environ):
165 def get_user_agent(environ):
166 return environ.get('HTTP_USER_AGENT')
166 return environ.get('HTTP_USER_AGENT')
167
167
168
168
169 def vcs_operation_context(
169 def vcs_operation_context(
170 environ, repo_name, username, action, scm, check_locking=True,
170 environ, repo_name, username, action, scm, check_locking=True,
171 is_shadow_repo=False):
171 is_shadow_repo=False):
172 """
172 """
173 Generate the context for a vcs operation, e.g. push or pull.
173 Generate the context for a vcs operation, e.g. push or pull.
174
174
175 This context is passed over the layers so that hooks triggered by the
175 This context is passed over the layers so that hooks triggered by the
176 vcs operation know details like the user, the user's IP address etc.
176 vcs operation know details like the user, the user's IP address etc.
177
177
178 :param check_locking: Allows to switch of the computation of the locking
178 :param check_locking: Allows to switch of the computation of the locking
179 data. This serves mainly the need of the simplevcs middleware to be
179 data. This serves mainly the need of the simplevcs middleware to be
180 able to disable this for certain operations.
180 able to disable this for certain operations.
181
181
182 """
182 """
183 # Tri-state value: False: unlock, None: nothing, True: lock
183 # Tri-state value: False: unlock, None: nothing, True: lock
184 make_lock = None
184 make_lock = None
185 locked_by = [None, None, None]
185 locked_by = [None, None, None]
186 is_anonymous = username == User.DEFAULT_USER
186 is_anonymous = username == User.DEFAULT_USER
187 if not is_anonymous and check_locking:
187 if not is_anonymous and check_locking:
188 log.debug('Checking locking on repository "%s"', repo_name)
188 log.debug('Checking locking on repository "%s"', repo_name)
189 user = User.get_by_username(username)
189 user = User.get_by_username(username)
190 repo = Repository.get_by_repo_name(repo_name)
190 repo = Repository.get_by_repo_name(repo_name)
191 make_lock, __, locked_by = repo.get_locking_state(
191 make_lock, __, locked_by = repo.get_locking_state(
192 action, user.user_id)
192 action, user.user_id)
193
193
194 settings_model = VcsSettingsModel(repo=repo_name)
194 settings_model = VcsSettingsModel(repo=repo_name)
195 ui_settings = settings_model.get_ui_settings()
195 ui_settings = settings_model.get_ui_settings()
196
196
197 extras = {
197 extras = {
198 'ip': get_ip_addr(environ),
198 'ip': get_ip_addr(environ),
199 'username': username,
199 'username': username,
200 'action': action,
200 'action': action,
201 'repository': repo_name,
201 'repository': repo_name,
202 'scm': scm,
202 'scm': scm,
203 'config': rhodecode.CONFIG['__file__'],
203 'config': rhodecode.CONFIG['__file__'],
204 'make_lock': make_lock,
204 'make_lock': make_lock,
205 'locked_by': locked_by,
205 'locked_by': locked_by,
206 'server_url': utils2.get_server_url(environ),
206 'server_url': utils2.get_server_url(environ),
207 'user_agent': get_user_agent(environ),
207 'user_agent': get_user_agent(environ),
208 'hooks': get_enabled_hook_classes(ui_settings),
208 'hooks': get_enabled_hook_classes(ui_settings),
209 'is_shadow_repo': is_shadow_repo,
209 'is_shadow_repo': is_shadow_repo,
210 }
210 }
211 return extras
211 return extras
212
212
213
213
214 class BasicAuth(AuthBasicAuthenticator):
214 class BasicAuth(AuthBasicAuthenticator):
215
215
216 def __init__(self, realm, authfunc, registry, auth_http_code=None,
216 def __init__(self, realm, authfunc, registry, auth_http_code=None,
217 initial_call_detection=False, acl_repo_name=None):
217 initial_call_detection=False, acl_repo_name=None):
218 self.realm = realm
218 self.realm = realm
219 self.initial_call = initial_call_detection
219 self.initial_call = initial_call_detection
220 self.authfunc = authfunc
220 self.authfunc = authfunc
221 self.registry = registry
221 self.registry = registry
222 self.acl_repo_name = acl_repo_name
222 self.acl_repo_name = acl_repo_name
223 self._rc_auth_http_code = auth_http_code
223 self._rc_auth_http_code = auth_http_code
224
224
225 def _get_response_from_code(self, http_code):
225 def _get_response_from_code(self, http_code):
226 try:
226 try:
227 return get_exception(safe_int(http_code))
227 return get_exception(safe_int(http_code))
228 except Exception:
228 except Exception:
229 log.exception('Failed to fetch response for code %s' % http_code)
229 log.exception('Failed to fetch response for code %s' % http_code)
230 return HTTPForbidden
230 return HTTPForbidden
231
231
232 def build_authentication(self):
232 def build_authentication(self):
233 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
233 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
234 if self._rc_auth_http_code and not self.initial_call:
234 if self._rc_auth_http_code and not self.initial_call:
235 # return alternative HTTP code if alternative http return code
235 # return alternative HTTP code if alternative http return code
236 # is specified in RhodeCode config, but ONLY if it's not the
236 # is specified in RhodeCode config, but ONLY if it's not the
237 # FIRST call
237 # FIRST call
238 custom_response_klass = self._get_response_from_code(
238 custom_response_klass = self._get_response_from_code(
239 self._rc_auth_http_code)
239 self._rc_auth_http_code)
240 return custom_response_klass(headers=head)
240 return custom_response_klass(headers=head)
241 return HTTPUnauthorized(headers=head)
241 return HTTPUnauthorized(headers=head)
242
242
243 def authenticate(self, environ):
243 def authenticate(self, environ):
244 authorization = AUTHORIZATION(environ)
244 authorization = AUTHORIZATION(environ)
245 if not authorization:
245 if not authorization:
246 return self.build_authentication()
246 return self.build_authentication()
247 (authmeth, auth) = authorization.split(' ', 1)
247 (authmeth, auth) = authorization.split(' ', 1)
248 if 'basic' != authmeth.lower():
248 if 'basic' != authmeth.lower():
249 return self.build_authentication()
249 return self.build_authentication()
250 auth = auth.strip().decode('base64')
250 auth = auth.strip().decode('base64')
251 _parts = auth.split(':', 1)
251 _parts = auth.split(':', 1)
252 if len(_parts) == 2:
252 if len(_parts) == 2:
253 username, password = _parts
253 username, password = _parts
254 if self.authfunc(
254 if self.authfunc(
255 username, password, environ, VCS_TYPE,
255 username, password, environ, VCS_TYPE,
256 registry=self.registry, acl_repo_name=self.acl_repo_name):
256 registry=self.registry, acl_repo_name=self.acl_repo_name):
257 return username
257 return username
258 if username and password:
258 if username and password:
259 # we mark that we actually executed authentication once, at
259 # we mark that we actually executed authentication once, at
260 # that point we can use the alternative auth code
260 # that point we can use the alternative auth code
261 self.initial_call = False
261 self.initial_call = False
262
262
263 return self.build_authentication()
263 return self.build_authentication()
264
264
265 __call__ = authenticate
265 __call__ = authenticate
266
266
267
267
268 def attach_context_attributes(context, request, user_id):
268 def attach_context_attributes(context, request, user_id, attach_to_request=False):
269 """
269 """
270 Attach variables into template context called `c`, please note that
270 Attach variables into template context called `c`, please note that
271 request could be pylons or pyramid request in here.
271 request could be pylons or pyramid request in here.
272 """
272 """
273 rc_config = SettingsModel().get_all_settings(cache=True)
273 rc_config = SettingsModel().get_all_settings(cache=True)
274
274
275 context.rhodecode_version = rhodecode.__version__
275 context.rhodecode_version = rhodecode.__version__
276 context.rhodecode_edition = config.get('rhodecode.edition')
276 context.rhodecode_edition = config.get('rhodecode.edition')
277 # unique secret + version does not leak the version but keep consistency
277 # unique secret + version does not leak the version but keep consistency
278 context.rhodecode_version_hash = md5(
278 context.rhodecode_version_hash = md5(
279 config.get('beaker.session.secret', '') +
279 config.get('beaker.session.secret', '') +
280 rhodecode.__version__)[:8]
280 rhodecode.__version__)[:8]
281
281
282 # Default language set for the incoming request
282 # Default language set for the incoming request
283 context.language = translation.get_lang()[0]
283 context.language = translation.get_lang()[0]
284
284
285 # Visual options
285 # Visual options
286 context.visual = AttributeDict({})
286 context.visual = AttributeDict({})
287
287
288 # DB stored Visual Items
288 # DB stored Visual Items
289 context.visual.show_public_icon = str2bool(
289 context.visual.show_public_icon = str2bool(
290 rc_config.get('rhodecode_show_public_icon'))
290 rc_config.get('rhodecode_show_public_icon'))
291 context.visual.show_private_icon = str2bool(
291 context.visual.show_private_icon = str2bool(
292 rc_config.get('rhodecode_show_private_icon'))
292 rc_config.get('rhodecode_show_private_icon'))
293 context.visual.stylify_metatags = str2bool(
293 context.visual.stylify_metatags = str2bool(
294 rc_config.get('rhodecode_stylify_metatags'))
294 rc_config.get('rhodecode_stylify_metatags'))
295 context.visual.dashboard_items = safe_int(
295 context.visual.dashboard_items = safe_int(
296 rc_config.get('rhodecode_dashboard_items', 100))
296 rc_config.get('rhodecode_dashboard_items', 100))
297 context.visual.admin_grid_items = safe_int(
297 context.visual.admin_grid_items = safe_int(
298 rc_config.get('rhodecode_admin_grid_items', 100))
298 rc_config.get('rhodecode_admin_grid_items', 100))
299 context.visual.repository_fields = str2bool(
299 context.visual.repository_fields = str2bool(
300 rc_config.get('rhodecode_repository_fields'))
300 rc_config.get('rhodecode_repository_fields'))
301 context.visual.show_version = str2bool(
301 context.visual.show_version = str2bool(
302 rc_config.get('rhodecode_show_version'))
302 rc_config.get('rhodecode_show_version'))
303 context.visual.use_gravatar = str2bool(
303 context.visual.use_gravatar = str2bool(
304 rc_config.get('rhodecode_use_gravatar'))
304 rc_config.get('rhodecode_use_gravatar'))
305 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
305 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
306 context.visual.default_renderer = rc_config.get(
306 context.visual.default_renderer = rc_config.get(
307 'rhodecode_markup_renderer', 'rst')
307 'rhodecode_markup_renderer', 'rst')
308 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
308 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
309 context.visual.rhodecode_support_url = \
309 context.visual.rhodecode_support_url = \
310 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
310 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
311
311
312 context.pre_code = rc_config.get('rhodecode_pre_code')
312 context.pre_code = rc_config.get('rhodecode_pre_code')
313 context.post_code = rc_config.get('rhodecode_post_code')
313 context.post_code = rc_config.get('rhodecode_post_code')
314 context.rhodecode_name = rc_config.get('rhodecode_title')
314 context.rhodecode_name = rc_config.get('rhodecode_title')
315 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
315 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
316 # if we have specified default_encoding in the request, it has more
316 # if we have specified default_encoding in the request, it has more
317 # priority
317 # priority
318 if request.GET.get('default_encoding'):
318 if request.GET.get('default_encoding'):
319 context.default_encodings.insert(0, request.GET.get('default_encoding'))
319 context.default_encodings.insert(0, request.GET.get('default_encoding'))
320 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
320 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
321
321
322 # INI stored
322 # INI stored
323 context.labs_active = str2bool(
323 context.labs_active = str2bool(
324 config.get('labs_settings_active', 'false'))
324 config.get('labs_settings_active', 'false'))
325 context.visual.allow_repo_location_change = str2bool(
325 context.visual.allow_repo_location_change = str2bool(
326 config.get('allow_repo_location_change', True))
326 config.get('allow_repo_location_change', True))
327 context.visual.allow_custom_hooks_settings = str2bool(
327 context.visual.allow_custom_hooks_settings = str2bool(
328 config.get('allow_custom_hooks_settings', True))
328 config.get('allow_custom_hooks_settings', True))
329 context.debug_style = str2bool(config.get('debug_style', False))
329 context.debug_style = str2bool(config.get('debug_style', False))
330
330
331 context.rhodecode_instanceid = config.get('instance_id')
331 context.rhodecode_instanceid = config.get('instance_id')
332
332
333 # AppEnlight
333 # AppEnlight
334 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
334 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
335 context.appenlight_api_public_key = config.get(
335 context.appenlight_api_public_key = config.get(
336 'appenlight.api_public_key', '')
336 'appenlight.api_public_key', '')
337 context.appenlight_server_url = config.get('appenlight.server_url', '')
337 context.appenlight_server_url = config.get('appenlight.server_url', '')
338
338
339 # JS template context
339 # JS template context
340 context.template_context = {
340 context.template_context = {
341 'repo_name': None,
341 'repo_name': None,
342 'repo_type': None,
342 'repo_type': None,
343 'repo_landing_commit': None,
343 'repo_landing_commit': None,
344 'rhodecode_user': {
344 'rhodecode_user': {
345 'username': None,
345 'username': None,
346 'email': None,
346 'email': None,
347 'notification_status': False
347 'notification_status': False
348 },
348 },
349 'visual': {
349 'visual': {
350 'default_renderer': None
350 'default_renderer': None
351 },
351 },
352 'commit_data': {
352 'commit_data': {
353 'commit_id': None
353 'commit_id': None
354 },
354 },
355 'pull_request_data': {'pull_request_id': None},
355 'pull_request_data': {'pull_request_id': None},
356 'timeago': {
356 'timeago': {
357 'refresh_time': 120 * 1000,
357 'refresh_time': 120 * 1000,
358 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
358 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
359 },
359 },
360 'pylons_dispatch': {
360 'pylons_dispatch': {
361 # 'controller': request.environ['pylons.routes_dict']['controller'],
361 # 'controller': request.environ['pylons.routes_dict']['controller'],
362 # 'action': request.environ['pylons.routes_dict']['action'],
362 # 'action': request.environ['pylons.routes_dict']['action'],
363 },
363 },
364 'pyramid_dispatch': {
364 'pyramid_dispatch': {
365
365
366 },
366 },
367 'extra': {'plugins': {}}
367 'extra': {'plugins': {}}
368 }
368 }
369 # END CONFIG VARS
369 # END CONFIG VARS
370
370
371 # TODO: This dosn't work when called from pylons compatibility tween.
371 # TODO: This dosn't work when called from pylons compatibility tween.
372 # Fix this and remove it from base controller.
372 # Fix this and remove it from base controller.
373 # context.repo_name = get_repo_slug(request) # can be empty
373 # context.repo_name = get_repo_slug(request) # can be empty
374
374
375 diffmode = 'sideside'
375 diffmode = 'sideside'
376 if request.GET.get('diffmode'):
376 if request.GET.get('diffmode'):
377 if request.GET['diffmode'] == 'unified':
377 if request.GET['diffmode'] == 'unified':
378 diffmode = 'unified'
378 diffmode = 'unified'
379 elif request.session.get('diffmode'):
379 elif request.session.get('diffmode'):
380 diffmode = request.session['diffmode']
380 diffmode = request.session['diffmode']
381
381
382 context.diffmode = diffmode
382 context.diffmode = diffmode
383
383
384 if request.session.get('diffmode') != diffmode:
384 if request.session.get('diffmode') != diffmode:
385 request.session['diffmode'] = diffmode
385 request.session['diffmode'] = diffmode
386
386
387 context.csrf_token = auth.get_csrf_token()
387 context.csrf_token = auth.get_csrf_token()
388 context.backends = rhodecode.BACKENDS.keys()
388 context.backends = rhodecode.BACKENDS.keys()
389 context.backends.sort()
389 context.backends.sort()
390 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
390 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
391 context.pyramid_request = pyramid.threadlocal.get_current_request()
391 if attach_to_request:
392 request.call_context = context
393 else:
394 context.pyramid_request = pyramid.threadlocal.get_current_request()
395
392
396
393
397
394 def get_auth_user(environ):
398 def get_auth_user(environ):
395 ip_addr = get_ip_addr(environ)
399 ip_addr = get_ip_addr(environ)
396 # make sure that we update permissions each time we call controller
400 # make sure that we update permissions each time we call controller
397 _auth_token = (request.GET.get('auth_token', '') or
401 _auth_token = (request.GET.get('auth_token', '') or
398 request.GET.get('api_key', ''))
402 request.GET.get('api_key', ''))
399
403
400 if _auth_token:
404 if _auth_token:
401 # when using API_KEY we assume user exists, and
405 # when using API_KEY we assume user exists, and
402 # doesn't need auth based on cookies.
406 # doesn't need auth based on cookies.
403 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
407 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
404 authenticated = False
408 authenticated = False
405 else:
409 else:
406 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
410 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
407 try:
411 try:
408 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
412 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
409 ip_addr=ip_addr)
413 ip_addr=ip_addr)
410 except UserCreationError as e:
414 except UserCreationError as e:
411 h.flash(e, 'error')
415 h.flash(e, 'error')
412 # container auth or other auth functions that create users
416 # container auth or other auth functions that create users
413 # on the fly can throw this exception signaling that there's
417 # on the fly can throw this exception signaling that there's
414 # issue with user creation, explanation should be provided
418 # issue with user creation, explanation should be provided
415 # in Exception itself. We then create a simple blank
419 # in Exception itself. We then create a simple blank
416 # AuthUser
420 # AuthUser
417 auth_user = AuthUser(ip_addr=ip_addr)
421 auth_user = AuthUser(ip_addr=ip_addr)
418
422
419 if password_changed(auth_user, session):
423 if password_changed(auth_user, session):
420 session.invalidate()
424 session.invalidate()
421 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
425 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
422 auth_user = AuthUser(ip_addr=ip_addr)
426 auth_user = AuthUser(ip_addr=ip_addr)
423
427
424 authenticated = cookie_store.get('is_authenticated')
428 authenticated = cookie_store.get('is_authenticated')
425
429
426 if not auth_user.is_authenticated and auth_user.is_user_object:
430 if not auth_user.is_authenticated and auth_user.is_user_object:
427 # user is not authenticated and not empty
431 # user is not authenticated and not empty
428 auth_user.set_authenticated(authenticated)
432 auth_user.set_authenticated(authenticated)
429
433
430 return auth_user
434 return auth_user
431
435
432
436
433 class BaseController(WSGIController):
437 class BaseController(WSGIController):
434
438
435 def __before__(self):
439 def __before__(self):
436 """
440 """
437 __before__ is called before controller methods and after __call__
441 __before__ is called before controller methods and after __call__
438 """
442 """
439 # on each call propagate settings calls into global settings.
443 # on each call propagate settings calls into global settings.
440 set_rhodecode_config(config)
444 set_rhodecode_config(config)
441 attach_context_attributes(c, request, c.rhodecode_user.user_id)
445 attach_context_attributes(c, request, c.rhodecode_user.user_id)
442
446
443 # TODO: Remove this when fixed in attach_context_attributes()
447 # TODO: Remove this when fixed in attach_context_attributes()
444 c.repo_name = get_repo_slug(request) # can be empty
448 c.repo_name = get_repo_slug(request) # can be empty
445
449
446 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
450 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
447 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
451 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
448 self.sa = meta.Session
452 self.sa = meta.Session
449 self.scm_model = ScmModel(self.sa)
453 self.scm_model = ScmModel(self.sa)
450
454
451 # set user language
455 # set user language
452 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
456 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
453 if user_lang:
457 if user_lang:
454 translation.set_lang(user_lang)
458 translation.set_lang(user_lang)
455 log.debug('set language to %s for user %s',
459 log.debug('set language to %s for user %s',
456 user_lang, self._rhodecode_user)
460 user_lang, self._rhodecode_user)
457
461
458 def _dispatch_redirect(self, with_url, environ, start_response):
462 def _dispatch_redirect(self, with_url, environ, start_response):
459 resp = HTTPFound(with_url)
463 resp = HTTPFound(with_url)
460 environ['SCRIPT_NAME'] = '' # handle prefix middleware
464 environ['SCRIPT_NAME'] = '' # handle prefix middleware
461 environ['PATH_INFO'] = with_url
465 environ['PATH_INFO'] = with_url
462 return resp(environ, start_response)
466 return resp(environ, start_response)
463
467
464 def __call__(self, environ, start_response):
468 def __call__(self, environ, start_response):
465 """Invoke the Controller"""
469 """Invoke the Controller"""
466 # WSGIController.__call__ dispatches to the Controller method
470 # WSGIController.__call__ dispatches to the Controller method
467 # the request is routed to. This routing information is
471 # the request is routed to. This routing information is
468 # available in environ['pylons.routes_dict']
472 # available in environ['pylons.routes_dict']
469 from rhodecode.lib import helpers as h
473 from rhodecode.lib import helpers as h
470
474
471 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
475 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
472 if environ.get('debugtoolbar.wants_pylons_context', False):
476 if environ.get('debugtoolbar.wants_pylons_context', False):
473 environ['debugtoolbar.pylons_context'] = c._current_obj()
477 environ['debugtoolbar.pylons_context'] = c._current_obj()
474
478
475 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
479 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
476 environ['pylons.routes_dict']['action']])
480 environ['pylons.routes_dict']['action']])
477
481
478 self.rc_config = SettingsModel().get_all_settings(cache=True)
482 self.rc_config = SettingsModel().get_all_settings(cache=True)
479 self.ip_addr = get_ip_addr(environ)
483 self.ip_addr = get_ip_addr(environ)
480
484
481 # The rhodecode auth user is looked up and passed through the
485 # The rhodecode auth user is looked up and passed through the
482 # environ by the pylons compatibility tween in pyramid.
486 # environ by the pylons compatibility tween in pyramid.
483 # So we can just grab it from there.
487 # So we can just grab it from there.
484 auth_user = environ['rc_auth_user']
488 auth_user = environ['rc_auth_user']
485
489
486 # set globals for auth user
490 # set globals for auth user
487 request.user = auth_user
491 request.user = auth_user
488 c.rhodecode_user = self._rhodecode_user = auth_user
492 c.rhodecode_user = self._rhodecode_user = auth_user
489
493
490 log.info('IP: %s User: %s accessed %s [%s]' % (
494 log.info('IP: %s User: %s accessed %s [%s]' % (
491 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
495 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
492 _route_name)
496 _route_name)
493 )
497 )
494
498
495 user_obj = auth_user.get_instance()
499 user_obj = auth_user.get_instance()
496 if user_obj and user_obj.user_data.get('force_password_change'):
500 if user_obj and user_obj.user_data.get('force_password_change'):
497 h.flash('You are required to change your password', 'warning',
501 h.flash('You are required to change your password', 'warning',
498 ignore_duplicate=True)
502 ignore_duplicate=True)
499 return self._dispatch_redirect(
503 return self._dispatch_redirect(
500 url('my_account_password'), environ, start_response)
504 url('my_account_password'), environ, start_response)
501
505
502 return WSGIController.__call__(self, environ, start_response)
506 return WSGIController.__call__(self, environ, start_response)
503
507
504
508
505 class BaseRepoController(BaseController):
509 class BaseRepoController(BaseController):
506 """
510 """
507 Base class for controllers responsible for loading all needed data for
511 Base class for controllers responsible for loading all needed data for
508 repository loaded items are
512 repository loaded items are
509
513
510 c.rhodecode_repo: instance of scm repository
514 c.rhodecode_repo: instance of scm repository
511 c.rhodecode_db_repo: instance of db
515 c.rhodecode_db_repo: instance of db
512 c.repository_requirements_missing: shows that repository specific data
516 c.repository_requirements_missing: shows that repository specific data
513 could not be displayed due to the missing requirements
517 could not be displayed due to the missing requirements
514 c.repository_pull_requests: show number of open pull requests
518 c.repository_pull_requests: show number of open pull requests
515 """
519 """
516
520
517 def __before__(self):
521 def __before__(self):
518 super(BaseRepoController, self).__before__()
522 super(BaseRepoController, self).__before__()
519 if c.repo_name: # extracted from routes
523 if c.repo_name: # extracted from routes
520 db_repo = Repository.get_by_repo_name(c.repo_name)
524 db_repo = Repository.get_by_repo_name(c.repo_name)
521 if not db_repo:
525 if not db_repo:
522 return
526 return
523
527
524 log.debug(
528 log.debug(
525 'Found repository in database %s with state `%s`',
529 'Found repository in database %s with state `%s`',
526 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
530 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
527 route = getattr(request.environ.get('routes.route'), 'name', '')
531 route = getattr(request.environ.get('routes.route'), 'name', '')
528
532
529 # allow to delete repos that are somehow damages in filesystem
533 # allow to delete repos that are somehow damages in filesystem
530 if route in ['delete_repo']:
534 if route in ['delete_repo']:
531 return
535 return
532
536
533 if db_repo.repo_state in [Repository.STATE_PENDING]:
537 if db_repo.repo_state in [Repository.STATE_PENDING]:
534 if route in ['repo_creating_home']:
538 if route in ['repo_creating_home']:
535 return
539 return
536 check_url = url('repo_creating_home', repo_name=c.repo_name)
540 check_url = url('repo_creating_home', repo_name=c.repo_name)
537 return redirect(check_url)
541 return redirect(check_url)
538
542
539 self.rhodecode_db_repo = db_repo
543 self.rhodecode_db_repo = db_repo
540
544
541 missing_requirements = False
545 missing_requirements = False
542 try:
546 try:
543 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
547 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
544 except RepositoryRequirementError as e:
548 except RepositoryRequirementError as e:
545 missing_requirements = True
549 missing_requirements = True
546 self._handle_missing_requirements(e)
550 self._handle_missing_requirements(e)
547
551
548 if self.rhodecode_repo is None and not missing_requirements:
552 if self.rhodecode_repo is None and not missing_requirements:
549 log.error('%s this repository is present in database but it '
553 log.error('%s this repository is present in database but it '
550 'cannot be created as an scm instance', c.repo_name)
554 'cannot be created as an scm instance', c.repo_name)
551
555
552 h.flash(_(
556 h.flash(_(
553 "The repository at %(repo_name)s cannot be located.") %
557 "The repository at %(repo_name)s cannot be located.") %
554 {'repo_name': c.repo_name},
558 {'repo_name': c.repo_name},
555 category='error', ignore_duplicate=True)
559 category='error', ignore_duplicate=True)
556 redirect(h.route_path('home'))
560 redirect(h.route_path('home'))
557
561
558 # update last change according to VCS data
562 # update last change according to VCS data
559 if not missing_requirements:
563 if not missing_requirements:
560 commit = db_repo.get_commit(
564 commit = db_repo.get_commit(
561 pre_load=["author", "date", "message", "parents"])
565 pre_load=["author", "date", "message", "parents"])
562 db_repo.update_commit_cache(commit)
566 db_repo.update_commit_cache(commit)
563
567
564 # Prepare context
568 # Prepare context
565 c.rhodecode_db_repo = db_repo
569 c.rhodecode_db_repo = db_repo
566 c.rhodecode_repo = self.rhodecode_repo
570 c.rhodecode_repo = self.rhodecode_repo
567 c.repository_requirements_missing = missing_requirements
571 c.repository_requirements_missing = missing_requirements
568
572
569 self._update_global_counters(self.scm_model, db_repo)
573 self._update_global_counters(self.scm_model, db_repo)
570
574
571 def _update_global_counters(self, scm_model, db_repo):
575 def _update_global_counters(self, scm_model, db_repo):
572 """
576 """
573 Base variables that are exposed to every page of repository
577 Base variables that are exposed to every page of repository
574 """
578 """
575 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
579 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
576
580
577 def _handle_missing_requirements(self, error):
581 def _handle_missing_requirements(self, error):
578 self.rhodecode_repo = None
582 self.rhodecode_repo = None
579 log.error(
583 log.error(
580 'Requirements are missing for repository %s: %s',
584 'Requirements are missing for repository %s: %s',
581 c.repo_name, error.message)
585 c.repo_name, error.message)
582
586
583 summary_url = h.route_path('repo_summary', repo_name=c.repo_name)
587 summary_url = h.route_path('repo_summary', repo_name=c.repo_name)
584 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
588 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
585 settings_update_url = url('repo', repo_name=c.repo_name)
589 settings_update_url = url('repo', repo_name=c.repo_name)
586 path = request.path
590 path = request.path
587 should_redirect = (
591 should_redirect = (
588 path not in (summary_url, settings_update_url)
592 path not in (summary_url, settings_update_url)
589 and '/settings' not in path or path == statistics_url
593 and '/settings' not in path or path == statistics_url
590 )
594 )
591 if should_redirect:
595 if should_redirect:
592 redirect(summary_url)
596 redirect(summary_url)
General Comments 0
You need to be logged in to leave comments. Login now