##// END OF EJS Templates
code: added more logging, and some notes
marcink -
r1300:0ec329be default
parent child Browse files
Show More
@@ -1,505 +1,507 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
25
26 import decorator
26 import decorator
27 import venusian
27 import venusian
28 from collections import OrderedDict
28 from collections import OrderedDict
29
29
30 from pyramid.exceptions import ConfigurationError
30 from pyramid.exceptions import ConfigurationError
31 from pyramid.renderers import render
31 from pyramid.renderers import render
32 from pyramid.response import Response
32 from pyramid.response import Response
33 from pyramid.httpexceptions import HTTPNotFound
33 from pyramid.httpexceptions import HTTPNotFound
34
34
35 from rhodecode.api.exc import (
35 from rhodecode.api.exc import (
36 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
36 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
37 from rhodecode.lib.auth import AuthUser
37 from rhodecode.lib.auth import AuthUser
38 from rhodecode.lib.base import get_ip_addr
38 from rhodecode.lib.base import get_ip_addr
39 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.ext_json import json
40 from rhodecode.lib.utils2 import safe_str
40 from rhodecode.lib.utils2 import safe_str
41 from rhodecode.lib.plugins.utils import get_plugin_settings
41 from rhodecode.lib.plugins.utils import get_plugin_settings
42 from rhodecode.model.db import User, UserApiKeys
42 from rhodecode.model.db import User, UserApiKeys
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46 DEFAULT_RENDERER = 'jsonrpc_renderer'
46 DEFAULT_RENDERER = 'jsonrpc_renderer'
47 DEFAULT_URL = '/_admin/apiv2'
47 DEFAULT_URL = '/_admin/apiv2'
48
48
49
49
50 class ExtJsonRenderer(object):
50 class ExtJsonRenderer(object):
51 """
51 """
52 Custom renderer that mkaes use of our ext_json lib
52 Custom renderer that mkaes use of our ext_json lib
53
53
54 """
54 """
55
55
56 def __init__(self, serializer=json.dumps, **kw):
56 def __init__(self, serializer=json.dumps, **kw):
57 """ Any keyword arguments will be passed to the ``serializer``
57 """ Any keyword arguments will be passed to the ``serializer``
58 function."""
58 function."""
59 self.serializer = serializer
59 self.serializer = serializer
60 self.kw = kw
60 self.kw = kw
61
61
62 def __call__(self, info):
62 def __call__(self, info):
63 """ Returns a plain JSON-encoded string with content-type
63 """ Returns a plain JSON-encoded string with content-type
64 ``application/json``. The content-type may be overridden by
64 ``application/json``. The content-type may be overridden by
65 setting ``request.response.content_type``."""
65 setting ``request.response.content_type``."""
66
66
67 def _render(value, system):
67 def _render(value, system):
68 request = system.get('request')
68 request = system.get('request')
69 if request is not None:
69 if request is not None:
70 response = request.response
70 response = request.response
71 ct = response.content_type
71 ct = response.content_type
72 if ct == response.default_content_type:
72 if ct == response.default_content_type:
73 response.content_type = 'application/json'
73 response.content_type = 'application/json'
74
74
75 return self.serializer(value, **self.kw)
75 return self.serializer(value, **self.kw)
76
76
77 return _render
77 return _render
78
78
79
79
80 def jsonrpc_response(request, result):
80 def jsonrpc_response(request, result):
81 rpc_id = getattr(request, 'rpc_id', None)
81 rpc_id = getattr(request, 'rpc_id', None)
82 response = request.response
82 response = request.response
83
83
84 # store content_type before render is called
84 # store content_type before render is called
85 ct = response.content_type
85 ct = response.content_type
86
86
87 ret_value = ''
87 ret_value = ''
88 if rpc_id:
88 if rpc_id:
89 ret_value = {
89 ret_value = {
90 'id': rpc_id,
90 'id': rpc_id,
91 'result': result,
91 'result': result,
92 'error': None,
92 'error': None,
93 }
93 }
94
94
95 # fetch deprecation warnings, and store it inside results
95 # fetch deprecation warnings, and store it inside results
96 deprecation = getattr(request, 'rpc_deprecation', None)
96 deprecation = getattr(request, 'rpc_deprecation', None)
97 if deprecation:
97 if deprecation:
98 ret_value['DEPRECATION_WARNING'] = deprecation
98 ret_value['DEPRECATION_WARNING'] = deprecation
99
99
100 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
100 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
101 response.body = safe_str(raw_body, response.charset)
101 response.body = safe_str(raw_body, response.charset)
102
102
103 if ct == response.default_content_type:
103 if ct == response.default_content_type:
104 response.content_type = 'application/json'
104 response.content_type = 'application/json'
105
105
106 return response
106 return response
107
107
108
108
109 def jsonrpc_error(request, message, retid=None, code=None):
109 def jsonrpc_error(request, message, retid=None, code=None):
110 """
110 """
111 Generate a Response object with a JSON-RPC error body
111 Generate a Response object with a JSON-RPC error body
112
112
113 :param code:
113 :param code:
114 :param retid:
114 :param retid:
115 :param message:
115 :param message:
116 """
116 """
117 err_dict = {'id': retid, 'result': None, 'error': message}
117 err_dict = {'id': retid, 'result': None, 'error': message}
118 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
118 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
119 return Response(
119 return Response(
120 body=body,
120 body=body,
121 status=code,
121 status=code,
122 content_type='application/json'
122 content_type='application/json'
123 )
123 )
124
124
125
125
126 def exception_view(exc, request):
126 def exception_view(exc, request):
127 rpc_id = getattr(request, 'rpc_id', None)
127 rpc_id = getattr(request, 'rpc_id', None)
128
128
129 fault_message = 'undefined error'
129 fault_message = 'undefined error'
130 if isinstance(exc, JSONRPCError):
130 if isinstance(exc, JSONRPCError):
131 fault_message = exc.message
131 fault_message = exc.message
132 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
132 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
133 elif isinstance(exc, JSONRPCValidationError):
133 elif isinstance(exc, JSONRPCValidationError):
134 colander_exc = exc.colander_exception
134 colander_exc = exc.colander_exception
135 #TODO: think maybe of nicer way to serialize errors ?
135 # TODO(marcink): think maybe of nicer way to serialize errors ?
136 fault_message = colander_exc.asdict()
136 fault_message = colander_exc.asdict()
137 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
137 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
138 elif isinstance(exc, JSONRPCForbidden):
138 elif isinstance(exc, JSONRPCForbidden):
139 fault_message = 'Access was denied to this resource.'
139 fault_message = 'Access was denied to this resource.'
140 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
140 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
141 elif isinstance(exc, HTTPNotFound):
141 elif isinstance(exc, HTTPNotFound):
142 method = request.rpc_method
142 method = request.rpc_method
143 log.debug('json-rpc method `%s` not found in list of '
143 log.debug('json-rpc method `%s` not found in list of '
144 'api calls: %s, rpc_id:%s',
144 'api calls: %s, rpc_id:%s',
145 method, request.registry.jsonrpc_methods.keys(), rpc_id)
145 method, request.registry.jsonrpc_methods.keys(), rpc_id)
146 fault_message = "No such method: {}".format(method)
146 fault_message = "No such method: {}".format(method)
147
147
148 return jsonrpc_error(request, fault_message, rpc_id)
148 return jsonrpc_error(request, fault_message, rpc_id)
149
149
150
150
151 def request_view(request):
151 def request_view(request):
152 """
152 """
153 Main request handling method. It handles all logic to call a specific
153 Main request handling method. It handles all logic to call a specific
154 exposed method
154 exposed method
155 """
155 """
156
156
157 # check if we can find this session using api_key, get_by_auth_token
157 # check if we can find this session using api_key, get_by_auth_token
158 # search not expired tokens only
158 # search not expired tokens only
159
159
160 try:
160 try:
161 u = User.get_by_auth_token(request.rpc_api_key)
161 u = User.get_by_auth_token(request.rpc_api_key)
162
162
163 if u is None:
163 if u is None:
164 return jsonrpc_error(
164 return jsonrpc_error(
165 request, retid=request.rpc_id, message='Invalid API KEY')
165 request, retid=request.rpc_id, message='Invalid API KEY')
166
166
167 if not u.active:
167 if not u.active:
168 return jsonrpc_error(
168 return jsonrpc_error(
169 request, retid=request.rpc_id,
169 request, retid=request.rpc_id,
170 message='Request from this user not allowed')
170 message='Request from this user not allowed')
171
171
172 # check if we are allowed to use this IP
172 # check if we are allowed to use this IP
173 auth_u = AuthUser(
173 auth_u = AuthUser(
174 u.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
174 u.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
175 if not auth_u.ip_allowed:
175 if not auth_u.ip_allowed:
176 return jsonrpc_error(
176 return jsonrpc_error(
177 request, retid=request.rpc_id,
177 request, retid=request.rpc_id,
178 message='Request from IP:%s not allowed' % (
178 message='Request from IP:%s not allowed' % (
179 request.rpc_ip_addr,))
179 request.rpc_ip_addr,))
180 else:
180 else:
181 log.info('Access for IP:%s allowed' % (request.rpc_ip_addr,))
181 log.info('Access for IP:%s allowed' % (request.rpc_ip_addr,))
182
182
183 # now check if token is valid for API
183 # now check if token is valid for API
184 role = UserApiKeys.ROLE_API
184 role = UserApiKeys.ROLE_API
185 extra_auth_tokens = [
185 extra_auth_tokens = [
186 x.api_key for x in User.extra_valid_auth_tokens(u, role=role)]
186 x.api_key for x in User.extra_valid_auth_tokens(u, role=role)]
187 active_tokens = [u.api_key] + extra_auth_tokens
187 active_tokens = [u.api_key] + extra_auth_tokens
188
188
189 log.debug('Checking if API key has proper role')
189 log.debug('Checking if API key has proper role')
190 if request.rpc_api_key not in active_tokens:
190 if request.rpc_api_key not in active_tokens:
191 return jsonrpc_error(
191 return jsonrpc_error(
192 request, retid=request.rpc_id,
192 request, retid=request.rpc_id,
193 message='API KEY has bad role for an API call')
193 message='API KEY has bad role for an API call')
194
194
195 except Exception as e:
195 except Exception as e:
196 log.exception('Error on API AUTH')
196 log.exception('Error on API AUTH')
197 return jsonrpc_error(
197 return jsonrpc_error(
198 request, retid=request.rpc_id, message='Invalid API KEY')
198 request, retid=request.rpc_id, message='Invalid API KEY')
199
199
200 method = request.rpc_method
200 method = request.rpc_method
201 func = request.registry.jsonrpc_methods[method]
201 func = request.registry.jsonrpc_methods[method]
202
202
203 # now that we have a method, add request._req_params to
203 # now that we have a method, add request._req_params to
204 # self.kargs and dispatch control to WGIController
204 # self.kargs and dispatch control to WGIController
205 argspec = inspect.getargspec(func)
205 argspec = inspect.getargspec(func)
206 arglist = argspec[0]
206 arglist = argspec[0]
207 defaults = map(type, argspec[3] or [])
207 defaults = map(type, argspec[3] or [])
208 default_empty = types.NotImplementedType
208 default_empty = types.NotImplementedType
209
209
210 # kw arguments required by this method
210 # kw arguments required by this method
211 func_kwargs = dict(itertools.izip_longest(
211 func_kwargs = dict(itertools.izip_longest(
212 reversed(arglist), reversed(defaults), fillvalue=default_empty))
212 reversed(arglist), reversed(defaults), fillvalue=default_empty))
213
213
214 # This attribute will need to be first param of a method that uses
214 # This attribute will need to be first param of a method that uses
215 # api_key, which is translated to instance of user at that name
215 # api_key, which is translated to instance of user at that name
216 user_var = 'apiuser'
216 user_var = 'apiuser'
217 request_var = 'request'
217 request_var = 'request'
218
218
219 for arg in [user_var, request_var]:
219 for arg in [user_var, request_var]:
220 if arg not in arglist:
220 if arg not in arglist:
221 return jsonrpc_error(
221 return jsonrpc_error(
222 request,
222 request,
223 retid=request.rpc_id,
223 retid=request.rpc_id,
224 message='This method [%s] does not support '
224 message='This method [%s] does not support '
225 'required parameter `%s`' % (func.__name__, arg))
225 'required parameter `%s`' % (func.__name__, arg))
226
226
227 # get our arglist and check if we provided them as args
227 # get our arglist and check if we provided them as args
228 for arg, default in func_kwargs.items():
228 for arg, default in func_kwargs.items():
229 if arg in [user_var, request_var]:
229 if arg in [user_var, request_var]:
230 # user_var and request_var are pre-hardcoded parameters and we
230 # user_var and request_var are pre-hardcoded parameters and we
231 # don't need to do any translation
231 # don't need to do any translation
232 continue
232 continue
233
233
234 # skip the required param check if it's default value is
234 # skip the required param check if it's default value is
235 # NotImplementedType (default_empty)
235 # NotImplementedType (default_empty)
236 if default == default_empty and arg not in request.rpc_params:
236 if default == default_empty and arg not in request.rpc_params:
237 return jsonrpc_error(
237 return jsonrpc_error(
238 request,
238 request,
239 retid=request.rpc_id,
239 retid=request.rpc_id,
240 message=('Missing non optional `%s` arg in JSON DATA' % arg)
240 message=('Missing non optional `%s` arg in JSON DATA' % arg)
241 )
241 )
242
242
243 # sanitze extra passed arguments
243 # sanitize extra passed arguments
244 for k in request.rpc_params.keys()[:]:
244 for k in request.rpc_params.keys()[:]:
245 if k not in func_kwargs:
245 if k not in func_kwargs:
246 del request.rpc_params[k]
246 del request.rpc_params[k]
247
247
248 call_params = request.rpc_params
248 call_params = request.rpc_params
249 call_params.update({
249 call_params.update({
250 'request': request,
250 'request': request,
251 'apiuser': auth_u
251 'apiuser': auth_u
252 })
252 })
253 try:
253 try:
254 ret_value = func(**call_params)
254 ret_value = func(**call_params)
255 return jsonrpc_response(request, ret_value)
255 return jsonrpc_response(request, ret_value)
256 except JSONRPCBaseError:
256 except JSONRPCBaseError:
257 raise
257 raise
258 except Exception:
258 except Exception:
259 log.exception('Unhandled exception occured on api call: %s', func)
259 log.exception('Unhandled exception occured on api call: %s', func)
260 return jsonrpc_error(request, retid=request.rpc_id,
260 return jsonrpc_error(request, retid=request.rpc_id,
261 message='Internal server error')
261 message='Internal server error')
262
262
263
263
264 def setup_request(request):
264 def setup_request(request):
265 """
265 """
266 Parse a JSON-RPC request body. It's used inside the predicates method
266 Parse a JSON-RPC request body. It's used inside the predicates method
267 to validate and bootstrap requests for usage in rpc calls.
267 to validate and bootstrap requests for usage in rpc calls.
268
268
269 We need to raise JSONRPCError here if we want to return some errors back to
269 We need to raise JSONRPCError here if we want to return some errors back to
270 user.
270 user.
271 """
271 """
272
272 log.debug('Executing setup request: %r', request)
273 log.debug('Executing setup request: %r', request)
273 request.rpc_ip_addr = get_ip_addr(request.environ)
274 request.rpc_ip_addr = get_ip_addr(request.environ)
274 # TODO: marcink, deprecate GET at some point
275 # TODO(marcink): deprecate GET at some point
275 if request.method not in ['POST', 'GET']:
276 if request.method not in ['POST', 'GET']:
276 log.debug('unsupported request method "%s"', request.method)
277 log.debug('unsupported request method "%s"', request.method)
277 raise JSONRPCError(
278 raise JSONRPCError(
278 'unsupported request method "%s". Please use POST' % request.method)
279 'unsupported request method "%s". Please use POST' % request.method)
279
280
280 if 'CONTENT_LENGTH' not in request.environ:
281 if 'CONTENT_LENGTH' not in request.environ:
281 log.debug("No Content-Length")
282 log.debug("No Content-Length")
282 raise JSONRPCError("Empty body, No Content-Length in request")
283 raise JSONRPCError("Empty body, No Content-Length in request")
283
284
284 else:
285 else:
285 length = request.environ['CONTENT_LENGTH']
286 length = request.environ['CONTENT_LENGTH']
286 log.debug('Content-Length: %s', length)
287 log.debug('Content-Length: %s', length)
287
288
288 if length == 0:
289 if length == 0:
289 log.debug("Content-Length is 0")
290 log.debug("Content-Length is 0")
290 raise JSONRPCError("Content-Length is 0")
291 raise JSONRPCError("Content-Length is 0")
291
292
292 raw_body = request.body
293 raw_body = request.body
293 try:
294 try:
294 json_body = json.loads(raw_body)
295 json_body = json.loads(raw_body)
295 except ValueError as e:
296 except ValueError as e:
296 # catch JSON errors Here
297 # catch JSON errors Here
297 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
298 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
298
299
299 request.rpc_id = json_body.get('id')
300 request.rpc_id = json_body.get('id')
300 request.rpc_method = json_body.get('method')
301 request.rpc_method = json_body.get('method')
301
302
302 # check required base parameters
303 # check required base parameters
303 try:
304 try:
304 api_key = json_body.get('api_key')
305 api_key = json_body.get('api_key')
305 if not api_key:
306 if not api_key:
306 api_key = json_body.get('auth_token')
307 api_key = json_body.get('auth_token')
307
308
308 if not api_key:
309 if not api_key:
309 raise KeyError('api_key or auth_token')
310 raise KeyError('api_key or auth_token')
310
311
312 # TODO(marcink): support passing in token in request header
313
311 request.rpc_api_key = api_key
314 request.rpc_api_key = api_key
312 request.rpc_id = json_body['id']
315 request.rpc_id = json_body['id']
313 request.rpc_method = json_body['method']
316 request.rpc_method = json_body['method']
314 request.rpc_params = json_body['args'] \
317 request.rpc_params = json_body['args'] \
315 if isinstance(json_body['args'], dict) else {}
318 if isinstance(json_body['args'], dict) else {}
316
319
317 log.debug(
320 log.debug(
318 'method: %s, params: %s' % (request.rpc_method, request.rpc_params))
321 'method: %s, params: %s' % (request.rpc_method, request.rpc_params))
319 except KeyError as e:
322 except KeyError as e:
320 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
323 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
321
324
322 log.debug('setup complete, now handling method:%s rpcid:%s',
325 log.debug('setup complete, now handling method:%s rpcid:%s',
323 request.rpc_method, request.rpc_id, )
326 request.rpc_method, request.rpc_id, )
324
327
325
328
326 class RoutePredicate(object):
329 class RoutePredicate(object):
327 def __init__(self, val, config):
330 def __init__(self, val, config):
328 self.val = val
331 self.val = val
329
332
330 def text(self):
333 def text(self):
331 return 'jsonrpc route = %s' % self.val
334 return 'jsonrpc route = %s' % self.val
332
335
333 phash = text
336 phash = text
334
337
335 def __call__(self, info, request):
338 def __call__(self, info, request):
336 if self.val:
339 if self.val:
337 # potentially setup and bootstrap our call
340 # potentially setup and bootstrap our call
338 setup_request(request)
341 setup_request(request)
339
342
340 # Always return True so that even if it isn't a valid RPC it
343 # Always return True so that even if it isn't a valid RPC it
341 # will fall through to the underlaying handlers like notfound_view
344 # will fall through to the underlaying handlers like notfound_view
342 return True
345 return True
343
346
344
347
345 class NotFoundPredicate(object):
348 class NotFoundPredicate(object):
346 def __init__(self, val, config):
349 def __init__(self, val, config):
347 self.val = val
350 self.val = val
348
351
349 def text(self):
352 def text(self):
350 return 'jsonrpc method not found = %s' % self.val
353 return 'jsonrpc method not found = %s' % self.val
351
354
352 phash = text
355 phash = text
353
356
354 def __call__(self, info, request):
357 def __call__(self, info, request):
355 return hasattr(request, 'rpc_method')
358 return hasattr(request, 'rpc_method')
356
359
357
360
358 class MethodPredicate(object):
361 class MethodPredicate(object):
359 def __init__(self, val, config):
362 def __init__(self, val, config):
360 self.method = val
363 self.method = val
361
364
362 def text(self):
365 def text(self):
363 return 'jsonrpc method = %s' % self.method
366 return 'jsonrpc method = %s' % self.method
364
367
365 phash = text
368 phash = text
366
369
367 def __call__(self, context, request):
370 def __call__(self, context, request):
368 # we need to explicitly return False here, so pyramid doesn't try to
371 # we need to explicitly return False here, so pyramid doesn't try to
369 # execute our view directly. We need our main handler to execute things
372 # execute our view directly. We need our main handler to execute things
370 return getattr(request, 'rpc_method') == self.method
373 return getattr(request, 'rpc_method') == self.method
371
374
372
375
373 def add_jsonrpc_method(config, view, **kwargs):
376 def add_jsonrpc_method(config, view, **kwargs):
374 # pop the method name
377 # pop the method name
375 method = kwargs.pop('method', None)
378 method = kwargs.pop('method', None)
376
379
377 if method is None:
380 if method is None:
378 raise ConfigurationError(
381 raise ConfigurationError(
379 'Cannot register a JSON-RPC method without specifying the '
382 'Cannot register a JSON-RPC method without specifying the '
380 '"method"')
383 '"method"')
381
384
382 # we define custom predicate, to enable to detect conflicting methods,
385 # we define custom predicate, to enable to detect conflicting methods,
383 # those predicates are kind of "translation" from the decorator variables
386 # those predicates are kind of "translation" from the decorator variables
384 # to internal predicates names
387 # to internal predicates names
385
388
386 kwargs['jsonrpc_method'] = method
389 kwargs['jsonrpc_method'] = method
387
390
388 # register our view into global view store for validation
391 # register our view into global view store for validation
389 config.registry.jsonrpc_methods[method] = view
392 config.registry.jsonrpc_methods[method] = view
390
393
391 # we're using our main request_view handler, here, so each method
394 # we're using our main request_view handler, here, so each method
392 # has a unified handler for itself
395 # has a unified handler for itself
393 config.add_view(request_view, route_name='apiv2', **kwargs)
396 config.add_view(request_view, route_name='apiv2', **kwargs)
394
397
395
398
396 class jsonrpc_method(object):
399 class jsonrpc_method(object):
397 """
400 """
398 decorator that works similar to @add_view_config decorator,
401 decorator that works similar to @add_view_config decorator,
399 but tailored for our JSON RPC
402 but tailored for our JSON RPC
400 """
403 """
401
404
402 venusian = venusian # for testing injection
405 venusian = venusian # for testing injection
403
406
404 def __init__(self, method=None, **kwargs):
407 def __init__(self, method=None, **kwargs):
405 self.method = method
408 self.method = method
406 self.kwargs = kwargs
409 self.kwargs = kwargs
407
410
408 def __call__(self, wrapped):
411 def __call__(self, wrapped):
409 kwargs = self.kwargs.copy()
412 kwargs = self.kwargs.copy()
410 kwargs['method'] = self.method or wrapped.__name__
413 kwargs['method'] = self.method or wrapped.__name__
411 depth = kwargs.pop('_depth', 0)
414 depth = kwargs.pop('_depth', 0)
412
415
413 def callback(context, name, ob):
416 def callback(context, name, ob):
414 config = context.config.with_package(info.module)
417 config = context.config.with_package(info.module)
415 config.add_jsonrpc_method(view=ob, **kwargs)
418 config.add_jsonrpc_method(view=ob, **kwargs)
416
419
417 info = venusian.attach(wrapped, callback, category='pyramid',
420 info = venusian.attach(wrapped, callback, category='pyramid',
418 depth=depth + 1)
421 depth=depth + 1)
419 if info.scope == 'class':
422 if info.scope == 'class':
420 # ensure that attr is set if decorating a class method
423 # ensure that attr is set if decorating a class method
421 kwargs.setdefault('attr', wrapped.__name__)
424 kwargs.setdefault('attr', wrapped.__name__)
422
425
423 kwargs['_info'] = info.codeinfo # fbo action_method
426 kwargs['_info'] = info.codeinfo # fbo action_method
424 return wrapped
427 return wrapped
425
428
426
429
427 class jsonrpc_deprecated_method(object):
430 class jsonrpc_deprecated_method(object):
428 """
431 """
429 Marks method as deprecated, adds log.warning, and inject special key to
432 Marks method as deprecated, adds log.warning, and inject special key to
430 the request variable to mark method as deprecated.
433 the request variable to mark method as deprecated.
431 Also injects special docstring that extract_docs will catch to mark
434 Also injects special docstring that extract_docs will catch to mark
432 method as deprecated.
435 method as deprecated.
433
436
434 :param use_method: specify which method should be used instead of
437 :param use_method: specify which method should be used instead of
435 the decorated one
438 the decorated one
436
439
437 Use like::
440 Use like::
438
441
439 @jsonrpc_method()
442 @jsonrpc_method()
440 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
443 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
441 def old_func(request, apiuser, arg1, arg2):
444 def old_func(request, apiuser, arg1, arg2):
442 ...
445 ...
443 """
446 """
444
447
445 def __init__(self, use_method, deprecated_at_version):
448 def __init__(self, use_method, deprecated_at_version):
446 self.use_method = use_method
449 self.use_method = use_method
447 self.deprecated_at_version = deprecated_at_version
450 self.deprecated_at_version = deprecated_at_version
448 self.deprecated_msg = ''
451 self.deprecated_msg = ''
449
452
450 def __call__(self, func):
453 def __call__(self, func):
451 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
454 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
452 method=self.use_method)
455 method=self.use_method)
453
456
454 docstring = """\n
457 docstring = """\n
455 .. deprecated:: {version}
458 .. deprecated:: {version}
456
459
457 {deprecation_message}
460 {deprecation_message}
458
461
459 {original_docstring}
462 {original_docstring}
460 """
463 """
461 func.__doc__ = docstring.format(
464 func.__doc__ = docstring.format(
462 version=self.deprecated_at_version,
465 version=self.deprecated_at_version,
463 deprecation_message=self.deprecated_msg,
466 deprecation_message=self.deprecated_msg,
464 original_docstring=func.__doc__)
467 original_docstring=func.__doc__)
465 return decorator.decorator(self.__wrapper, func)
468 return decorator.decorator(self.__wrapper, func)
466
469
467 def __wrapper(self, func, *fargs, **fkwargs):
470 def __wrapper(self, func, *fargs, **fkwargs):
468 log.warning('DEPRECATED API CALL on function %s, please '
471 log.warning('DEPRECATED API CALL on function %s, please '
469 'use `%s` instead', func, self.use_method)
472 'use `%s` instead', func, self.use_method)
470 # alter function docstring to mark as deprecated, this is picked up
473 # alter function docstring to mark as deprecated, this is picked up
471 # via fabric file that generates API DOC.
474 # via fabric file that generates API DOC.
472 result = func(*fargs, **fkwargs)
475 result = func(*fargs, **fkwargs)
473
476
474 request = fargs[0]
477 request = fargs[0]
475 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
478 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
476 return result
479 return result
477
480
478
481
479 def includeme(config):
482 def includeme(config):
480 plugin_module = 'rhodecode.api'
483 plugin_module = 'rhodecode.api'
481 plugin_settings = get_plugin_settings(
484 plugin_settings = get_plugin_settings(
482 plugin_module, config.registry.settings)
485 plugin_module, config.registry.settings)
483
486
484 if not hasattr(config.registry, 'jsonrpc_methods'):
487 if not hasattr(config.registry, 'jsonrpc_methods'):
485 config.registry.jsonrpc_methods = OrderedDict()
488 config.registry.jsonrpc_methods = OrderedDict()
486
489
487 # match filter by given method only
490 # match filter by given method only
488 config.add_view_predicate(
491 config.add_view_predicate('jsonrpc_method', MethodPredicate)
489 'jsonrpc_method', MethodPredicate)
490
492
491 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
493 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
492 serializer=json.dumps, indent=4))
494 serializer=json.dumps, indent=4))
493 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
495 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
494
496
495 config.add_route_predicate(
497 config.add_route_predicate(
496 'jsonrpc_call', RoutePredicate)
498 'jsonrpc_call', RoutePredicate)
497
499
498 config.add_route(
500 config.add_route(
499 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
501 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
500
502
501 config.scan(plugin_module, ignore='rhodecode.api.tests')
503 config.scan(plugin_module, ignore='rhodecode.api.tests')
502 # register some exception handling view
504 # register some exception handling view
503 config.add_view(exception_view, context=JSONRPCBaseError)
505 config.add_view(exception_view, context=JSONRPCBaseError)
504 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
506 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
505 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
507 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
@@ -1,1906 +1,1906 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 authentication and permission libraries
22 authentication and permission libraries
23 """
23 """
24
24
25 import inspect
25 import inspect
26 import collections
26 import collections
27 import fnmatch
27 import fnmatch
28 import hashlib
28 import hashlib
29 import itertools
29 import itertools
30 import logging
30 import logging
31 import os
31 import os
32 import random
32 import random
33 import time
33 import time
34 import traceback
34 import traceback
35 from functools import wraps
35 from functools import wraps
36
36
37 import ipaddress
37 import ipaddress
38 from pylons import url, request
38 from pylons import url, request
39 from pylons.controllers.util import abort, redirect
39 from pylons.controllers.util import abort, redirect
40 from pylons.i18n.translation import _
40 from pylons.i18n.translation import _
41 from sqlalchemy import or_
41 from sqlalchemy import or_
42 from sqlalchemy.orm.exc import ObjectDeletedError
42 from sqlalchemy.orm.exc import ObjectDeletedError
43 from sqlalchemy.orm import joinedload
43 from sqlalchemy.orm import joinedload
44 from zope.cachedescriptors.property import Lazy as LazyProperty
44 from zope.cachedescriptors.property import Lazy as LazyProperty
45
45
46 import rhodecode
46 import rhodecode
47 from rhodecode.model import meta
47 from rhodecode.model import meta
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.model.user import UserModel
49 from rhodecode.model.user import UserModel
50 from rhodecode.model.db import (
50 from rhodecode.model.db import (
51 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
51 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
52 UserIpMap, UserApiKeys, RepoGroup)
52 UserIpMap, UserApiKeys, RepoGroup)
53 from rhodecode.lib import caches
53 from rhodecode.lib import caches
54 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5
54 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5
55 from rhodecode.lib.utils import (
55 from rhodecode.lib.utils import (
56 get_repo_slug, get_repo_group_slug, get_user_group_slug)
56 get_repo_slug, get_repo_group_slug, get_user_group_slug)
57 from rhodecode.lib.caching_query import FromCache
57 from rhodecode.lib.caching_query import FromCache
58
58
59
59
60 if rhodecode.is_unix:
60 if rhodecode.is_unix:
61 import bcrypt
61 import bcrypt
62
62
63 log = logging.getLogger(__name__)
63 log = logging.getLogger(__name__)
64
64
65 csrf_token_key = "csrf_token"
65 csrf_token_key = "csrf_token"
66
66
67
67
68 class PasswordGenerator(object):
68 class PasswordGenerator(object):
69 """
69 """
70 This is a simple class for generating password from different sets of
70 This is a simple class for generating password from different sets of
71 characters
71 characters
72 usage::
72 usage::
73
73
74 passwd_gen = PasswordGenerator()
74 passwd_gen = PasswordGenerator()
75 #print 8-letter password containing only big and small letters
75 #print 8-letter password containing only big and small letters
76 of alphabet
76 of alphabet
77 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
77 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
78 """
78 """
79 ALPHABETS_NUM = r'''1234567890'''
79 ALPHABETS_NUM = r'''1234567890'''
80 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
80 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
81 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
81 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
82 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
82 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
83 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
83 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
84 + ALPHABETS_NUM + ALPHABETS_SPECIAL
84 + ALPHABETS_NUM + ALPHABETS_SPECIAL
85 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
85 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
86 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
86 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
87 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
87 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
88 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
88 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
89
89
90 def __init__(self, passwd=''):
90 def __init__(self, passwd=''):
91 self.passwd = passwd
91 self.passwd = passwd
92
92
93 def gen_password(self, length, type_=None):
93 def gen_password(self, length, type_=None):
94 if type_ is None:
94 if type_ is None:
95 type_ = self.ALPHABETS_FULL
95 type_ = self.ALPHABETS_FULL
96 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
96 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
97 return self.passwd
97 return self.passwd
98
98
99
99
100 class _RhodeCodeCryptoBase(object):
100 class _RhodeCodeCryptoBase(object):
101
101
102 def hash_create(self, str_):
102 def hash_create(self, str_):
103 """
103 """
104 hash the string using
104 hash the string using
105
105
106 :param str_: password to hash
106 :param str_: password to hash
107 """
107 """
108 raise NotImplementedError
108 raise NotImplementedError
109
109
110 def hash_check_with_upgrade(self, password, hashed):
110 def hash_check_with_upgrade(self, password, hashed):
111 """
111 """
112 Returns tuple in which first element is boolean that states that
112 Returns tuple in which first element is boolean that states that
113 given password matches it's hashed version, and the second is new hash
113 given password matches it's hashed version, and the second is new hash
114 of the password, in case this password should be migrated to new
114 of the password, in case this password should be migrated to new
115 cipher.
115 cipher.
116 """
116 """
117 checked_hash = self.hash_check(password, hashed)
117 checked_hash = self.hash_check(password, hashed)
118 return checked_hash, None
118 return checked_hash, None
119
119
120 def hash_check(self, password, hashed):
120 def hash_check(self, password, hashed):
121 """
121 """
122 Checks matching password with it's hashed value.
122 Checks matching password with it's hashed value.
123
123
124 :param password: password
124 :param password: password
125 :param hashed: password in hashed form
125 :param hashed: password in hashed form
126 """
126 """
127 raise NotImplementedError
127 raise NotImplementedError
128
128
129 def _assert_bytes(self, value):
129 def _assert_bytes(self, value):
130 """
130 """
131 Passing in an `unicode` object can lead to hard to detect issues
131 Passing in an `unicode` object can lead to hard to detect issues
132 if passwords contain non-ascii characters. Doing a type check
132 if passwords contain non-ascii characters. Doing a type check
133 during runtime, so that such mistakes are detected early on.
133 during runtime, so that such mistakes are detected early on.
134 """
134 """
135 if not isinstance(value, str):
135 if not isinstance(value, str):
136 raise TypeError(
136 raise TypeError(
137 "Bytestring required as input, got %r." % (value, ))
137 "Bytestring required as input, got %r." % (value, ))
138
138
139
139
140 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
140 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
141
141
142 def hash_create(self, str_):
142 def hash_create(self, str_):
143 self._assert_bytes(str_)
143 self._assert_bytes(str_)
144 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
144 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
145
145
146 def hash_check_with_upgrade(self, password, hashed):
146 def hash_check_with_upgrade(self, password, hashed):
147 """
147 """
148 Returns tuple in which first element is boolean that states that
148 Returns tuple in which first element is boolean that states that
149 given password matches it's hashed version, and the second is new hash
149 given password matches it's hashed version, and the second is new hash
150 of the password, in case this password should be migrated to new
150 of the password, in case this password should be migrated to new
151 cipher.
151 cipher.
152
152
153 This implements special upgrade logic which works like that:
153 This implements special upgrade logic which works like that:
154 - check if the given password == bcrypted hash, if yes then we
154 - check if the given password == bcrypted hash, if yes then we
155 properly used password and it was already in bcrypt. Proceed
155 properly used password and it was already in bcrypt. Proceed
156 without any changes
156 without any changes
157 - if bcrypt hash check is not working try with sha256. If hash compare
157 - if bcrypt hash check is not working try with sha256. If hash compare
158 is ok, it means we using correct but old hashed password. indicate
158 is ok, it means we using correct but old hashed password. indicate
159 hash change and proceed
159 hash change and proceed
160 """
160 """
161
161
162 new_hash = None
162 new_hash = None
163
163
164 # regular pw check
164 # regular pw check
165 password_match_bcrypt = self.hash_check(password, hashed)
165 password_match_bcrypt = self.hash_check(password, hashed)
166
166
167 # now we want to know if the password was maybe from sha256
167 # now we want to know if the password was maybe from sha256
168 # basically calling _RhodeCodeCryptoSha256().hash_check()
168 # basically calling _RhodeCodeCryptoSha256().hash_check()
169 if not password_match_bcrypt:
169 if not password_match_bcrypt:
170 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
170 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
171 new_hash = self.hash_create(password) # make new bcrypt hash
171 new_hash = self.hash_create(password) # make new bcrypt hash
172 password_match_bcrypt = True
172 password_match_bcrypt = True
173
173
174 return password_match_bcrypt, new_hash
174 return password_match_bcrypt, new_hash
175
175
176 def hash_check(self, password, hashed):
176 def hash_check(self, password, hashed):
177 """
177 """
178 Checks matching password with it's hashed value.
178 Checks matching password with it's hashed value.
179
179
180 :param password: password
180 :param password: password
181 :param hashed: password in hashed form
181 :param hashed: password in hashed form
182 """
182 """
183 self._assert_bytes(password)
183 self._assert_bytes(password)
184 try:
184 try:
185 return bcrypt.hashpw(password, hashed) == hashed
185 return bcrypt.hashpw(password, hashed) == hashed
186 except ValueError as e:
186 except ValueError as e:
187 # we're having a invalid salt here probably, we should not crash
187 # we're having a invalid salt here probably, we should not crash
188 # just return with False as it would be a wrong password.
188 # just return with False as it would be a wrong password.
189 log.debug('Failed to check password hash using bcrypt %s',
189 log.debug('Failed to check password hash using bcrypt %s',
190 safe_str(e))
190 safe_str(e))
191
191
192 return False
192 return False
193
193
194
194
195 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
195 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
196
196
197 def hash_create(self, str_):
197 def hash_create(self, str_):
198 self._assert_bytes(str_)
198 self._assert_bytes(str_)
199 return hashlib.sha256(str_).hexdigest()
199 return hashlib.sha256(str_).hexdigest()
200
200
201 def hash_check(self, password, hashed):
201 def hash_check(self, password, hashed):
202 """
202 """
203 Checks matching password with it's hashed value.
203 Checks matching password with it's hashed value.
204
204
205 :param password: password
205 :param password: password
206 :param hashed: password in hashed form
206 :param hashed: password in hashed form
207 """
207 """
208 self._assert_bytes(password)
208 self._assert_bytes(password)
209 return hashlib.sha256(password).hexdigest() == hashed
209 return hashlib.sha256(password).hexdigest() == hashed
210
210
211
211
212 class _RhodeCodeCryptoMd5(_RhodeCodeCryptoBase):
212 class _RhodeCodeCryptoMd5(_RhodeCodeCryptoBase):
213
213
214 def hash_create(self, str_):
214 def hash_create(self, str_):
215 self._assert_bytes(str_)
215 self._assert_bytes(str_)
216 return hashlib.md5(str_).hexdigest()
216 return hashlib.md5(str_).hexdigest()
217
217
218 def hash_check(self, password, hashed):
218 def hash_check(self, password, hashed):
219 """
219 """
220 Checks matching password with it's hashed value.
220 Checks matching password with it's hashed value.
221
221
222 :param password: password
222 :param password: password
223 :param hashed: password in hashed form
223 :param hashed: password in hashed form
224 """
224 """
225 self._assert_bytes(password)
225 self._assert_bytes(password)
226 return hashlib.md5(password).hexdigest() == hashed
226 return hashlib.md5(password).hexdigest() == hashed
227
227
228
228
229 def crypto_backend():
229 def crypto_backend():
230 """
230 """
231 Return the matching crypto backend.
231 Return the matching crypto backend.
232
232
233 Selection is based on if we run tests or not, we pick md5 backend to run
233 Selection is based on if we run tests or not, we pick md5 backend to run
234 tests faster since BCRYPT is expensive to calculate
234 tests faster since BCRYPT is expensive to calculate
235 """
235 """
236 if rhodecode.is_test:
236 if rhodecode.is_test:
237 RhodeCodeCrypto = _RhodeCodeCryptoMd5()
237 RhodeCodeCrypto = _RhodeCodeCryptoMd5()
238 else:
238 else:
239 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
239 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
240
240
241 return RhodeCodeCrypto
241 return RhodeCodeCrypto
242
242
243
243
244 def get_crypt_password(password):
244 def get_crypt_password(password):
245 """
245 """
246 Create the hash of `password` with the active crypto backend.
246 Create the hash of `password` with the active crypto backend.
247
247
248 :param password: The cleartext password.
248 :param password: The cleartext password.
249 :type password: unicode
249 :type password: unicode
250 """
250 """
251 password = safe_str(password)
251 password = safe_str(password)
252 return crypto_backend().hash_create(password)
252 return crypto_backend().hash_create(password)
253
253
254
254
255 def check_password(password, hashed):
255 def check_password(password, hashed):
256 """
256 """
257 Check if the value in `password` matches the hash in `hashed`.
257 Check if the value in `password` matches the hash in `hashed`.
258
258
259 :param password: The cleartext password.
259 :param password: The cleartext password.
260 :type password: unicode
260 :type password: unicode
261
261
262 :param hashed: The expected hashed version of the password.
262 :param hashed: The expected hashed version of the password.
263 :type hashed: The hash has to be passed in in text representation.
263 :type hashed: The hash has to be passed in in text representation.
264 """
264 """
265 password = safe_str(password)
265 password = safe_str(password)
266 return crypto_backend().hash_check(password, hashed)
266 return crypto_backend().hash_check(password, hashed)
267
267
268
268
269 def generate_auth_token(data, salt=None):
269 def generate_auth_token(data, salt=None):
270 """
270 """
271 Generates API KEY from given string
271 Generates API KEY from given string
272 """
272 """
273
273
274 if salt is None:
274 if salt is None:
275 salt = os.urandom(16)
275 salt = os.urandom(16)
276 return hashlib.sha1(safe_str(data) + salt).hexdigest()
276 return hashlib.sha1(safe_str(data) + salt).hexdigest()
277
277
278
278
279 class CookieStoreWrapper(object):
279 class CookieStoreWrapper(object):
280
280
281 def __init__(self, cookie_store):
281 def __init__(self, cookie_store):
282 self.cookie_store = cookie_store
282 self.cookie_store = cookie_store
283
283
284 def __repr__(self):
284 def __repr__(self):
285 return 'CookieStore<%s>' % (self.cookie_store)
285 return 'CookieStore<%s>' % (self.cookie_store)
286
286
287 def get(self, key, other=None):
287 def get(self, key, other=None):
288 if isinstance(self.cookie_store, dict):
288 if isinstance(self.cookie_store, dict):
289 return self.cookie_store.get(key, other)
289 return self.cookie_store.get(key, other)
290 elif isinstance(self.cookie_store, AuthUser):
290 elif isinstance(self.cookie_store, AuthUser):
291 return self.cookie_store.__dict__.get(key, other)
291 return self.cookie_store.__dict__.get(key, other)
292
292
293
293
294 def _cached_perms_data(user_id, scope, user_is_admin,
294 def _cached_perms_data(user_id, scope, user_is_admin,
295 user_inherit_default_permissions, explicit, algo):
295 user_inherit_default_permissions, explicit, algo):
296
296
297 permissions = PermissionCalculator(
297 permissions = PermissionCalculator(
298 user_id, scope, user_is_admin, user_inherit_default_permissions,
298 user_id, scope, user_is_admin, user_inherit_default_permissions,
299 explicit, algo)
299 explicit, algo)
300 return permissions.calculate()
300 return permissions.calculate()
301
301
302 class PermOrigin:
302 class PermOrigin:
303 ADMIN = 'superadmin'
303 ADMIN = 'superadmin'
304
304
305 REPO_USER = 'user:%s'
305 REPO_USER = 'user:%s'
306 REPO_USERGROUP = 'usergroup:%s'
306 REPO_USERGROUP = 'usergroup:%s'
307 REPO_OWNER = 'repo.owner'
307 REPO_OWNER = 'repo.owner'
308 REPO_DEFAULT = 'repo.default'
308 REPO_DEFAULT = 'repo.default'
309 REPO_PRIVATE = 'repo.private'
309 REPO_PRIVATE = 'repo.private'
310
310
311 REPOGROUP_USER = 'user:%s'
311 REPOGROUP_USER = 'user:%s'
312 REPOGROUP_USERGROUP = 'usergroup:%s'
312 REPOGROUP_USERGROUP = 'usergroup:%s'
313 REPOGROUP_OWNER = 'group.owner'
313 REPOGROUP_OWNER = 'group.owner'
314 REPOGROUP_DEFAULT = 'group.default'
314 REPOGROUP_DEFAULT = 'group.default'
315
315
316 USERGROUP_USER = 'user:%s'
316 USERGROUP_USER = 'user:%s'
317 USERGROUP_USERGROUP = 'usergroup:%s'
317 USERGROUP_USERGROUP = 'usergroup:%s'
318 USERGROUP_OWNER = 'usergroup.owner'
318 USERGROUP_OWNER = 'usergroup.owner'
319 USERGROUP_DEFAULT = 'usergroup.default'
319 USERGROUP_DEFAULT = 'usergroup.default'
320
320
321
321
322 class PermOriginDict(dict):
322 class PermOriginDict(dict):
323 """
323 """
324 A special dict used for tracking permissions along with their origins.
324 A special dict used for tracking permissions along with their origins.
325
325
326 `__setitem__` has been overridden to expect a tuple(perm, origin)
326 `__setitem__` has been overridden to expect a tuple(perm, origin)
327 `__getitem__` will return only the perm
327 `__getitem__` will return only the perm
328 `.perm_origin_stack` will return the stack of (perm, origin) set per key
328 `.perm_origin_stack` will return the stack of (perm, origin) set per key
329
329
330 >>> perms = PermOriginDict()
330 >>> perms = PermOriginDict()
331 >>> perms['resource'] = 'read', 'default'
331 >>> perms['resource'] = 'read', 'default'
332 >>> perms['resource']
332 >>> perms['resource']
333 'read'
333 'read'
334 >>> perms['resource'] = 'write', 'admin'
334 >>> perms['resource'] = 'write', 'admin'
335 >>> perms['resource']
335 >>> perms['resource']
336 'write'
336 'write'
337 >>> perms.perm_origin_stack
337 >>> perms.perm_origin_stack
338 {'resource': [('read', 'default'), ('write', 'admin')]}
338 {'resource': [('read', 'default'), ('write', 'admin')]}
339 """
339 """
340
340
341
341
342 def __init__(self, *args, **kw):
342 def __init__(self, *args, **kw):
343 dict.__init__(self, *args, **kw)
343 dict.__init__(self, *args, **kw)
344 self.perm_origin_stack = {}
344 self.perm_origin_stack = {}
345
345
346 def __setitem__(self, key, (perm, origin)):
346 def __setitem__(self, key, (perm, origin)):
347 self.perm_origin_stack.setdefault(key, []).append((perm, origin))
347 self.perm_origin_stack.setdefault(key, []).append((perm, origin))
348 dict.__setitem__(self, key, perm)
348 dict.__setitem__(self, key, perm)
349
349
350
350
351 class PermissionCalculator(object):
351 class PermissionCalculator(object):
352
352
353 def __init__(
353 def __init__(
354 self, user_id, scope, user_is_admin,
354 self, user_id, scope, user_is_admin,
355 user_inherit_default_permissions, explicit, algo):
355 user_inherit_default_permissions, explicit, algo):
356 self.user_id = user_id
356 self.user_id = user_id
357 self.user_is_admin = user_is_admin
357 self.user_is_admin = user_is_admin
358 self.inherit_default_permissions = user_inherit_default_permissions
358 self.inherit_default_permissions = user_inherit_default_permissions
359 self.explicit = explicit
359 self.explicit = explicit
360 self.algo = algo
360 self.algo = algo
361
361
362 scope = scope or {}
362 scope = scope or {}
363 self.scope_repo_id = scope.get('repo_id')
363 self.scope_repo_id = scope.get('repo_id')
364 self.scope_repo_group_id = scope.get('repo_group_id')
364 self.scope_repo_group_id = scope.get('repo_group_id')
365 self.scope_user_group_id = scope.get('user_group_id')
365 self.scope_user_group_id = scope.get('user_group_id')
366
366
367 self.default_user_id = User.get_default_user(cache=True).user_id
367 self.default_user_id = User.get_default_user(cache=True).user_id
368
368
369 self.permissions_repositories = PermOriginDict()
369 self.permissions_repositories = PermOriginDict()
370 self.permissions_repository_groups = PermOriginDict()
370 self.permissions_repository_groups = PermOriginDict()
371 self.permissions_user_groups = PermOriginDict()
371 self.permissions_user_groups = PermOriginDict()
372 self.permissions_global = set()
372 self.permissions_global = set()
373
373
374 self.default_repo_perms = Permission.get_default_repo_perms(
374 self.default_repo_perms = Permission.get_default_repo_perms(
375 self.default_user_id, self.scope_repo_id)
375 self.default_user_id, self.scope_repo_id)
376 self.default_repo_groups_perms = Permission.get_default_group_perms(
376 self.default_repo_groups_perms = Permission.get_default_group_perms(
377 self.default_user_id, self.scope_repo_group_id)
377 self.default_user_id, self.scope_repo_group_id)
378 self.default_user_group_perms = \
378 self.default_user_group_perms = \
379 Permission.get_default_user_group_perms(
379 Permission.get_default_user_group_perms(
380 self.default_user_id, self.scope_user_group_id)
380 self.default_user_id, self.scope_user_group_id)
381
381
382 def calculate(self):
382 def calculate(self):
383 if self.user_is_admin:
383 if self.user_is_admin:
384 return self._admin_permissions()
384 return self._admin_permissions()
385
385
386 self._calculate_global_default_permissions()
386 self._calculate_global_default_permissions()
387 self._calculate_global_permissions()
387 self._calculate_global_permissions()
388 self._calculate_default_permissions()
388 self._calculate_default_permissions()
389 self._calculate_repository_permissions()
389 self._calculate_repository_permissions()
390 self._calculate_repository_group_permissions()
390 self._calculate_repository_group_permissions()
391 self._calculate_user_group_permissions()
391 self._calculate_user_group_permissions()
392 return self._permission_structure()
392 return self._permission_structure()
393
393
394 def _admin_permissions(self):
394 def _admin_permissions(self):
395 """
395 """
396 admin user have all default rights for repositories
396 admin user have all default rights for repositories
397 and groups set to admin
397 and groups set to admin
398 """
398 """
399 self.permissions_global.add('hg.admin')
399 self.permissions_global.add('hg.admin')
400 self.permissions_global.add('hg.create.write_on_repogroup.true')
400 self.permissions_global.add('hg.create.write_on_repogroup.true')
401
401
402 # repositories
402 # repositories
403 for perm in self.default_repo_perms:
403 for perm in self.default_repo_perms:
404 r_k = perm.UserRepoToPerm.repository.repo_name
404 r_k = perm.UserRepoToPerm.repository.repo_name
405 p = 'repository.admin'
405 p = 'repository.admin'
406 self.permissions_repositories[r_k] = p, PermOrigin.ADMIN
406 self.permissions_repositories[r_k] = p, PermOrigin.ADMIN
407
407
408 # repository groups
408 # repository groups
409 for perm in self.default_repo_groups_perms:
409 for perm in self.default_repo_groups_perms:
410 rg_k = perm.UserRepoGroupToPerm.group.group_name
410 rg_k = perm.UserRepoGroupToPerm.group.group_name
411 p = 'group.admin'
411 p = 'group.admin'
412 self.permissions_repository_groups[rg_k] = p, PermOrigin.ADMIN
412 self.permissions_repository_groups[rg_k] = p, PermOrigin.ADMIN
413
413
414 # user groups
414 # user groups
415 for perm in self.default_user_group_perms:
415 for perm in self.default_user_group_perms:
416 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
416 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
417 p = 'usergroup.admin'
417 p = 'usergroup.admin'
418 self.permissions_user_groups[u_k] = p, PermOrigin.ADMIN
418 self.permissions_user_groups[u_k] = p, PermOrigin.ADMIN
419
419
420 return self._permission_structure()
420 return self._permission_structure()
421
421
422 def _calculate_global_default_permissions(self):
422 def _calculate_global_default_permissions(self):
423 """
423 """
424 global permissions taken from the default user
424 global permissions taken from the default user
425 """
425 """
426 default_global_perms = UserToPerm.query()\
426 default_global_perms = UserToPerm.query()\
427 .filter(UserToPerm.user_id == self.default_user_id)\
427 .filter(UserToPerm.user_id == self.default_user_id)\
428 .options(joinedload(UserToPerm.permission))
428 .options(joinedload(UserToPerm.permission))
429
429
430 for perm in default_global_perms:
430 for perm in default_global_perms:
431 self.permissions_global.add(perm.permission.permission_name)
431 self.permissions_global.add(perm.permission.permission_name)
432
432
433 def _calculate_global_permissions(self):
433 def _calculate_global_permissions(self):
434 """
434 """
435 Set global system permissions with user permissions or permissions
435 Set global system permissions with user permissions or permissions
436 taken from the user groups of the current user.
436 taken from the user groups of the current user.
437
437
438 The permissions include repo creating, repo group creating, forking
438 The permissions include repo creating, repo group creating, forking
439 etc.
439 etc.
440 """
440 """
441
441
442 # now we read the defined permissions and overwrite what we have set
442 # now we read the defined permissions and overwrite what we have set
443 # before those can be configured from groups or users explicitly.
443 # before those can be configured from groups or users explicitly.
444
444
445 # TODO: johbo: This seems to be out of sync, find out the reason
445 # TODO: johbo: This seems to be out of sync, find out the reason
446 # for the comment below and update it.
446 # for the comment below and update it.
447
447
448 # In case we want to extend this list we should be always in sync with
448 # In case we want to extend this list we should be always in sync with
449 # User.DEFAULT_USER_PERMISSIONS definitions
449 # User.DEFAULT_USER_PERMISSIONS definitions
450 _configurable = frozenset([
450 _configurable = frozenset([
451 'hg.fork.none', 'hg.fork.repository',
451 'hg.fork.none', 'hg.fork.repository',
452 'hg.create.none', 'hg.create.repository',
452 'hg.create.none', 'hg.create.repository',
453 'hg.usergroup.create.false', 'hg.usergroup.create.true',
453 'hg.usergroup.create.false', 'hg.usergroup.create.true',
454 'hg.repogroup.create.false', 'hg.repogroup.create.true',
454 'hg.repogroup.create.false', 'hg.repogroup.create.true',
455 'hg.create.write_on_repogroup.false',
455 'hg.create.write_on_repogroup.false',
456 'hg.create.write_on_repogroup.true',
456 'hg.create.write_on_repogroup.true',
457 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
457 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
458 ])
458 ])
459
459
460 # USER GROUPS comes first user group global permissions
460 # USER GROUPS comes first user group global permissions
461 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
461 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
462 .options(joinedload(UserGroupToPerm.permission))\
462 .options(joinedload(UserGroupToPerm.permission))\
463 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
463 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
464 UserGroupMember.users_group_id))\
464 UserGroupMember.users_group_id))\
465 .filter(UserGroupMember.user_id == self.user_id)\
465 .filter(UserGroupMember.user_id == self.user_id)\
466 .order_by(UserGroupToPerm.users_group_id)\
466 .order_by(UserGroupToPerm.users_group_id)\
467 .all()
467 .all()
468
468
469 # need to group here by groups since user can be in more than
469 # need to group here by groups since user can be in more than
470 # one group, so we get all groups
470 # one group, so we get all groups
471 _explicit_grouped_perms = [
471 _explicit_grouped_perms = [
472 [x, list(y)] for x, y in
472 [x, list(y)] for x, y in
473 itertools.groupby(user_perms_from_users_groups,
473 itertools.groupby(user_perms_from_users_groups,
474 lambda _x: _x.users_group)]
474 lambda _x: _x.users_group)]
475
475
476 for gr, perms in _explicit_grouped_perms:
476 for gr, perms in _explicit_grouped_perms:
477 # since user can be in multiple groups iterate over them and
477 # since user can be in multiple groups iterate over them and
478 # select the lowest permissions first (more explicit)
478 # select the lowest permissions first (more explicit)
479 # TODO: marcink: do this^^
479 # TODO: marcink: do this^^
480
480
481 # group doesn't inherit default permissions so we actually set them
481 # group doesn't inherit default permissions so we actually set them
482 if not gr.inherit_default_permissions:
482 if not gr.inherit_default_permissions:
483 # NEED TO IGNORE all previously set configurable permissions
483 # NEED TO IGNORE all previously set configurable permissions
484 # and replace them with explicitly set from this user
484 # and replace them with explicitly set from this user
485 # group permissions
485 # group permissions
486 self.permissions_global = self.permissions_global.difference(
486 self.permissions_global = self.permissions_global.difference(
487 _configurable)
487 _configurable)
488 for perm in perms:
488 for perm in perms:
489 self.permissions_global.add(perm.permission.permission_name)
489 self.permissions_global.add(perm.permission.permission_name)
490
490
491 # user explicit global permissions
491 # user explicit global permissions
492 user_perms = Session().query(UserToPerm)\
492 user_perms = Session().query(UserToPerm)\
493 .options(joinedload(UserToPerm.permission))\
493 .options(joinedload(UserToPerm.permission))\
494 .filter(UserToPerm.user_id == self.user_id).all()
494 .filter(UserToPerm.user_id == self.user_id).all()
495
495
496 if not self.inherit_default_permissions:
496 if not self.inherit_default_permissions:
497 # NEED TO IGNORE all configurable permissions and
497 # NEED TO IGNORE all configurable permissions and
498 # replace them with explicitly set from this user permissions
498 # replace them with explicitly set from this user permissions
499 self.permissions_global = self.permissions_global.difference(
499 self.permissions_global = self.permissions_global.difference(
500 _configurable)
500 _configurable)
501 for perm in user_perms:
501 for perm in user_perms:
502 self.permissions_global.add(perm.permission.permission_name)
502 self.permissions_global.add(perm.permission.permission_name)
503
503
504 def _calculate_default_permissions(self):
504 def _calculate_default_permissions(self):
505 """
505 """
506 Set default user permissions for repositories, repository groups
506 Set default user permissions for repositories, repository groups
507 taken from the default user.
507 taken from the default user.
508
508
509 Calculate inheritance of object permissions based on what we have now
509 Calculate inheritance of object permissions based on what we have now
510 in GLOBAL permissions. We check if .false is in GLOBAL since this is
510 in GLOBAL permissions. We check if .false is in GLOBAL since this is
511 explicitly set. Inherit is the opposite of .false being there.
511 explicitly set. Inherit is the opposite of .false being there.
512
512
513 .. note::
513 .. note::
514
514
515 the syntax is little bit odd but what we need to check here is
515 the syntax is little bit odd but what we need to check here is
516 the opposite of .false permission being in the list so even for
516 the opposite of .false permission being in the list so even for
517 inconsistent state when both .true/.false is there
517 inconsistent state when both .true/.false is there
518 .false is more important
518 .false is more important
519
519
520 """
520 """
521 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
521 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
522 in self.permissions_global)
522 in self.permissions_global)
523
523
524 # defaults for repositories, taken from `default` user permissions
524 # defaults for repositories, taken from `default` user permissions
525 # on given repo
525 # on given repo
526 for perm in self.default_repo_perms:
526 for perm in self.default_repo_perms:
527 r_k = perm.UserRepoToPerm.repository.repo_name
527 r_k = perm.UserRepoToPerm.repository.repo_name
528 o = PermOrigin.REPO_DEFAULT
528 o = PermOrigin.REPO_DEFAULT
529 if perm.Repository.private and not (
529 if perm.Repository.private and not (
530 perm.Repository.user_id == self.user_id):
530 perm.Repository.user_id == self.user_id):
531 # disable defaults for private repos,
531 # disable defaults for private repos,
532 p = 'repository.none'
532 p = 'repository.none'
533 o = PermOrigin.REPO_PRIVATE
533 o = PermOrigin.REPO_PRIVATE
534 elif perm.Repository.user_id == self.user_id:
534 elif perm.Repository.user_id == self.user_id:
535 # set admin if owner
535 # set admin if owner
536 p = 'repository.admin'
536 p = 'repository.admin'
537 o = PermOrigin.REPO_OWNER
537 o = PermOrigin.REPO_OWNER
538 else:
538 else:
539 p = perm.Permission.permission_name
539 p = perm.Permission.permission_name
540 # if we decide this user isn't inheriting permissions from
540 # if we decide this user isn't inheriting permissions from
541 # default user we set him to .none so only explicit
541 # default user we set him to .none so only explicit
542 # permissions work
542 # permissions work
543 if not user_inherit_object_permissions:
543 if not user_inherit_object_permissions:
544 p = 'repository.none'
544 p = 'repository.none'
545 self.permissions_repositories[r_k] = p, o
545 self.permissions_repositories[r_k] = p, o
546
546
547 # defaults for repository groups taken from `default` user permission
547 # defaults for repository groups taken from `default` user permission
548 # on given group
548 # on given group
549 for perm in self.default_repo_groups_perms:
549 for perm in self.default_repo_groups_perms:
550 rg_k = perm.UserRepoGroupToPerm.group.group_name
550 rg_k = perm.UserRepoGroupToPerm.group.group_name
551 o = PermOrigin.REPOGROUP_DEFAULT
551 o = PermOrigin.REPOGROUP_DEFAULT
552 if perm.RepoGroup.user_id == self.user_id:
552 if perm.RepoGroup.user_id == self.user_id:
553 # set admin if owner
553 # set admin if owner
554 p = 'group.admin'
554 p = 'group.admin'
555 o = PermOrigin.REPOGROUP_OWNER
555 o = PermOrigin.REPOGROUP_OWNER
556 else:
556 else:
557 p = perm.Permission.permission_name
557 p = perm.Permission.permission_name
558
558
559 # if we decide this user isn't inheriting permissions from default
559 # if we decide this user isn't inheriting permissions from default
560 # user we set him to .none so only explicit permissions work
560 # user we set him to .none so only explicit permissions work
561 if not user_inherit_object_permissions:
561 if not user_inherit_object_permissions:
562 p = 'group.none'
562 p = 'group.none'
563 self.permissions_repository_groups[rg_k] = p, o
563 self.permissions_repository_groups[rg_k] = p, o
564
564
565 # defaults for user groups taken from `default` user permission
565 # defaults for user groups taken from `default` user permission
566 # on given user group
566 # on given user group
567 for perm in self.default_user_group_perms:
567 for perm in self.default_user_group_perms:
568 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
568 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
569 p = perm.Permission.permission_name
569 p = perm.Permission.permission_name
570 o = PermOrigin.USERGROUP_DEFAULT
570 o = PermOrigin.USERGROUP_DEFAULT
571 # if we decide this user isn't inheriting permissions from default
571 # if we decide this user isn't inheriting permissions from default
572 # user we set him to .none so only explicit permissions work
572 # user we set him to .none so only explicit permissions work
573 if not user_inherit_object_permissions:
573 if not user_inherit_object_permissions:
574 p = 'usergroup.none'
574 p = 'usergroup.none'
575 self.permissions_user_groups[u_k] = p, o
575 self.permissions_user_groups[u_k] = p, o
576
576
577 def _calculate_repository_permissions(self):
577 def _calculate_repository_permissions(self):
578 """
578 """
579 Repository permissions for the current user.
579 Repository permissions for the current user.
580
580
581 Check if the user is part of user groups for this repository and
581 Check if the user is part of user groups for this repository and
582 fill in the permission from it. `_choose_permission` decides of which
582 fill in the permission from it. `_choose_permission` decides of which
583 permission should be selected based on selected method.
583 permission should be selected based on selected method.
584 """
584 """
585
585
586 # user group for repositories permissions
586 # user group for repositories permissions
587 user_repo_perms_from_user_group = Permission\
587 user_repo_perms_from_user_group = Permission\
588 .get_default_repo_perms_from_user_group(
588 .get_default_repo_perms_from_user_group(
589 self.user_id, self.scope_repo_id)
589 self.user_id, self.scope_repo_id)
590
590
591 multiple_counter = collections.defaultdict(int)
591 multiple_counter = collections.defaultdict(int)
592 for perm in user_repo_perms_from_user_group:
592 for perm in user_repo_perms_from_user_group:
593 r_k = perm.UserGroupRepoToPerm.repository.repo_name
593 r_k = perm.UserGroupRepoToPerm.repository.repo_name
594 ug_k = perm.UserGroupRepoToPerm.users_group.users_group_name
594 ug_k = perm.UserGroupRepoToPerm.users_group.users_group_name
595 multiple_counter[r_k] += 1
595 multiple_counter[r_k] += 1
596 p = perm.Permission.permission_name
596 p = perm.Permission.permission_name
597 o = PermOrigin.REPO_USERGROUP % ug_k
597 o = PermOrigin.REPO_USERGROUP % ug_k
598
598
599 if perm.Repository.user_id == self.user_id:
599 if perm.Repository.user_id == self.user_id:
600 # set admin if owner
600 # set admin if owner
601 p = 'repository.admin'
601 p = 'repository.admin'
602 o = PermOrigin.REPO_OWNER
602 o = PermOrigin.REPO_OWNER
603 else:
603 else:
604 if multiple_counter[r_k] > 1:
604 if multiple_counter[r_k] > 1:
605 cur_perm = self.permissions_repositories[r_k]
605 cur_perm = self.permissions_repositories[r_k]
606 p = self._choose_permission(p, cur_perm)
606 p = self._choose_permission(p, cur_perm)
607 self.permissions_repositories[r_k] = p, o
607 self.permissions_repositories[r_k] = p, o
608
608
609 # user explicit permissions for repositories, overrides any specified
609 # user explicit permissions for repositories, overrides any specified
610 # by the group permission
610 # by the group permission
611 user_repo_perms = Permission.get_default_repo_perms(
611 user_repo_perms = Permission.get_default_repo_perms(
612 self.user_id, self.scope_repo_id)
612 self.user_id, self.scope_repo_id)
613 for perm in user_repo_perms:
613 for perm in user_repo_perms:
614 r_k = perm.UserRepoToPerm.repository.repo_name
614 r_k = perm.UserRepoToPerm.repository.repo_name
615 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
615 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
616 # set admin if owner
616 # set admin if owner
617 if perm.Repository.user_id == self.user_id:
617 if perm.Repository.user_id == self.user_id:
618 p = 'repository.admin'
618 p = 'repository.admin'
619 o = PermOrigin.REPO_OWNER
619 o = PermOrigin.REPO_OWNER
620 else:
620 else:
621 p = perm.Permission.permission_name
621 p = perm.Permission.permission_name
622 if not self.explicit:
622 if not self.explicit:
623 cur_perm = self.permissions_repositories.get(
623 cur_perm = self.permissions_repositories.get(
624 r_k, 'repository.none')
624 r_k, 'repository.none')
625 p = self._choose_permission(p, cur_perm)
625 p = self._choose_permission(p, cur_perm)
626 self.permissions_repositories[r_k] = p, o
626 self.permissions_repositories[r_k] = p, o
627
627
628 def _calculate_repository_group_permissions(self):
628 def _calculate_repository_group_permissions(self):
629 """
629 """
630 Repository group permissions for the current user.
630 Repository group permissions for the current user.
631
631
632 Check if the user is part of user groups for repository groups and
632 Check if the user is part of user groups for repository groups and
633 fill in the permissions from it. `_choose_permmission` decides of which
633 fill in the permissions from it. `_choose_permmission` decides of which
634 permission should be selected based on selected method.
634 permission should be selected based on selected method.
635 """
635 """
636 # user group for repo groups permissions
636 # user group for repo groups permissions
637 user_repo_group_perms_from_user_group = Permission\
637 user_repo_group_perms_from_user_group = Permission\
638 .get_default_group_perms_from_user_group(
638 .get_default_group_perms_from_user_group(
639 self.user_id, self.scope_repo_group_id)
639 self.user_id, self.scope_repo_group_id)
640
640
641 multiple_counter = collections.defaultdict(int)
641 multiple_counter = collections.defaultdict(int)
642 for perm in user_repo_group_perms_from_user_group:
642 for perm in user_repo_group_perms_from_user_group:
643 g_k = perm.UserGroupRepoGroupToPerm.group.group_name
643 g_k = perm.UserGroupRepoGroupToPerm.group.group_name
644 ug_k = perm.UserGroupRepoGroupToPerm.users_group.users_group_name
644 ug_k = perm.UserGroupRepoGroupToPerm.users_group.users_group_name
645 o = PermOrigin.REPOGROUP_USERGROUP % ug_k
645 o = PermOrigin.REPOGROUP_USERGROUP % ug_k
646 multiple_counter[g_k] += 1
646 multiple_counter[g_k] += 1
647 p = perm.Permission.permission_name
647 p = perm.Permission.permission_name
648 if perm.RepoGroup.user_id == self.user_id:
648 if perm.RepoGroup.user_id == self.user_id:
649 # set admin if owner
649 # set admin if owner
650 p = 'group.admin'
650 p = 'group.admin'
651 o = PermOrigin.REPOGROUP_OWNER
651 o = PermOrigin.REPOGROUP_OWNER
652 else:
652 else:
653 if multiple_counter[g_k] > 1:
653 if multiple_counter[g_k] > 1:
654 cur_perm = self.permissions_repository_groups[g_k]
654 cur_perm = self.permissions_repository_groups[g_k]
655 p = self._choose_permission(p, cur_perm)
655 p = self._choose_permission(p, cur_perm)
656 self.permissions_repository_groups[g_k] = p, o
656 self.permissions_repository_groups[g_k] = p, o
657
657
658 # user explicit permissions for repository groups
658 # user explicit permissions for repository groups
659 user_repo_groups_perms = Permission.get_default_group_perms(
659 user_repo_groups_perms = Permission.get_default_group_perms(
660 self.user_id, self.scope_repo_group_id)
660 self.user_id, self.scope_repo_group_id)
661 for perm in user_repo_groups_perms:
661 for perm in user_repo_groups_perms:
662 rg_k = perm.UserRepoGroupToPerm.group.group_name
662 rg_k = perm.UserRepoGroupToPerm.group.group_name
663 u_k = perm.UserRepoGroupToPerm.user.username
663 u_k = perm.UserRepoGroupToPerm.user.username
664 o = PermOrigin.REPOGROUP_USER % u_k
664 o = PermOrigin.REPOGROUP_USER % u_k
665
665
666 if perm.RepoGroup.user_id == self.user_id:
666 if perm.RepoGroup.user_id == self.user_id:
667 # set admin if owner
667 # set admin if owner
668 p = 'group.admin'
668 p = 'group.admin'
669 o = PermOrigin.REPOGROUP_OWNER
669 o = PermOrigin.REPOGROUP_OWNER
670 else:
670 else:
671 p = perm.Permission.permission_name
671 p = perm.Permission.permission_name
672 if not self.explicit:
672 if not self.explicit:
673 cur_perm = self.permissions_repository_groups.get(
673 cur_perm = self.permissions_repository_groups.get(
674 rg_k, 'group.none')
674 rg_k, 'group.none')
675 p = self._choose_permission(p, cur_perm)
675 p = self._choose_permission(p, cur_perm)
676 self.permissions_repository_groups[rg_k] = p, o
676 self.permissions_repository_groups[rg_k] = p, o
677
677
678 def _calculate_user_group_permissions(self):
678 def _calculate_user_group_permissions(self):
679 """
679 """
680 User group permissions for the current user.
680 User group permissions for the current user.
681 """
681 """
682 # user group for user group permissions
682 # user group for user group permissions
683 user_group_from_user_group = Permission\
683 user_group_from_user_group = Permission\
684 .get_default_user_group_perms_from_user_group(
684 .get_default_user_group_perms_from_user_group(
685 self.user_id, self.scope_repo_group_id)
685 self.user_id, self.scope_repo_group_id)
686
686
687 multiple_counter = collections.defaultdict(int)
687 multiple_counter = collections.defaultdict(int)
688 for perm in user_group_from_user_group:
688 for perm in user_group_from_user_group:
689 g_k = perm.UserGroupUserGroupToPerm\
689 g_k = perm.UserGroupUserGroupToPerm\
690 .target_user_group.users_group_name
690 .target_user_group.users_group_name
691 u_k = perm.UserGroupUserGroupToPerm\
691 u_k = perm.UserGroupUserGroupToPerm\
692 .user_group.users_group_name
692 .user_group.users_group_name
693 o = PermOrigin.USERGROUP_USERGROUP % u_k
693 o = PermOrigin.USERGROUP_USERGROUP % u_k
694 multiple_counter[g_k] += 1
694 multiple_counter[g_k] += 1
695 p = perm.Permission.permission_name
695 p = perm.Permission.permission_name
696 if multiple_counter[g_k] > 1:
696 if multiple_counter[g_k] > 1:
697 cur_perm = self.permissions_user_groups[g_k]
697 cur_perm = self.permissions_user_groups[g_k]
698 p = self._choose_permission(p, cur_perm)
698 p = self._choose_permission(p, cur_perm)
699 self.permissions_user_groups[g_k] = p, o
699 self.permissions_user_groups[g_k] = p, o
700
700
701 # user explicit permission for user groups
701 # user explicit permission for user groups
702 user_user_groups_perms = Permission.get_default_user_group_perms(
702 user_user_groups_perms = Permission.get_default_user_group_perms(
703 self.user_id, self.scope_user_group_id)
703 self.user_id, self.scope_user_group_id)
704 for perm in user_user_groups_perms:
704 for perm in user_user_groups_perms:
705 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
705 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
706 u_k = perm.UserUserGroupToPerm.user.username
706 u_k = perm.UserUserGroupToPerm.user.username
707 p = perm.Permission.permission_name
707 p = perm.Permission.permission_name
708 o = PermOrigin.USERGROUP_USER % u_k
708 o = PermOrigin.USERGROUP_USER % u_k
709 if not self.explicit:
709 if not self.explicit:
710 cur_perm = self.permissions_user_groups.get(
710 cur_perm = self.permissions_user_groups.get(
711 ug_k, 'usergroup.none')
711 ug_k, 'usergroup.none')
712 p = self._choose_permission(p, cur_perm)
712 p = self._choose_permission(p, cur_perm)
713 self.permissions_user_groups[ug_k] = p, o
713 self.permissions_user_groups[ug_k] = p, o
714
714
715 def _choose_permission(self, new_perm, cur_perm):
715 def _choose_permission(self, new_perm, cur_perm):
716 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
716 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
717 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
717 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
718 if self.algo == 'higherwin':
718 if self.algo == 'higherwin':
719 if new_perm_val > cur_perm_val:
719 if new_perm_val > cur_perm_val:
720 return new_perm
720 return new_perm
721 return cur_perm
721 return cur_perm
722 elif self.algo == 'lowerwin':
722 elif self.algo == 'lowerwin':
723 if new_perm_val < cur_perm_val:
723 if new_perm_val < cur_perm_val:
724 return new_perm
724 return new_perm
725 return cur_perm
725 return cur_perm
726
726
727 def _permission_structure(self):
727 def _permission_structure(self):
728 return {
728 return {
729 'global': self.permissions_global,
729 'global': self.permissions_global,
730 'repositories': self.permissions_repositories,
730 'repositories': self.permissions_repositories,
731 'repositories_groups': self.permissions_repository_groups,
731 'repositories_groups': self.permissions_repository_groups,
732 'user_groups': self.permissions_user_groups,
732 'user_groups': self.permissions_user_groups,
733 }
733 }
734
734
735
735
736 def allowed_auth_token_access(controller_name, whitelist=None, auth_token=None):
736 def allowed_auth_token_access(controller_name, whitelist=None, auth_token=None):
737 """
737 """
738 Check if given controller_name is in whitelist of auth token access
738 Check if given controller_name is in whitelist of auth token access
739 """
739 """
740 if not whitelist:
740 if not whitelist:
741 from rhodecode import CONFIG
741 from rhodecode import CONFIG
742 whitelist = aslist(
742 whitelist = aslist(
743 CONFIG.get('api_access_controllers_whitelist'), sep=',')
743 CONFIG.get('api_access_controllers_whitelist'), sep=',')
744 log.debug(
744 log.debug(
745 'Allowed controllers for AUTH TOKEN access: %s' % (whitelist,))
745 'Allowed controllers for AUTH TOKEN access: %s' % (whitelist,))
746
746
747 auth_token_access_valid = False
747 auth_token_access_valid = False
748 for entry in whitelist:
748 for entry in whitelist:
749 if fnmatch.fnmatch(controller_name, entry):
749 if fnmatch.fnmatch(controller_name, entry):
750 auth_token_access_valid = True
750 auth_token_access_valid = True
751 break
751 break
752
752
753 if auth_token_access_valid:
753 if auth_token_access_valid:
754 log.debug('controller:%s matches entry in whitelist'
754 log.debug('controller:%s matches entry in whitelist'
755 % (controller_name,))
755 % (controller_name,))
756 else:
756 else:
757 msg = ('controller: %s does *NOT* match any entry in whitelist'
757 msg = ('controller: %s does *NOT* match any entry in whitelist'
758 % (controller_name,))
758 % (controller_name,))
759 if auth_token:
759 if auth_token:
760 # if we use auth token key and don't have access it's a warning
760 # if we use auth token key and don't have access it's a warning
761 log.warning(msg)
761 log.warning(msg)
762 else:
762 else:
763 log.debug(msg)
763 log.debug(msg)
764
764
765 return auth_token_access_valid
765 return auth_token_access_valid
766
766
767
767
768 class AuthUser(object):
768 class AuthUser(object):
769 """
769 """
770 A simple object that handles all attributes of user in RhodeCode
770 A simple object that handles all attributes of user in RhodeCode
771
771
772 It does lookup based on API key,given user, or user present in session
772 It does lookup based on API key,given user, or user present in session
773 Then it fills all required information for such user. It also checks if
773 Then it fills all required information for such user. It also checks if
774 anonymous access is enabled and if so, it returns default user as logged in
774 anonymous access is enabled and if so, it returns default user as logged in
775 """
775 """
776 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
776 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
777
777
778 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
778 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
779
779
780 self.user_id = user_id
780 self.user_id = user_id
781 self._api_key = api_key
781 self._api_key = api_key
782
782
783 self.api_key = None
783 self.api_key = None
784 self.feed_token = ''
784 self.feed_token = ''
785 self.username = username
785 self.username = username
786 self.ip_addr = ip_addr
786 self.ip_addr = ip_addr
787 self.name = ''
787 self.name = ''
788 self.lastname = ''
788 self.lastname = ''
789 self.email = ''
789 self.email = ''
790 self.is_authenticated = False
790 self.is_authenticated = False
791 self.admin = False
791 self.admin = False
792 self.inherit_default_permissions = False
792 self.inherit_default_permissions = False
793 self.password = ''
793 self.password = ''
794
794
795 self.anonymous_user = None # propagated on propagate_data
795 self.anonymous_user = None # propagated on propagate_data
796 self.propagate_data()
796 self.propagate_data()
797 self._instance = None
797 self._instance = None
798 self._permissions_scoped_cache = {} # used to bind scoped calculation
798 self._permissions_scoped_cache = {} # used to bind scoped calculation
799
799
800 @LazyProperty
800 @LazyProperty
801 def permissions(self):
801 def permissions(self):
802 return self.get_perms(user=self, cache=False)
802 return self.get_perms(user=self, cache=False)
803
803
804 def permissions_with_scope(self, scope):
804 def permissions_with_scope(self, scope):
805 """
805 """
806 Call the get_perms function with scoped data. The scope in that function
806 Call the get_perms function with scoped data. The scope in that function
807 narrows the SQL calls to the given ID of objects resulting in fetching
807 narrows the SQL calls to the given ID of objects resulting in fetching
808 Just particular permission we want to obtain. If scope is an empty dict
808 Just particular permission we want to obtain. If scope is an empty dict
809 then it basically narrows the scope to GLOBAL permissions only.
809 then it basically narrows the scope to GLOBAL permissions only.
810
810
811 :param scope: dict
811 :param scope: dict
812 """
812 """
813 if 'repo_name' in scope:
813 if 'repo_name' in scope:
814 obj = Repository.get_by_repo_name(scope['repo_name'])
814 obj = Repository.get_by_repo_name(scope['repo_name'])
815 if obj:
815 if obj:
816 scope['repo_id'] = obj.repo_id
816 scope['repo_id'] = obj.repo_id
817 _scope = {
817 _scope = {
818 'repo_id': -1,
818 'repo_id': -1,
819 'user_group_id': -1,
819 'user_group_id': -1,
820 'repo_group_id': -1,
820 'repo_group_id': -1,
821 }
821 }
822 _scope.update(scope)
822 _scope.update(scope)
823 cache_key = "_".join(map(safe_str, reduce(lambda a, b: a+b,
823 cache_key = "_".join(map(safe_str, reduce(lambda a, b: a+b,
824 _scope.items())))
824 _scope.items())))
825 if cache_key not in self._permissions_scoped_cache:
825 if cache_key not in self._permissions_scoped_cache:
826 # store in cache to mimic how the @LazyProperty works,
826 # store in cache to mimic how the @LazyProperty works,
827 # the difference here is that we use the unique key calculated
827 # the difference here is that we use the unique key calculated
828 # from params and values
828 # from params and values
829 res = self.get_perms(user=self, cache=False, scope=_scope)
829 res = self.get_perms(user=self, cache=False, scope=_scope)
830 self._permissions_scoped_cache[cache_key] = res
830 self._permissions_scoped_cache[cache_key] = res
831 return self._permissions_scoped_cache[cache_key]
831 return self._permissions_scoped_cache[cache_key]
832
832
833 @property
833 @property
834 def auth_tokens(self):
834 def auth_tokens(self):
835 return self.get_auth_tokens()
835 return self.get_auth_tokens()
836
836
837 def get_instance(self):
837 def get_instance(self):
838 return User.get(self.user_id)
838 return User.get(self.user_id)
839
839
840 def update_lastactivity(self):
840 def update_lastactivity(self):
841 if self.user_id:
841 if self.user_id:
842 User.get(self.user_id).update_lastactivity()
842 User.get(self.user_id).update_lastactivity()
843
843
844 def propagate_data(self):
844 def propagate_data(self):
845 """
845 """
846 Fills in user data and propagates values to this instance. Maps fetched
846 Fills in user data and propagates values to this instance. Maps fetched
847 user attributes to this class instance attributes
847 user attributes to this class instance attributes
848 """
848 """
849
849 log.debug('starting data propagation for new potential AuthUser')
850 user_model = UserModel()
850 user_model = UserModel()
851 anon_user = self.anonymous_user = User.get_default_user(cache=True)
851 anon_user = self.anonymous_user = User.get_default_user(cache=True)
852 is_user_loaded = False
852 is_user_loaded = False
853
853
854 # lookup by userid
854 # lookup by userid
855 if self.user_id is not None and self.user_id != anon_user.user_id:
855 if self.user_id is not None and self.user_id != anon_user.user_id:
856 log.debug('Trying Auth User lookup by USER ID: `%s`' % self.user_id)
856 log.debug('Trying Auth User lookup by USER ID: `%s`' % self.user_id)
857 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
857 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
858
858
859 # try go get user by api key
859 # try go get user by api key
860 elif self._api_key and self._api_key != anon_user.api_key:
860 elif self._api_key and self._api_key != anon_user.api_key:
861 log.debug('Trying Auth User lookup by API KEY: `%s`' % self._api_key)
861 log.debug('Trying Auth User lookup by API KEY: `%s`' % self._api_key)
862 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
862 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
863
863
864 # lookup by username
864 # lookup by username
865 elif self.username:
865 elif self.username:
866 log.debug('Trying Auth User lookup by USER NAME: `%s`' % self.username)
866 log.debug('Trying Auth User lookup by USER NAME: `%s`' % self.username)
867 is_user_loaded = user_model.fill_data(self, username=self.username)
867 is_user_loaded = user_model.fill_data(self, username=self.username)
868 else:
868 else:
869 log.debug('No data in %s that could been used to log in' % self)
869 log.debug('No data in %s that could been used to log in' % self)
870
870
871 if not is_user_loaded:
871 if not is_user_loaded:
872 log.debug('Failed to load user. Fallback to default user')
872 log.debug('Failed to load user. Fallback to default user')
873 # if we cannot authenticate user try anonymous
873 # if we cannot authenticate user try anonymous
874 if anon_user.active:
874 if anon_user.active:
875 user_model.fill_data(self, user_id=anon_user.user_id)
875 user_model.fill_data(self, user_id=anon_user.user_id)
876 # then we set this user is logged in
876 # then we set this user is logged in
877 self.is_authenticated = True
877 self.is_authenticated = True
878 else:
878 else:
879 # in case of disabled anonymous user we reset some of the
879 # in case of disabled anonymous user we reset some of the
880 # parameters so such user is "corrupted", skipping the fill_data
880 # parameters so such user is "corrupted", skipping the fill_data
881 for attr in ['user_id', 'username', 'admin', 'active']:
881 for attr in ['user_id', 'username', 'admin', 'active']:
882 setattr(self, attr, None)
882 setattr(self, attr, None)
883 self.is_authenticated = False
883 self.is_authenticated = False
884
884
885 if not self.username:
885 if not self.username:
886 self.username = 'None'
886 self.username = 'None'
887
887
888 log.debug('Auth User is now %s' % self)
888 log.debug('Auth User is now %s' % self)
889
889
890 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
890 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
891 cache=False):
891 cache=False):
892 """
892 """
893 Fills user permission attribute with permissions taken from database
893 Fills user permission attribute with permissions taken from database
894 works for permissions given for repositories, and for permissions that
894 works for permissions given for repositories, and for permissions that
895 are granted to groups
895 are granted to groups
896
896
897 :param user: instance of User object from database
897 :param user: instance of User object from database
898 :param explicit: In case there are permissions both for user and a group
898 :param explicit: In case there are permissions both for user and a group
899 that user is part of, explicit flag will defiine if user will
899 that user is part of, explicit flag will defiine if user will
900 explicitly override permissions from group, if it's False it will
900 explicitly override permissions from group, if it's False it will
901 make decision based on the algo
901 make decision based on the algo
902 :param algo: algorithm to decide what permission should be choose if
902 :param algo: algorithm to decide what permission should be choose if
903 it's multiple defined, eg user in two different groups. It also
903 it's multiple defined, eg user in two different groups. It also
904 decides if explicit flag is turned off how to specify the permission
904 decides if explicit flag is turned off how to specify the permission
905 for case when user is in a group + have defined separate permission
905 for case when user is in a group + have defined separate permission
906 """
906 """
907 user_id = user.user_id
907 user_id = user.user_id
908 user_is_admin = user.is_admin
908 user_is_admin = user.is_admin
909
909
910 # inheritance of global permissions like create repo/fork repo etc
910 # inheritance of global permissions like create repo/fork repo etc
911 user_inherit_default_permissions = user.inherit_default_permissions
911 user_inherit_default_permissions = user.inherit_default_permissions
912
912
913 log.debug('Computing PERMISSION tree for scope %s' % (scope, ))
913 log.debug('Computing PERMISSION tree for scope %s' % (scope, ))
914 compute = caches.conditional_cache(
914 compute = caches.conditional_cache(
915 'short_term', 'cache_desc',
915 'short_term', 'cache_desc',
916 condition=cache, func=_cached_perms_data)
916 condition=cache, func=_cached_perms_data)
917 result = compute(user_id, scope, user_is_admin,
917 result = compute(user_id, scope, user_is_admin,
918 user_inherit_default_permissions, explicit, algo)
918 user_inherit_default_permissions, explicit, algo)
919
919
920 result_repr = []
920 result_repr = []
921 for k in result:
921 for k in result:
922 result_repr.append((k, len(result[k])))
922 result_repr.append((k, len(result[k])))
923
923
924 log.debug('PERMISSION tree computed %s' % (result_repr,))
924 log.debug('PERMISSION tree computed %s' % (result_repr,))
925 return result
925 return result
926
926
927 def get_auth_tokens(self):
927 def get_auth_tokens(self):
928 auth_tokens = [self.api_key]
928 auth_tokens = [self.api_key]
929 for api_key in UserApiKeys.query()\
929 for api_key in UserApiKeys.query()\
930 .filter(UserApiKeys.user_id == self.user_id)\
930 .filter(UserApiKeys.user_id == self.user_id)\
931 .filter(or_(UserApiKeys.expires == -1,
931 .filter(or_(UserApiKeys.expires == -1,
932 UserApiKeys.expires >= time.time())).all():
932 UserApiKeys.expires >= time.time())).all():
933 auth_tokens.append(api_key.api_key)
933 auth_tokens.append(api_key.api_key)
934
934
935 return auth_tokens
935 return auth_tokens
936
936
937 @property
937 @property
938 def is_default(self):
938 def is_default(self):
939 return self.username == User.DEFAULT_USER
939 return self.username == User.DEFAULT_USER
940
940
941 @property
941 @property
942 def is_admin(self):
942 def is_admin(self):
943 return self.admin
943 return self.admin
944
944
945 @property
945 @property
946 def is_user_object(self):
946 def is_user_object(self):
947 return self.user_id is not None
947 return self.user_id is not None
948
948
949 @property
949 @property
950 def repositories_admin(self):
950 def repositories_admin(self):
951 """
951 """
952 Returns list of repositories you're an admin of
952 Returns list of repositories you're an admin of
953 """
953 """
954 return [x[0] for x in self.permissions['repositories'].iteritems()
954 return [x[0] for x in self.permissions['repositories'].iteritems()
955 if x[1] == 'repository.admin']
955 if x[1] == 'repository.admin']
956
956
957 @property
957 @property
958 def repository_groups_admin(self):
958 def repository_groups_admin(self):
959 """
959 """
960 Returns list of repository groups you're an admin of
960 Returns list of repository groups you're an admin of
961 """
961 """
962 return [x[0]
962 return [x[0]
963 for x in self.permissions['repositories_groups'].iteritems()
963 for x in self.permissions['repositories_groups'].iteritems()
964 if x[1] == 'group.admin']
964 if x[1] == 'group.admin']
965
965
966 @property
966 @property
967 def user_groups_admin(self):
967 def user_groups_admin(self):
968 """
968 """
969 Returns list of user groups you're an admin of
969 Returns list of user groups you're an admin of
970 """
970 """
971 return [x[0] for x in self.permissions['user_groups'].iteritems()
971 return [x[0] for x in self.permissions['user_groups'].iteritems()
972 if x[1] == 'usergroup.admin']
972 if x[1] == 'usergroup.admin']
973
973
974 @property
974 @property
975 def ip_allowed(self):
975 def ip_allowed(self):
976 """
976 """
977 Checks if ip_addr used in constructor is allowed from defined list of
977 Checks if ip_addr used in constructor is allowed from defined list of
978 allowed ip_addresses for user
978 allowed ip_addresses for user
979
979
980 :returns: boolean, True if ip is in allowed ip range
980 :returns: boolean, True if ip is in allowed ip range
981 """
981 """
982 # check IP
982 # check IP
983 inherit = self.inherit_default_permissions
983 inherit = self.inherit_default_permissions
984 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
984 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
985 inherit_from_default=inherit)
985 inherit_from_default=inherit)
986 @property
986 @property
987 def personal_repo_group(self):
987 def personal_repo_group(self):
988 return RepoGroup.get_user_personal_repo_group(self.user_id)
988 return RepoGroup.get_user_personal_repo_group(self.user_id)
989
989
990 @classmethod
990 @classmethod
991 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
991 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
992 allowed_ips = AuthUser.get_allowed_ips(
992 allowed_ips = AuthUser.get_allowed_ips(
993 user_id, cache=True, inherit_from_default=inherit_from_default)
993 user_id, cache=True, inherit_from_default=inherit_from_default)
994 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
994 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
995 log.debug('IP:%s is in range of %s' % (ip_addr, allowed_ips))
995 log.debug('IP:%s is in range of %s' % (ip_addr, allowed_ips))
996 return True
996 return True
997 else:
997 else:
998 log.info('Access for IP:%s forbidden, '
998 log.info('Access for IP:%s forbidden, '
999 'not in %s' % (ip_addr, allowed_ips))
999 'not in %s' % (ip_addr, allowed_ips))
1000 return False
1000 return False
1001
1001
1002 def __repr__(self):
1002 def __repr__(self):
1003 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1003 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1004 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1004 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1005
1005
1006 def set_authenticated(self, authenticated=True):
1006 def set_authenticated(self, authenticated=True):
1007 if self.user_id != self.anonymous_user.user_id:
1007 if self.user_id != self.anonymous_user.user_id:
1008 self.is_authenticated = authenticated
1008 self.is_authenticated = authenticated
1009
1009
1010 def get_cookie_store(self):
1010 def get_cookie_store(self):
1011 return {
1011 return {
1012 'username': self.username,
1012 'username': self.username,
1013 'password': md5(self.password),
1013 'password': md5(self.password),
1014 'user_id': self.user_id,
1014 'user_id': self.user_id,
1015 'is_authenticated': self.is_authenticated
1015 'is_authenticated': self.is_authenticated
1016 }
1016 }
1017
1017
1018 @classmethod
1018 @classmethod
1019 def from_cookie_store(cls, cookie_store):
1019 def from_cookie_store(cls, cookie_store):
1020 """
1020 """
1021 Creates AuthUser from a cookie store
1021 Creates AuthUser from a cookie store
1022
1022
1023 :param cls:
1023 :param cls:
1024 :param cookie_store:
1024 :param cookie_store:
1025 """
1025 """
1026 user_id = cookie_store.get('user_id')
1026 user_id = cookie_store.get('user_id')
1027 username = cookie_store.get('username')
1027 username = cookie_store.get('username')
1028 api_key = cookie_store.get('api_key')
1028 api_key = cookie_store.get('api_key')
1029 return AuthUser(user_id, api_key, username)
1029 return AuthUser(user_id, api_key, username)
1030
1030
1031 @classmethod
1031 @classmethod
1032 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1032 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1033 _set = set()
1033 _set = set()
1034
1034
1035 if inherit_from_default:
1035 if inherit_from_default:
1036 default_ips = UserIpMap.query().filter(
1036 default_ips = UserIpMap.query().filter(
1037 UserIpMap.user == User.get_default_user(cache=True))
1037 UserIpMap.user == User.get_default_user(cache=True))
1038 if cache:
1038 if cache:
1039 default_ips = default_ips.options(FromCache("sql_cache_short",
1039 default_ips = default_ips.options(FromCache("sql_cache_short",
1040 "get_user_ips_default"))
1040 "get_user_ips_default"))
1041
1041
1042 # populate from default user
1042 # populate from default user
1043 for ip in default_ips:
1043 for ip in default_ips:
1044 try:
1044 try:
1045 _set.add(ip.ip_addr)
1045 _set.add(ip.ip_addr)
1046 except ObjectDeletedError:
1046 except ObjectDeletedError:
1047 # since we use heavy caching sometimes it happens that
1047 # since we use heavy caching sometimes it happens that
1048 # we get deleted objects here, we just skip them
1048 # we get deleted objects here, we just skip them
1049 pass
1049 pass
1050
1050
1051 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1051 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1052 if cache:
1052 if cache:
1053 user_ips = user_ips.options(FromCache("sql_cache_short",
1053 user_ips = user_ips.options(FromCache("sql_cache_short",
1054 "get_user_ips_%s" % user_id))
1054 "get_user_ips_%s" % user_id))
1055
1055
1056 for ip in user_ips:
1056 for ip in user_ips:
1057 try:
1057 try:
1058 _set.add(ip.ip_addr)
1058 _set.add(ip.ip_addr)
1059 except ObjectDeletedError:
1059 except ObjectDeletedError:
1060 # since we use heavy caching sometimes it happens that we get
1060 # since we use heavy caching sometimes it happens that we get
1061 # deleted objects here, we just skip them
1061 # deleted objects here, we just skip them
1062 pass
1062 pass
1063 return _set or set(['0.0.0.0/0', '::/0'])
1063 return _set or set(['0.0.0.0/0', '::/0'])
1064
1064
1065
1065
1066 def set_available_permissions(config):
1066 def set_available_permissions(config):
1067 """
1067 """
1068 This function will propagate pylons globals with all available defined
1068 This function will propagate pylons globals with all available defined
1069 permission given in db. We don't want to check each time from db for new
1069 permission given in db. We don't want to check each time from db for new
1070 permissions since adding a new permission also requires application restart
1070 permissions since adding a new permission also requires application restart
1071 ie. to decorate new views with the newly created permission
1071 ie. to decorate new views with the newly created permission
1072
1072
1073 :param config: current pylons config instance
1073 :param config: current pylons config instance
1074
1074
1075 """
1075 """
1076 log.info('getting information about all available permissions')
1076 log.info('getting information about all available permissions')
1077 try:
1077 try:
1078 sa = meta.Session
1078 sa = meta.Session
1079 all_perms = sa.query(Permission).all()
1079 all_perms = sa.query(Permission).all()
1080 config['available_permissions'] = [x.permission_name for x in all_perms]
1080 config['available_permissions'] = [x.permission_name for x in all_perms]
1081 except Exception:
1081 except Exception:
1082 log.error(traceback.format_exc())
1082 log.error(traceback.format_exc())
1083 finally:
1083 finally:
1084 meta.Session.remove()
1084 meta.Session.remove()
1085
1085
1086
1086
1087 def get_csrf_token(session=None, force_new=False, save_if_missing=True):
1087 def get_csrf_token(session=None, force_new=False, save_if_missing=True):
1088 """
1088 """
1089 Return the current authentication token, creating one if one doesn't
1089 Return the current authentication token, creating one if one doesn't
1090 already exist and the save_if_missing flag is present.
1090 already exist and the save_if_missing flag is present.
1091
1091
1092 :param session: pass in the pylons session, else we use the global ones
1092 :param session: pass in the pylons session, else we use the global ones
1093 :param force_new: force to re-generate the token and store it in session
1093 :param force_new: force to re-generate the token and store it in session
1094 :param save_if_missing: save the newly generated token if it's missing in
1094 :param save_if_missing: save the newly generated token if it's missing in
1095 session
1095 session
1096 """
1096 """
1097 if not session:
1097 if not session:
1098 from pylons import session
1098 from pylons import session
1099
1099
1100 if (csrf_token_key not in session and save_if_missing) or force_new:
1100 if (csrf_token_key not in session and save_if_missing) or force_new:
1101 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1101 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1102 session[csrf_token_key] = token
1102 session[csrf_token_key] = token
1103 if hasattr(session, 'save'):
1103 if hasattr(session, 'save'):
1104 session.save()
1104 session.save()
1105 return session.get(csrf_token_key)
1105 return session.get(csrf_token_key)
1106
1106
1107
1107
1108 # CHECK DECORATORS
1108 # CHECK DECORATORS
1109 class CSRFRequired(object):
1109 class CSRFRequired(object):
1110 """
1110 """
1111 Decorator for authenticating a form
1111 Decorator for authenticating a form
1112
1112
1113 This decorator uses an authorization token stored in the client's
1113 This decorator uses an authorization token stored in the client's
1114 session for prevention of certain Cross-site request forgery (CSRF)
1114 session for prevention of certain Cross-site request forgery (CSRF)
1115 attacks (See
1115 attacks (See
1116 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1116 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1117 information).
1117 information).
1118
1118
1119 For use with the ``webhelpers.secure_form`` helper functions.
1119 For use with the ``webhelpers.secure_form`` helper functions.
1120
1120
1121 """
1121 """
1122 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1122 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1123 except_methods=None):
1123 except_methods=None):
1124 self.token = token
1124 self.token = token
1125 self.header = header
1125 self.header = header
1126 self.except_methods = except_methods or []
1126 self.except_methods = except_methods or []
1127
1127
1128 def __call__(self, func):
1128 def __call__(self, func):
1129 return get_cython_compat_decorator(self.__wrapper, func)
1129 return get_cython_compat_decorator(self.__wrapper, func)
1130
1130
1131 def _get_csrf(self, _request):
1131 def _get_csrf(self, _request):
1132 return _request.POST.get(self.token, _request.headers.get(self.header))
1132 return _request.POST.get(self.token, _request.headers.get(self.header))
1133
1133
1134 def check_csrf(self, _request, cur_token):
1134 def check_csrf(self, _request, cur_token):
1135 supplied_token = self._get_csrf(_request)
1135 supplied_token = self._get_csrf(_request)
1136 return supplied_token and supplied_token == cur_token
1136 return supplied_token and supplied_token == cur_token
1137
1137
1138 def __wrapper(self, func, *fargs, **fkwargs):
1138 def __wrapper(self, func, *fargs, **fkwargs):
1139 if request.method in self.except_methods:
1139 if request.method in self.except_methods:
1140 return func(*fargs, **fkwargs)
1140 return func(*fargs, **fkwargs)
1141
1141
1142 cur_token = get_csrf_token(save_if_missing=False)
1142 cur_token = get_csrf_token(save_if_missing=False)
1143 if self.check_csrf(request, cur_token):
1143 if self.check_csrf(request, cur_token):
1144 if request.POST.get(self.token):
1144 if request.POST.get(self.token):
1145 del request.POST[self.token]
1145 del request.POST[self.token]
1146 return func(*fargs, **fkwargs)
1146 return func(*fargs, **fkwargs)
1147 else:
1147 else:
1148 reason = 'token-missing'
1148 reason = 'token-missing'
1149 supplied_token = self._get_csrf(request)
1149 supplied_token = self._get_csrf(request)
1150 if supplied_token and cur_token != supplied_token:
1150 if supplied_token and cur_token != supplied_token:
1151 reason = 'token-mismatch [%s:%s]' % (cur_token or ''[:6],
1151 reason = 'token-mismatch [%s:%s]' % (cur_token or ''[:6],
1152 supplied_token or ''[:6])
1152 supplied_token or ''[:6])
1153
1153
1154 csrf_message = \
1154 csrf_message = \
1155 ("Cross-site request forgery detected, request denied. See "
1155 ("Cross-site request forgery detected, request denied. See "
1156 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1156 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1157 "more information.")
1157 "more information.")
1158 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1158 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1159 'REMOTE_ADDR:%s, HEADERS:%s' % (
1159 'REMOTE_ADDR:%s, HEADERS:%s' % (
1160 request, reason, request.remote_addr, request.headers))
1160 request, reason, request.remote_addr, request.headers))
1161
1161
1162 abort(403, detail=csrf_message)
1162 abort(403, detail=csrf_message)
1163
1163
1164
1164
1165 class LoginRequired(object):
1165 class LoginRequired(object):
1166 """
1166 """
1167 Must be logged in to execute this function else
1167 Must be logged in to execute this function else
1168 redirect to login page
1168 redirect to login page
1169
1169
1170 :param api_access: if enabled this checks only for valid auth token
1170 :param api_access: if enabled this checks only for valid auth token
1171 and grants access based on valid token
1171 and grants access based on valid token
1172 """
1172 """
1173 def __init__(self, auth_token_access=False):
1173 def __init__(self, auth_token_access=False):
1174 self.auth_token_access = auth_token_access
1174 self.auth_token_access = auth_token_access
1175
1175
1176 def __call__(self, func):
1176 def __call__(self, func):
1177 return get_cython_compat_decorator(self.__wrapper, func)
1177 return get_cython_compat_decorator(self.__wrapper, func)
1178
1178
1179 def __wrapper(self, func, *fargs, **fkwargs):
1179 def __wrapper(self, func, *fargs, **fkwargs):
1180 from rhodecode.lib import helpers as h
1180 from rhodecode.lib import helpers as h
1181 cls = fargs[0]
1181 cls = fargs[0]
1182 user = cls._rhodecode_user
1182 user = cls._rhodecode_user
1183 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1183 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1184 log.debug('Starting login restriction checks for user: %s' % (user,))
1184 log.debug('Starting login restriction checks for user: %s' % (user,))
1185 # check if our IP is allowed
1185 # check if our IP is allowed
1186 ip_access_valid = True
1186 ip_access_valid = True
1187 if not user.ip_allowed:
1187 if not user.ip_allowed:
1188 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1188 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1189 category='warning')
1189 category='warning')
1190 ip_access_valid = False
1190 ip_access_valid = False
1191
1191
1192 # check if we used an APIKEY and it's a valid one
1192 # check if we used an APIKEY and it's a valid one
1193 # defined whitelist of controllers which API access will be enabled
1193 # defined whitelist of controllers which API access will be enabled
1194 _auth_token = request.GET.get(
1194 _auth_token = request.GET.get(
1195 'auth_token', '') or request.GET.get('api_key', '')
1195 'auth_token', '') or request.GET.get('api_key', '')
1196 auth_token_access_valid = allowed_auth_token_access(
1196 auth_token_access_valid = allowed_auth_token_access(
1197 loc, auth_token=_auth_token)
1197 loc, auth_token=_auth_token)
1198
1198
1199 # explicit controller is enabled or API is in our whitelist
1199 # explicit controller is enabled or API is in our whitelist
1200 if self.auth_token_access or auth_token_access_valid:
1200 if self.auth_token_access or auth_token_access_valid:
1201 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1201 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1202
1202
1203 if _auth_token and _auth_token in user.auth_tokens:
1203 if _auth_token and _auth_token in user.auth_tokens:
1204 auth_token_access_valid = True
1204 auth_token_access_valid = True
1205 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1205 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1206 else:
1206 else:
1207 auth_token_access_valid = False
1207 auth_token_access_valid = False
1208 if not _auth_token:
1208 if not _auth_token:
1209 log.debug("AUTH TOKEN *NOT* present in request")
1209 log.debug("AUTH TOKEN *NOT* present in request")
1210 else:
1210 else:
1211 log.warning(
1211 log.warning(
1212 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1212 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1213
1213
1214 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1214 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1215 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1215 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1216 else 'AUTH_TOKEN_AUTH'
1216 else 'AUTH_TOKEN_AUTH'
1217
1217
1218 if ip_access_valid and (
1218 if ip_access_valid and (
1219 user.is_authenticated or auth_token_access_valid):
1219 user.is_authenticated or auth_token_access_valid):
1220 log.info(
1220 log.info(
1221 'user %s authenticating with:%s IS authenticated on func %s'
1221 'user %s authenticating with:%s IS authenticated on func %s'
1222 % (user, reason, loc))
1222 % (user, reason, loc))
1223
1223
1224 # update user data to check last activity
1224 # update user data to check last activity
1225 user.update_lastactivity()
1225 user.update_lastactivity()
1226 Session().commit()
1226 Session().commit()
1227 return func(*fargs, **fkwargs)
1227 return func(*fargs, **fkwargs)
1228 else:
1228 else:
1229 log.warning(
1229 log.warning(
1230 'user %s authenticating with:%s NOT authenticated on '
1230 'user %s authenticating with:%s NOT authenticated on '
1231 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1231 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1232 % (user, reason, loc, ip_access_valid,
1232 % (user, reason, loc, ip_access_valid,
1233 auth_token_access_valid))
1233 auth_token_access_valid))
1234 # we preserve the get PARAM
1234 # we preserve the get PARAM
1235 came_from = request.path_qs
1235 came_from = request.path_qs
1236
1236
1237 log.debug('redirecting to login page with %s' % (came_from,))
1237 log.debug('redirecting to login page with %s' % (came_from,))
1238 return redirect(
1238 return redirect(
1239 h.route_path('login', _query={'came_from': came_from}))
1239 h.route_path('login', _query={'came_from': came_from}))
1240
1240
1241
1241
1242 class NotAnonymous(object):
1242 class NotAnonymous(object):
1243 """
1243 """
1244 Must be logged in to execute this function else
1244 Must be logged in to execute this function else
1245 redirect to login page"""
1245 redirect to login page"""
1246
1246
1247 def __call__(self, func):
1247 def __call__(self, func):
1248 return get_cython_compat_decorator(self.__wrapper, func)
1248 return get_cython_compat_decorator(self.__wrapper, func)
1249
1249
1250 def __wrapper(self, func, *fargs, **fkwargs):
1250 def __wrapper(self, func, *fargs, **fkwargs):
1251 cls = fargs[0]
1251 cls = fargs[0]
1252 self.user = cls._rhodecode_user
1252 self.user = cls._rhodecode_user
1253
1253
1254 log.debug('Checking if user is not anonymous @%s' % cls)
1254 log.debug('Checking if user is not anonymous @%s' % cls)
1255
1255
1256 anonymous = self.user.username == User.DEFAULT_USER
1256 anonymous = self.user.username == User.DEFAULT_USER
1257
1257
1258 if anonymous:
1258 if anonymous:
1259 came_from = request.path_qs
1259 came_from = request.path_qs
1260
1260
1261 import rhodecode.lib.helpers as h
1261 import rhodecode.lib.helpers as h
1262 h.flash(_('You need to be a registered user to '
1262 h.flash(_('You need to be a registered user to '
1263 'perform this action'),
1263 'perform this action'),
1264 category='warning')
1264 category='warning')
1265 return redirect(
1265 return redirect(
1266 h.route_path('login', _query={'came_from': came_from}))
1266 h.route_path('login', _query={'came_from': came_from}))
1267 else:
1267 else:
1268 return func(*fargs, **fkwargs)
1268 return func(*fargs, **fkwargs)
1269
1269
1270
1270
1271 class XHRRequired(object):
1271 class XHRRequired(object):
1272 def __call__(self, func):
1272 def __call__(self, func):
1273 return get_cython_compat_decorator(self.__wrapper, func)
1273 return get_cython_compat_decorator(self.__wrapper, func)
1274
1274
1275 def __wrapper(self, func, *fargs, **fkwargs):
1275 def __wrapper(self, func, *fargs, **fkwargs):
1276 log.debug('Checking if request is XMLHttpRequest (XHR)')
1276 log.debug('Checking if request is XMLHttpRequest (XHR)')
1277 xhr_message = 'This is not a valid XMLHttpRequest (XHR) request'
1277 xhr_message = 'This is not a valid XMLHttpRequest (XHR) request'
1278 if not request.is_xhr:
1278 if not request.is_xhr:
1279 abort(400, detail=xhr_message)
1279 abort(400, detail=xhr_message)
1280
1280
1281 return func(*fargs, **fkwargs)
1281 return func(*fargs, **fkwargs)
1282
1282
1283
1283
1284 class HasAcceptedRepoType(object):
1284 class HasAcceptedRepoType(object):
1285 """
1285 """
1286 Check if requested repo is within given repo type aliases
1286 Check if requested repo is within given repo type aliases
1287
1287
1288 TODO: anderson: not sure where to put this decorator
1288 TODO: anderson: not sure where to put this decorator
1289 """
1289 """
1290
1290
1291 def __init__(self, *repo_type_list):
1291 def __init__(self, *repo_type_list):
1292 self.repo_type_list = set(repo_type_list)
1292 self.repo_type_list = set(repo_type_list)
1293
1293
1294 def __call__(self, func):
1294 def __call__(self, func):
1295 return get_cython_compat_decorator(self.__wrapper, func)
1295 return get_cython_compat_decorator(self.__wrapper, func)
1296
1296
1297 def __wrapper(self, func, *fargs, **fkwargs):
1297 def __wrapper(self, func, *fargs, **fkwargs):
1298 cls = fargs[0]
1298 cls = fargs[0]
1299 rhodecode_repo = cls.rhodecode_repo
1299 rhodecode_repo = cls.rhodecode_repo
1300
1300
1301 log.debug('%s checking repo type for %s in %s',
1301 log.debug('%s checking repo type for %s in %s',
1302 self.__class__.__name__,
1302 self.__class__.__name__,
1303 rhodecode_repo.alias, self.repo_type_list)
1303 rhodecode_repo.alias, self.repo_type_list)
1304
1304
1305 if rhodecode_repo.alias in self.repo_type_list:
1305 if rhodecode_repo.alias in self.repo_type_list:
1306 return func(*fargs, **fkwargs)
1306 return func(*fargs, **fkwargs)
1307 else:
1307 else:
1308 import rhodecode.lib.helpers as h
1308 import rhodecode.lib.helpers as h
1309 h.flash(h.literal(
1309 h.flash(h.literal(
1310 _('Action not supported for %s.' % rhodecode_repo.alias)),
1310 _('Action not supported for %s.' % rhodecode_repo.alias)),
1311 category='warning')
1311 category='warning')
1312 return redirect(
1312 return redirect(
1313 url('summary_home', repo_name=cls.rhodecode_db_repo.repo_name))
1313 url('summary_home', repo_name=cls.rhodecode_db_repo.repo_name))
1314
1314
1315
1315
1316 class PermsDecorator(object):
1316 class PermsDecorator(object):
1317 """
1317 """
1318 Base class for controller decorators, we extract the current user from
1318 Base class for controller decorators, we extract the current user from
1319 the class itself, which has it stored in base controllers
1319 the class itself, which has it stored in base controllers
1320 """
1320 """
1321
1321
1322 def __init__(self, *required_perms):
1322 def __init__(self, *required_perms):
1323 self.required_perms = set(required_perms)
1323 self.required_perms = set(required_perms)
1324
1324
1325 def __call__(self, func):
1325 def __call__(self, func):
1326 return get_cython_compat_decorator(self.__wrapper, func)
1326 return get_cython_compat_decorator(self.__wrapper, func)
1327
1327
1328 def __wrapper(self, func, *fargs, **fkwargs):
1328 def __wrapper(self, func, *fargs, **fkwargs):
1329 cls = fargs[0]
1329 cls = fargs[0]
1330 _user = cls._rhodecode_user
1330 _user = cls._rhodecode_user
1331
1331
1332 log.debug('checking %s permissions %s for %s %s',
1332 log.debug('checking %s permissions %s for %s %s',
1333 self.__class__.__name__, self.required_perms, cls, _user)
1333 self.__class__.__name__, self.required_perms, cls, _user)
1334
1334
1335 if self.check_permissions(_user):
1335 if self.check_permissions(_user):
1336 log.debug('Permission granted for %s %s', cls, _user)
1336 log.debug('Permission granted for %s %s', cls, _user)
1337 return func(*fargs, **fkwargs)
1337 return func(*fargs, **fkwargs)
1338
1338
1339 else:
1339 else:
1340 log.debug('Permission denied for %s %s', cls, _user)
1340 log.debug('Permission denied for %s %s', cls, _user)
1341 anonymous = _user.username == User.DEFAULT_USER
1341 anonymous = _user.username == User.DEFAULT_USER
1342
1342
1343 if anonymous:
1343 if anonymous:
1344 came_from = request.path_qs
1344 came_from = request.path_qs
1345
1345
1346 import rhodecode.lib.helpers as h
1346 import rhodecode.lib.helpers as h
1347 h.flash(_('You need to be signed in to view this page'),
1347 h.flash(_('You need to be signed in to view this page'),
1348 category='warning')
1348 category='warning')
1349 return redirect(
1349 return redirect(
1350 h.route_path('login', _query={'came_from': came_from}))
1350 h.route_path('login', _query={'came_from': came_from}))
1351
1351
1352 else:
1352 else:
1353 # redirect with forbidden ret code
1353 # redirect with forbidden ret code
1354 return abort(403)
1354 return abort(403)
1355
1355
1356 def check_permissions(self, user):
1356 def check_permissions(self, user):
1357 """Dummy function for overriding"""
1357 """Dummy function for overriding"""
1358 raise NotImplementedError(
1358 raise NotImplementedError(
1359 'You have to write this function in child class')
1359 'You have to write this function in child class')
1360
1360
1361
1361
1362 class HasPermissionAllDecorator(PermsDecorator):
1362 class HasPermissionAllDecorator(PermsDecorator):
1363 """
1363 """
1364 Checks for access permission for all given predicates. All of them
1364 Checks for access permission for all given predicates. All of them
1365 have to be meet in order to fulfill the request
1365 have to be meet in order to fulfill the request
1366 """
1366 """
1367
1367
1368 def check_permissions(self, user):
1368 def check_permissions(self, user):
1369 perms = user.permissions_with_scope({})
1369 perms = user.permissions_with_scope({})
1370 if self.required_perms.issubset(perms['global']):
1370 if self.required_perms.issubset(perms['global']):
1371 return True
1371 return True
1372 return False
1372 return False
1373
1373
1374
1374
1375 class HasPermissionAnyDecorator(PermsDecorator):
1375 class HasPermissionAnyDecorator(PermsDecorator):
1376 """
1376 """
1377 Checks for access permission for any of given predicates. In order to
1377 Checks for access permission for any of given predicates. In order to
1378 fulfill the request any of predicates must be meet
1378 fulfill the request any of predicates must be meet
1379 """
1379 """
1380
1380
1381 def check_permissions(self, user):
1381 def check_permissions(self, user):
1382 perms = user.permissions_with_scope({})
1382 perms = user.permissions_with_scope({})
1383 if self.required_perms.intersection(perms['global']):
1383 if self.required_perms.intersection(perms['global']):
1384 return True
1384 return True
1385 return False
1385 return False
1386
1386
1387
1387
1388 class HasRepoPermissionAllDecorator(PermsDecorator):
1388 class HasRepoPermissionAllDecorator(PermsDecorator):
1389 """
1389 """
1390 Checks for access permission for all given predicates for specific
1390 Checks for access permission for all given predicates for specific
1391 repository. All of them have to be meet in order to fulfill the request
1391 repository. All of them have to be meet in order to fulfill the request
1392 """
1392 """
1393
1393
1394 def check_permissions(self, user):
1394 def check_permissions(self, user):
1395 perms = user.permissions
1395 perms = user.permissions
1396 repo_name = get_repo_slug(request)
1396 repo_name = get_repo_slug(request)
1397 try:
1397 try:
1398 user_perms = set([perms['repositories'][repo_name]])
1398 user_perms = set([perms['repositories'][repo_name]])
1399 except KeyError:
1399 except KeyError:
1400 return False
1400 return False
1401 if self.required_perms.issubset(user_perms):
1401 if self.required_perms.issubset(user_perms):
1402 return True
1402 return True
1403 return False
1403 return False
1404
1404
1405
1405
1406 class HasRepoPermissionAnyDecorator(PermsDecorator):
1406 class HasRepoPermissionAnyDecorator(PermsDecorator):
1407 """
1407 """
1408 Checks for access permission for any of given predicates for specific
1408 Checks for access permission for any of given predicates for specific
1409 repository. In order to fulfill the request any of predicates must be meet
1409 repository. In order to fulfill the request any of predicates must be meet
1410 """
1410 """
1411
1411
1412 def check_permissions(self, user):
1412 def check_permissions(self, user):
1413 perms = user.permissions
1413 perms = user.permissions
1414 repo_name = get_repo_slug(request)
1414 repo_name = get_repo_slug(request)
1415 try:
1415 try:
1416 user_perms = set([perms['repositories'][repo_name]])
1416 user_perms = set([perms['repositories'][repo_name]])
1417 except KeyError:
1417 except KeyError:
1418 return False
1418 return False
1419
1419
1420 if self.required_perms.intersection(user_perms):
1420 if self.required_perms.intersection(user_perms):
1421 return True
1421 return True
1422 return False
1422 return False
1423
1423
1424
1424
1425 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1425 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1426 """
1426 """
1427 Checks for access permission for all given predicates for specific
1427 Checks for access permission for all given predicates for specific
1428 repository group. All of them have to be meet in order to
1428 repository group. All of them have to be meet in order to
1429 fulfill the request
1429 fulfill the request
1430 """
1430 """
1431
1431
1432 def check_permissions(self, user):
1432 def check_permissions(self, user):
1433 perms = user.permissions
1433 perms = user.permissions
1434 group_name = get_repo_group_slug(request)
1434 group_name = get_repo_group_slug(request)
1435 try:
1435 try:
1436 user_perms = set([perms['repositories_groups'][group_name]])
1436 user_perms = set([perms['repositories_groups'][group_name]])
1437 except KeyError:
1437 except KeyError:
1438 return False
1438 return False
1439
1439
1440 if self.required_perms.issubset(user_perms):
1440 if self.required_perms.issubset(user_perms):
1441 return True
1441 return True
1442 return False
1442 return False
1443
1443
1444
1444
1445 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1445 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1446 """
1446 """
1447 Checks for access permission for any of given predicates for specific
1447 Checks for access permission for any of given predicates for specific
1448 repository group. In order to fulfill the request any
1448 repository group. In order to fulfill the request any
1449 of predicates must be met
1449 of predicates must be met
1450 """
1450 """
1451
1451
1452 def check_permissions(self, user):
1452 def check_permissions(self, user):
1453 perms = user.permissions
1453 perms = user.permissions
1454 group_name = get_repo_group_slug(request)
1454 group_name = get_repo_group_slug(request)
1455 try:
1455 try:
1456 user_perms = set([perms['repositories_groups'][group_name]])
1456 user_perms = set([perms['repositories_groups'][group_name]])
1457 except KeyError:
1457 except KeyError:
1458 return False
1458 return False
1459
1459
1460 if self.required_perms.intersection(user_perms):
1460 if self.required_perms.intersection(user_perms):
1461 return True
1461 return True
1462 return False
1462 return False
1463
1463
1464
1464
1465 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1465 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1466 """
1466 """
1467 Checks for access permission for all given predicates for specific
1467 Checks for access permission for all given predicates for specific
1468 user group. All of them have to be meet in order to fulfill the request
1468 user group. All of them have to be meet in order to fulfill the request
1469 """
1469 """
1470
1470
1471 def check_permissions(self, user):
1471 def check_permissions(self, user):
1472 perms = user.permissions
1472 perms = user.permissions
1473 group_name = get_user_group_slug(request)
1473 group_name = get_user_group_slug(request)
1474 try:
1474 try:
1475 user_perms = set([perms['user_groups'][group_name]])
1475 user_perms = set([perms['user_groups'][group_name]])
1476 except KeyError:
1476 except KeyError:
1477 return False
1477 return False
1478
1478
1479 if self.required_perms.issubset(user_perms):
1479 if self.required_perms.issubset(user_perms):
1480 return True
1480 return True
1481 return False
1481 return False
1482
1482
1483
1483
1484 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1484 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1485 """
1485 """
1486 Checks for access permission for any of given predicates for specific
1486 Checks for access permission for any of given predicates for specific
1487 user group. In order to fulfill the request any of predicates must be meet
1487 user group. In order to fulfill the request any of predicates must be meet
1488 """
1488 """
1489
1489
1490 def check_permissions(self, user):
1490 def check_permissions(self, user):
1491 perms = user.permissions
1491 perms = user.permissions
1492 group_name = get_user_group_slug(request)
1492 group_name = get_user_group_slug(request)
1493 try:
1493 try:
1494 user_perms = set([perms['user_groups'][group_name]])
1494 user_perms = set([perms['user_groups'][group_name]])
1495 except KeyError:
1495 except KeyError:
1496 return False
1496 return False
1497
1497
1498 if self.required_perms.intersection(user_perms):
1498 if self.required_perms.intersection(user_perms):
1499 return True
1499 return True
1500 return False
1500 return False
1501
1501
1502
1502
1503 # CHECK FUNCTIONS
1503 # CHECK FUNCTIONS
1504 class PermsFunction(object):
1504 class PermsFunction(object):
1505 """Base function for other check functions"""
1505 """Base function for other check functions"""
1506
1506
1507 def __init__(self, *perms):
1507 def __init__(self, *perms):
1508 self.required_perms = set(perms)
1508 self.required_perms = set(perms)
1509 self.repo_name = None
1509 self.repo_name = None
1510 self.repo_group_name = None
1510 self.repo_group_name = None
1511 self.user_group_name = None
1511 self.user_group_name = None
1512
1512
1513 def __bool__(self):
1513 def __bool__(self):
1514 frame = inspect.currentframe()
1514 frame = inspect.currentframe()
1515 stack_trace = traceback.format_stack(frame)
1515 stack_trace = traceback.format_stack(frame)
1516 log.error('Checking bool value on a class instance of perm '
1516 log.error('Checking bool value on a class instance of perm '
1517 'function is not allowed: %s' % ''.join(stack_trace))
1517 'function is not allowed: %s' % ''.join(stack_trace))
1518 # rather than throwing errors, here we always return False so if by
1518 # rather than throwing errors, here we always return False so if by
1519 # accident someone checks truth for just an instance it will always end
1519 # accident someone checks truth for just an instance it will always end
1520 # up in returning False
1520 # up in returning False
1521 return False
1521 return False
1522 __nonzero__ = __bool__
1522 __nonzero__ = __bool__
1523
1523
1524 def __call__(self, check_location='', user=None):
1524 def __call__(self, check_location='', user=None):
1525 if not user:
1525 if not user:
1526 log.debug('Using user attribute from global request')
1526 log.debug('Using user attribute from global request')
1527 # TODO: remove this someday,put as user as attribute here
1527 # TODO: remove this someday,put as user as attribute here
1528 user = request.user
1528 user = request.user
1529
1529
1530 # init auth user if not already given
1530 # init auth user if not already given
1531 if not isinstance(user, AuthUser):
1531 if not isinstance(user, AuthUser):
1532 log.debug('Wrapping user %s into AuthUser', user)
1532 log.debug('Wrapping user %s into AuthUser', user)
1533 user = AuthUser(user.user_id)
1533 user = AuthUser(user.user_id)
1534
1534
1535 cls_name = self.__class__.__name__
1535 cls_name = self.__class__.__name__
1536 check_scope = self._get_check_scope(cls_name)
1536 check_scope = self._get_check_scope(cls_name)
1537 check_location = check_location or 'unspecified location'
1537 check_location = check_location or 'unspecified location'
1538
1538
1539 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1539 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1540 self.required_perms, user, check_scope, check_location)
1540 self.required_perms, user, check_scope, check_location)
1541 if not user:
1541 if not user:
1542 log.warning('Empty user given for permission check')
1542 log.warning('Empty user given for permission check')
1543 return False
1543 return False
1544
1544
1545 if self.check_permissions(user):
1545 if self.check_permissions(user):
1546 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1546 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1547 check_scope, user, check_location)
1547 check_scope, user, check_location)
1548 return True
1548 return True
1549
1549
1550 else:
1550 else:
1551 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1551 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1552 check_scope, user, check_location)
1552 check_scope, user, check_location)
1553 return False
1553 return False
1554
1554
1555 def _get_check_scope(self, cls_name):
1555 def _get_check_scope(self, cls_name):
1556 return {
1556 return {
1557 'HasPermissionAll': 'GLOBAL',
1557 'HasPermissionAll': 'GLOBAL',
1558 'HasPermissionAny': 'GLOBAL',
1558 'HasPermissionAny': 'GLOBAL',
1559 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1559 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1560 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1560 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1561 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1561 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1562 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1562 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1563 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1563 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1564 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1564 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1565 }.get(cls_name, '?:%s' % cls_name)
1565 }.get(cls_name, '?:%s' % cls_name)
1566
1566
1567 def check_permissions(self, user):
1567 def check_permissions(self, user):
1568 """Dummy function for overriding"""
1568 """Dummy function for overriding"""
1569 raise Exception('You have to write this function in child class')
1569 raise Exception('You have to write this function in child class')
1570
1570
1571
1571
1572 class HasPermissionAll(PermsFunction):
1572 class HasPermissionAll(PermsFunction):
1573 def check_permissions(self, user):
1573 def check_permissions(self, user):
1574 perms = user.permissions_with_scope({})
1574 perms = user.permissions_with_scope({})
1575 if self.required_perms.issubset(perms.get('global')):
1575 if self.required_perms.issubset(perms.get('global')):
1576 return True
1576 return True
1577 return False
1577 return False
1578
1578
1579
1579
1580 class HasPermissionAny(PermsFunction):
1580 class HasPermissionAny(PermsFunction):
1581 def check_permissions(self, user):
1581 def check_permissions(self, user):
1582 perms = user.permissions_with_scope({})
1582 perms = user.permissions_with_scope({})
1583 if self.required_perms.intersection(perms.get('global')):
1583 if self.required_perms.intersection(perms.get('global')):
1584 return True
1584 return True
1585 return False
1585 return False
1586
1586
1587
1587
1588 class HasRepoPermissionAll(PermsFunction):
1588 class HasRepoPermissionAll(PermsFunction):
1589 def __call__(self, repo_name=None, check_location='', user=None):
1589 def __call__(self, repo_name=None, check_location='', user=None):
1590 self.repo_name = repo_name
1590 self.repo_name = repo_name
1591 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1591 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1592
1592
1593 def check_permissions(self, user):
1593 def check_permissions(self, user):
1594 if not self.repo_name:
1594 if not self.repo_name:
1595 self.repo_name = get_repo_slug(request)
1595 self.repo_name = get_repo_slug(request)
1596
1596
1597 perms = user.permissions
1597 perms = user.permissions
1598 try:
1598 try:
1599 user_perms = set([perms['repositories'][self.repo_name]])
1599 user_perms = set([perms['repositories'][self.repo_name]])
1600 except KeyError:
1600 except KeyError:
1601 return False
1601 return False
1602 if self.required_perms.issubset(user_perms):
1602 if self.required_perms.issubset(user_perms):
1603 return True
1603 return True
1604 return False
1604 return False
1605
1605
1606
1606
1607 class HasRepoPermissionAny(PermsFunction):
1607 class HasRepoPermissionAny(PermsFunction):
1608 def __call__(self, repo_name=None, check_location='', user=None):
1608 def __call__(self, repo_name=None, check_location='', user=None):
1609 self.repo_name = repo_name
1609 self.repo_name = repo_name
1610 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1610 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1611
1611
1612 def check_permissions(self, user):
1612 def check_permissions(self, user):
1613 if not self.repo_name:
1613 if not self.repo_name:
1614 self.repo_name = get_repo_slug(request)
1614 self.repo_name = get_repo_slug(request)
1615
1615
1616 perms = user.permissions
1616 perms = user.permissions
1617 try:
1617 try:
1618 user_perms = set([perms['repositories'][self.repo_name]])
1618 user_perms = set([perms['repositories'][self.repo_name]])
1619 except KeyError:
1619 except KeyError:
1620 return False
1620 return False
1621 if self.required_perms.intersection(user_perms):
1621 if self.required_perms.intersection(user_perms):
1622 return True
1622 return True
1623 return False
1623 return False
1624
1624
1625
1625
1626 class HasRepoGroupPermissionAny(PermsFunction):
1626 class HasRepoGroupPermissionAny(PermsFunction):
1627 def __call__(self, group_name=None, check_location='', user=None):
1627 def __call__(self, group_name=None, check_location='', user=None):
1628 self.repo_group_name = group_name
1628 self.repo_group_name = group_name
1629 return super(HasRepoGroupPermissionAny, self).__call__(
1629 return super(HasRepoGroupPermissionAny, self).__call__(
1630 check_location, user)
1630 check_location, user)
1631
1631
1632 def check_permissions(self, user):
1632 def check_permissions(self, user):
1633 perms = user.permissions
1633 perms = user.permissions
1634 try:
1634 try:
1635 user_perms = set(
1635 user_perms = set(
1636 [perms['repositories_groups'][self.repo_group_name]])
1636 [perms['repositories_groups'][self.repo_group_name]])
1637 except KeyError:
1637 except KeyError:
1638 return False
1638 return False
1639 if self.required_perms.intersection(user_perms):
1639 if self.required_perms.intersection(user_perms):
1640 return True
1640 return True
1641 return False
1641 return False
1642
1642
1643
1643
1644 class HasRepoGroupPermissionAll(PermsFunction):
1644 class HasRepoGroupPermissionAll(PermsFunction):
1645 def __call__(self, group_name=None, check_location='', user=None):
1645 def __call__(self, group_name=None, check_location='', user=None):
1646 self.repo_group_name = group_name
1646 self.repo_group_name = group_name
1647 return super(HasRepoGroupPermissionAll, self).__call__(
1647 return super(HasRepoGroupPermissionAll, self).__call__(
1648 check_location, user)
1648 check_location, user)
1649
1649
1650 def check_permissions(self, user):
1650 def check_permissions(self, user):
1651 perms = user.permissions
1651 perms = user.permissions
1652 try:
1652 try:
1653 user_perms = set(
1653 user_perms = set(
1654 [perms['repositories_groups'][self.repo_group_name]])
1654 [perms['repositories_groups'][self.repo_group_name]])
1655 except KeyError:
1655 except KeyError:
1656 return False
1656 return False
1657 if self.required_perms.issubset(user_perms):
1657 if self.required_perms.issubset(user_perms):
1658 return True
1658 return True
1659 return False
1659 return False
1660
1660
1661
1661
1662 class HasUserGroupPermissionAny(PermsFunction):
1662 class HasUserGroupPermissionAny(PermsFunction):
1663 def __call__(self, user_group_name=None, check_location='', user=None):
1663 def __call__(self, user_group_name=None, check_location='', user=None):
1664 self.user_group_name = user_group_name
1664 self.user_group_name = user_group_name
1665 return super(HasUserGroupPermissionAny, self).__call__(
1665 return super(HasUserGroupPermissionAny, self).__call__(
1666 check_location, user)
1666 check_location, user)
1667
1667
1668 def check_permissions(self, user):
1668 def check_permissions(self, user):
1669 perms = user.permissions
1669 perms = user.permissions
1670 try:
1670 try:
1671 user_perms = set([perms['user_groups'][self.user_group_name]])
1671 user_perms = set([perms['user_groups'][self.user_group_name]])
1672 except KeyError:
1672 except KeyError:
1673 return False
1673 return False
1674 if self.required_perms.intersection(user_perms):
1674 if self.required_perms.intersection(user_perms):
1675 return True
1675 return True
1676 return False
1676 return False
1677
1677
1678
1678
1679 class HasUserGroupPermissionAll(PermsFunction):
1679 class HasUserGroupPermissionAll(PermsFunction):
1680 def __call__(self, user_group_name=None, check_location='', user=None):
1680 def __call__(self, user_group_name=None, check_location='', user=None):
1681 self.user_group_name = user_group_name
1681 self.user_group_name = user_group_name
1682 return super(HasUserGroupPermissionAll, self).__call__(
1682 return super(HasUserGroupPermissionAll, self).__call__(
1683 check_location, user)
1683 check_location, user)
1684
1684
1685 def check_permissions(self, user):
1685 def check_permissions(self, user):
1686 perms = user.permissions
1686 perms = user.permissions
1687 try:
1687 try:
1688 user_perms = set([perms['user_groups'][self.user_group_name]])
1688 user_perms = set([perms['user_groups'][self.user_group_name]])
1689 except KeyError:
1689 except KeyError:
1690 return False
1690 return False
1691 if self.required_perms.issubset(user_perms):
1691 if self.required_perms.issubset(user_perms):
1692 return True
1692 return True
1693 return False
1693 return False
1694
1694
1695
1695
1696 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
1696 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
1697 class HasPermissionAnyMiddleware(object):
1697 class HasPermissionAnyMiddleware(object):
1698 def __init__(self, *perms):
1698 def __init__(self, *perms):
1699 self.required_perms = set(perms)
1699 self.required_perms = set(perms)
1700
1700
1701 def __call__(self, user, repo_name):
1701 def __call__(self, user, repo_name):
1702 # repo_name MUST be unicode, since we handle keys in permission
1702 # repo_name MUST be unicode, since we handle keys in permission
1703 # dict by unicode
1703 # dict by unicode
1704 repo_name = safe_unicode(repo_name)
1704 repo_name = safe_unicode(repo_name)
1705 user = AuthUser(user.user_id)
1705 user = AuthUser(user.user_id)
1706 log.debug(
1706 log.debug(
1707 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
1707 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
1708 self.required_perms, user, repo_name)
1708 self.required_perms, user, repo_name)
1709
1709
1710 if self.check_permissions(user, repo_name):
1710 if self.check_permissions(user, repo_name):
1711 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
1711 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
1712 repo_name, user, 'PermissionMiddleware')
1712 repo_name, user, 'PermissionMiddleware')
1713 return True
1713 return True
1714
1714
1715 else:
1715 else:
1716 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
1716 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
1717 repo_name, user, 'PermissionMiddleware')
1717 repo_name, user, 'PermissionMiddleware')
1718 return False
1718 return False
1719
1719
1720 def check_permissions(self, user, repo_name):
1720 def check_permissions(self, user, repo_name):
1721 perms = user.permissions_with_scope({'repo_name': repo_name})
1721 perms = user.permissions_with_scope({'repo_name': repo_name})
1722
1722
1723 try:
1723 try:
1724 user_perms = set([perms['repositories'][repo_name]])
1724 user_perms = set([perms['repositories'][repo_name]])
1725 except Exception:
1725 except Exception:
1726 log.exception('Error while accessing user permissions')
1726 log.exception('Error while accessing user permissions')
1727 return False
1727 return False
1728
1728
1729 if self.required_perms.intersection(user_perms):
1729 if self.required_perms.intersection(user_perms):
1730 return True
1730 return True
1731 return False
1731 return False
1732
1732
1733
1733
1734 # SPECIAL VERSION TO HANDLE API AUTH
1734 # SPECIAL VERSION TO HANDLE API AUTH
1735 class _BaseApiPerm(object):
1735 class _BaseApiPerm(object):
1736 def __init__(self, *perms):
1736 def __init__(self, *perms):
1737 self.required_perms = set(perms)
1737 self.required_perms = set(perms)
1738
1738
1739 def __call__(self, check_location=None, user=None, repo_name=None,
1739 def __call__(self, check_location=None, user=None, repo_name=None,
1740 group_name=None, user_group_name=None):
1740 group_name=None, user_group_name=None):
1741 cls_name = self.__class__.__name__
1741 cls_name = self.__class__.__name__
1742 check_scope = 'global:%s' % (self.required_perms,)
1742 check_scope = 'global:%s' % (self.required_perms,)
1743 if repo_name:
1743 if repo_name:
1744 check_scope += ', repo_name:%s' % (repo_name,)
1744 check_scope += ', repo_name:%s' % (repo_name,)
1745
1745
1746 if group_name:
1746 if group_name:
1747 check_scope += ', repo_group_name:%s' % (group_name,)
1747 check_scope += ', repo_group_name:%s' % (group_name,)
1748
1748
1749 if user_group_name:
1749 if user_group_name:
1750 check_scope += ', user_group_name:%s' % (user_group_name,)
1750 check_scope += ', user_group_name:%s' % (user_group_name,)
1751
1751
1752 log.debug(
1752 log.debug(
1753 'checking cls:%s %s %s @ %s'
1753 'checking cls:%s %s %s @ %s'
1754 % (cls_name, self.required_perms, check_scope, check_location))
1754 % (cls_name, self.required_perms, check_scope, check_location))
1755 if not user:
1755 if not user:
1756 log.debug('Empty User passed into arguments')
1756 log.debug('Empty User passed into arguments')
1757 return False
1757 return False
1758
1758
1759 # process user
1759 # process user
1760 if not isinstance(user, AuthUser):
1760 if not isinstance(user, AuthUser):
1761 user = AuthUser(user.user_id)
1761 user = AuthUser(user.user_id)
1762 if not check_location:
1762 if not check_location:
1763 check_location = 'unspecified'
1763 check_location = 'unspecified'
1764 if self.check_permissions(user.permissions, repo_name, group_name,
1764 if self.check_permissions(user.permissions, repo_name, group_name,
1765 user_group_name):
1765 user_group_name):
1766 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1766 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1767 check_scope, user, check_location)
1767 check_scope, user, check_location)
1768 return True
1768 return True
1769
1769
1770 else:
1770 else:
1771 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1771 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1772 check_scope, user, check_location)
1772 check_scope, user, check_location)
1773 return False
1773 return False
1774
1774
1775 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1775 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1776 user_group_name=None):
1776 user_group_name=None):
1777 """
1777 """
1778 implement in child class should return True if permissions are ok,
1778 implement in child class should return True if permissions are ok,
1779 False otherwise
1779 False otherwise
1780
1780
1781 :param perm_defs: dict with permission definitions
1781 :param perm_defs: dict with permission definitions
1782 :param repo_name: repo name
1782 :param repo_name: repo name
1783 """
1783 """
1784 raise NotImplementedError()
1784 raise NotImplementedError()
1785
1785
1786
1786
1787 class HasPermissionAllApi(_BaseApiPerm):
1787 class HasPermissionAllApi(_BaseApiPerm):
1788 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1788 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1789 user_group_name=None):
1789 user_group_name=None):
1790 if self.required_perms.issubset(perm_defs.get('global')):
1790 if self.required_perms.issubset(perm_defs.get('global')):
1791 return True
1791 return True
1792 return False
1792 return False
1793
1793
1794
1794
1795 class HasPermissionAnyApi(_BaseApiPerm):
1795 class HasPermissionAnyApi(_BaseApiPerm):
1796 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1796 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1797 user_group_name=None):
1797 user_group_name=None):
1798 if self.required_perms.intersection(perm_defs.get('global')):
1798 if self.required_perms.intersection(perm_defs.get('global')):
1799 return True
1799 return True
1800 return False
1800 return False
1801
1801
1802
1802
1803 class HasRepoPermissionAllApi(_BaseApiPerm):
1803 class HasRepoPermissionAllApi(_BaseApiPerm):
1804 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1804 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1805 user_group_name=None):
1805 user_group_name=None):
1806 try:
1806 try:
1807 _user_perms = set([perm_defs['repositories'][repo_name]])
1807 _user_perms = set([perm_defs['repositories'][repo_name]])
1808 except KeyError:
1808 except KeyError:
1809 log.warning(traceback.format_exc())
1809 log.warning(traceback.format_exc())
1810 return False
1810 return False
1811 if self.required_perms.issubset(_user_perms):
1811 if self.required_perms.issubset(_user_perms):
1812 return True
1812 return True
1813 return False
1813 return False
1814
1814
1815
1815
1816 class HasRepoPermissionAnyApi(_BaseApiPerm):
1816 class HasRepoPermissionAnyApi(_BaseApiPerm):
1817 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1817 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1818 user_group_name=None):
1818 user_group_name=None):
1819 try:
1819 try:
1820 _user_perms = set([perm_defs['repositories'][repo_name]])
1820 _user_perms = set([perm_defs['repositories'][repo_name]])
1821 except KeyError:
1821 except KeyError:
1822 log.warning(traceback.format_exc())
1822 log.warning(traceback.format_exc())
1823 return False
1823 return False
1824 if self.required_perms.intersection(_user_perms):
1824 if self.required_perms.intersection(_user_perms):
1825 return True
1825 return True
1826 return False
1826 return False
1827
1827
1828
1828
1829 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
1829 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
1830 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1830 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1831 user_group_name=None):
1831 user_group_name=None):
1832 try:
1832 try:
1833 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1833 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1834 except KeyError:
1834 except KeyError:
1835 log.warning(traceback.format_exc())
1835 log.warning(traceback.format_exc())
1836 return False
1836 return False
1837 if self.required_perms.intersection(_user_perms):
1837 if self.required_perms.intersection(_user_perms):
1838 return True
1838 return True
1839 return False
1839 return False
1840
1840
1841
1841
1842 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
1842 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
1843 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1843 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1844 user_group_name=None):
1844 user_group_name=None):
1845 try:
1845 try:
1846 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1846 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1847 except KeyError:
1847 except KeyError:
1848 log.warning(traceback.format_exc())
1848 log.warning(traceback.format_exc())
1849 return False
1849 return False
1850 if self.required_perms.issubset(_user_perms):
1850 if self.required_perms.issubset(_user_perms):
1851 return True
1851 return True
1852 return False
1852 return False
1853
1853
1854
1854
1855 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
1855 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
1856 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1856 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1857 user_group_name=None):
1857 user_group_name=None):
1858 try:
1858 try:
1859 _user_perms = set([perm_defs['user_groups'][user_group_name]])
1859 _user_perms = set([perm_defs['user_groups'][user_group_name]])
1860 except KeyError:
1860 except KeyError:
1861 log.warning(traceback.format_exc())
1861 log.warning(traceback.format_exc())
1862 return False
1862 return False
1863 if self.required_perms.intersection(_user_perms):
1863 if self.required_perms.intersection(_user_perms):
1864 return True
1864 return True
1865 return False
1865 return False
1866
1866
1867
1867
1868 def check_ip_access(source_ip, allowed_ips=None):
1868 def check_ip_access(source_ip, allowed_ips=None):
1869 """
1869 """
1870 Checks if source_ip is a subnet of any of allowed_ips.
1870 Checks if source_ip is a subnet of any of allowed_ips.
1871
1871
1872 :param source_ip:
1872 :param source_ip:
1873 :param allowed_ips: list of allowed ips together with mask
1873 :param allowed_ips: list of allowed ips together with mask
1874 """
1874 """
1875 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
1875 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
1876 source_ip_address = ipaddress.ip_address(source_ip)
1876 source_ip_address = ipaddress.ip_address(source_ip)
1877 if isinstance(allowed_ips, (tuple, list, set)):
1877 if isinstance(allowed_ips, (tuple, list, set)):
1878 for ip in allowed_ips:
1878 for ip in allowed_ips:
1879 try:
1879 try:
1880 network_address = ipaddress.ip_network(ip, strict=False)
1880 network_address = ipaddress.ip_network(ip, strict=False)
1881 if source_ip_address in network_address:
1881 if source_ip_address in network_address:
1882 log.debug('IP %s is network %s' %
1882 log.debug('IP %s is network %s' %
1883 (source_ip_address, network_address))
1883 (source_ip_address, network_address))
1884 return True
1884 return True
1885 # for any case we cannot determine the IP, don't crash just
1885 # for any case we cannot determine the IP, don't crash just
1886 # skip it and log as error, we want to say forbidden still when
1886 # skip it and log as error, we want to say forbidden still when
1887 # sending bad IP
1887 # sending bad IP
1888 except Exception:
1888 except Exception:
1889 log.error(traceback.format_exc())
1889 log.error(traceback.format_exc())
1890 continue
1890 continue
1891 return False
1891 return False
1892
1892
1893
1893
1894 def get_cython_compat_decorator(wrapper, func):
1894 def get_cython_compat_decorator(wrapper, func):
1895 """
1895 """
1896 Creates a cython compatible decorator. The previously used
1896 Creates a cython compatible decorator. The previously used
1897 decorator.decorator() function seems to be incompatible with cython.
1897 decorator.decorator() function seems to be incompatible with cython.
1898
1898
1899 :param wrapper: __wrapper method of the decorator class
1899 :param wrapper: __wrapper method of the decorator class
1900 :param func: decorated function
1900 :param func: decorated function
1901 """
1901 """
1902 @wraps(func)
1902 @wraps(func)
1903 def local_wrapper(*args, **kwds):
1903 def local_wrapper(*args, **kwds):
1904 return wrapper(func, *args, **kwds)
1904 return wrapper(func, *args, **kwds)
1905 local_wrapper.__wrapped__ = func
1905 local_wrapper.__wrapped__ = func
1906 return local_wrapper
1906 return local_wrapper
@@ -1,604 +1,604 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
59 from rhodecode.model.db import Repository, User
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 vcs_operation_context(
165 def vcs_operation_context(
166 environ, repo_name, username, action, scm, check_locking=True,
166 environ, repo_name, username, action, scm, check_locking=True,
167 is_shadow_repo=False):
167 is_shadow_repo=False):
168 """
168 """
169 Generate the context for a vcs operation, e.g. push or pull.
169 Generate the context for a vcs operation, e.g. push or pull.
170
170
171 This context is passed over the layers so that hooks triggered by the
171 This context is passed over the layers so that hooks triggered by the
172 vcs operation know details like the user, the user's IP address etc.
172 vcs operation know details like the user, the user's IP address etc.
173
173
174 :param check_locking: Allows to switch of the computation of the locking
174 :param check_locking: Allows to switch of the computation of the locking
175 data. This serves mainly the need of the simplevcs middleware to be
175 data. This serves mainly the need of the simplevcs middleware to be
176 able to disable this for certain operations.
176 able to disable this for certain operations.
177
177
178 """
178 """
179 # Tri-state value: False: unlock, None: nothing, True: lock
179 # Tri-state value: False: unlock, None: nothing, True: lock
180 make_lock = None
180 make_lock = None
181 locked_by = [None, None, None]
181 locked_by = [None, None, None]
182 is_anonymous = username == User.DEFAULT_USER
182 is_anonymous = username == User.DEFAULT_USER
183 if not is_anonymous and check_locking:
183 if not is_anonymous and check_locking:
184 log.debug('Checking locking on repository "%s"', repo_name)
184 log.debug('Checking locking on repository "%s"', repo_name)
185 user = User.get_by_username(username)
185 user = User.get_by_username(username)
186 repo = Repository.get_by_repo_name(repo_name)
186 repo = Repository.get_by_repo_name(repo_name)
187 make_lock, __, locked_by = repo.get_locking_state(
187 make_lock, __, locked_by = repo.get_locking_state(
188 action, user.user_id)
188 action, user.user_id)
189
189
190 settings_model = VcsSettingsModel(repo=repo_name)
190 settings_model = VcsSettingsModel(repo=repo_name)
191 ui_settings = settings_model.get_ui_settings()
191 ui_settings = settings_model.get_ui_settings()
192
192
193 extras = {
193 extras = {
194 'ip': get_ip_addr(environ),
194 'ip': get_ip_addr(environ),
195 'username': username,
195 'username': username,
196 'action': action,
196 'action': action,
197 'repository': repo_name,
197 'repository': repo_name,
198 'scm': scm,
198 'scm': scm,
199 'config': rhodecode.CONFIG['__file__'],
199 'config': rhodecode.CONFIG['__file__'],
200 'make_lock': make_lock,
200 'make_lock': make_lock,
201 'locked_by': locked_by,
201 'locked_by': locked_by,
202 'server_url': utils2.get_server_url(environ),
202 'server_url': utils2.get_server_url(environ),
203 'hooks': get_enabled_hook_classes(ui_settings),
203 'hooks': get_enabled_hook_classes(ui_settings),
204 'is_shadow_repo': is_shadow_repo,
204 'is_shadow_repo': is_shadow_repo,
205 }
205 }
206 return extras
206 return extras
207
207
208
208
209 class BasicAuth(AuthBasicAuthenticator):
209 class BasicAuth(AuthBasicAuthenticator):
210
210
211 def __init__(self, realm, authfunc, registry, auth_http_code=None,
211 def __init__(self, realm, authfunc, registry, auth_http_code=None,
212 initial_call_detection=False):
212 initial_call_detection=False):
213 self.realm = realm
213 self.realm = realm
214 self.initial_call = initial_call_detection
214 self.initial_call = initial_call_detection
215 self.authfunc = authfunc
215 self.authfunc = authfunc
216 self.registry = registry
216 self.registry = registry
217 self._rc_auth_http_code = auth_http_code
217 self._rc_auth_http_code = auth_http_code
218
218
219 def _get_response_from_code(self, http_code):
219 def _get_response_from_code(self, http_code):
220 try:
220 try:
221 return get_exception(safe_int(http_code))
221 return get_exception(safe_int(http_code))
222 except Exception:
222 except Exception:
223 log.exception('Failed to fetch response for code %s' % http_code)
223 log.exception('Failed to fetch response for code %s' % http_code)
224 return HTTPForbidden
224 return HTTPForbidden
225
225
226 def build_authentication(self):
226 def build_authentication(self):
227 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
227 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
228 if self._rc_auth_http_code and not self.initial_call:
228 if self._rc_auth_http_code and not self.initial_call:
229 # return alternative HTTP code if alternative http return code
229 # return alternative HTTP code if alternative http return code
230 # is specified in RhodeCode config, but ONLY if it's not the
230 # is specified in RhodeCode config, but ONLY if it's not the
231 # FIRST call
231 # FIRST call
232 custom_response_klass = self._get_response_from_code(
232 custom_response_klass = self._get_response_from_code(
233 self._rc_auth_http_code)
233 self._rc_auth_http_code)
234 return custom_response_klass(headers=head)
234 return custom_response_klass(headers=head)
235 return HTTPUnauthorized(headers=head)
235 return HTTPUnauthorized(headers=head)
236
236
237 def authenticate(self, environ):
237 def authenticate(self, environ):
238 authorization = AUTHORIZATION(environ)
238 authorization = AUTHORIZATION(environ)
239 if not authorization:
239 if not authorization:
240 return self.build_authentication()
240 return self.build_authentication()
241 (authmeth, auth) = authorization.split(' ', 1)
241 (authmeth, auth) = authorization.split(' ', 1)
242 if 'basic' != authmeth.lower():
242 if 'basic' != authmeth.lower():
243 return self.build_authentication()
243 return self.build_authentication()
244 auth = auth.strip().decode('base64')
244 auth = auth.strip().decode('base64')
245 _parts = auth.split(':', 1)
245 _parts = auth.split(':', 1)
246 if len(_parts) == 2:
246 if len(_parts) == 2:
247 username, password = _parts
247 username, password = _parts
248 if self.authfunc(
248 if self.authfunc(
249 username, password, environ, VCS_TYPE,
249 username, password, environ, VCS_TYPE,
250 registry=self.registry):
250 registry=self.registry):
251 return username
251 return username
252 if username and password:
252 if username and password:
253 # we mark that we actually executed authentication once, at
253 # we mark that we actually executed authentication once, at
254 # that point we can use the alternative auth code
254 # that point we can use the alternative auth code
255 self.initial_call = False
255 self.initial_call = False
256
256
257 return self.build_authentication()
257 return self.build_authentication()
258
258
259 __call__ = authenticate
259 __call__ = authenticate
260
260
261
261
262 def attach_context_attributes(context, request):
262 def attach_context_attributes(context, request):
263 """
263 """
264 Attach variables into template context called `c`, please note that
264 Attach variables into template context called `c`, please note that
265 request could be pylons or pyramid request in here.
265 request could be pylons or pyramid request in here.
266 """
266 """
267 rc_config = SettingsModel().get_all_settings(cache=True)
267 rc_config = SettingsModel().get_all_settings(cache=True)
268
268
269 context.rhodecode_version = rhodecode.__version__
269 context.rhodecode_version = rhodecode.__version__
270 context.rhodecode_edition = config.get('rhodecode.edition')
270 context.rhodecode_edition = config.get('rhodecode.edition')
271 # unique secret + version does not leak the version but keep consistency
271 # unique secret + version does not leak the version but keep consistency
272 context.rhodecode_version_hash = md5(
272 context.rhodecode_version_hash = md5(
273 config.get('beaker.session.secret', '') +
273 config.get('beaker.session.secret', '') +
274 rhodecode.__version__)[:8]
274 rhodecode.__version__)[:8]
275
275
276 # Default language set for the incoming request
276 # Default language set for the incoming request
277 context.language = translation.get_lang()[0]
277 context.language = translation.get_lang()[0]
278
278
279 # Visual options
279 # Visual options
280 context.visual = AttributeDict({})
280 context.visual = AttributeDict({})
281
281
282 # DB stored Visual Items
282 # DB stored Visual Items
283 context.visual.show_public_icon = str2bool(
283 context.visual.show_public_icon = str2bool(
284 rc_config.get('rhodecode_show_public_icon'))
284 rc_config.get('rhodecode_show_public_icon'))
285 context.visual.show_private_icon = str2bool(
285 context.visual.show_private_icon = str2bool(
286 rc_config.get('rhodecode_show_private_icon'))
286 rc_config.get('rhodecode_show_private_icon'))
287 context.visual.stylify_metatags = str2bool(
287 context.visual.stylify_metatags = str2bool(
288 rc_config.get('rhodecode_stylify_metatags'))
288 rc_config.get('rhodecode_stylify_metatags'))
289 context.visual.dashboard_items = safe_int(
289 context.visual.dashboard_items = safe_int(
290 rc_config.get('rhodecode_dashboard_items', 100))
290 rc_config.get('rhodecode_dashboard_items', 100))
291 context.visual.admin_grid_items = safe_int(
291 context.visual.admin_grid_items = safe_int(
292 rc_config.get('rhodecode_admin_grid_items', 100))
292 rc_config.get('rhodecode_admin_grid_items', 100))
293 context.visual.repository_fields = str2bool(
293 context.visual.repository_fields = str2bool(
294 rc_config.get('rhodecode_repository_fields'))
294 rc_config.get('rhodecode_repository_fields'))
295 context.visual.show_version = str2bool(
295 context.visual.show_version = str2bool(
296 rc_config.get('rhodecode_show_version'))
296 rc_config.get('rhodecode_show_version'))
297 context.visual.use_gravatar = str2bool(
297 context.visual.use_gravatar = str2bool(
298 rc_config.get('rhodecode_use_gravatar'))
298 rc_config.get('rhodecode_use_gravatar'))
299 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
299 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
300 context.visual.default_renderer = rc_config.get(
300 context.visual.default_renderer = rc_config.get(
301 'rhodecode_markup_renderer', 'rst')
301 'rhodecode_markup_renderer', 'rst')
302 context.visual.rhodecode_support_url = \
302 context.visual.rhodecode_support_url = \
303 rc_config.get('rhodecode_support_url') or url('rhodecode_support')
303 rc_config.get('rhodecode_support_url') or url('rhodecode_support')
304
304
305 context.pre_code = rc_config.get('rhodecode_pre_code')
305 context.pre_code = rc_config.get('rhodecode_pre_code')
306 context.post_code = rc_config.get('rhodecode_post_code')
306 context.post_code = rc_config.get('rhodecode_post_code')
307 context.rhodecode_name = rc_config.get('rhodecode_title')
307 context.rhodecode_name = rc_config.get('rhodecode_title')
308 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
308 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
309 # if we have specified default_encoding in the request, it has more
309 # if we have specified default_encoding in the request, it has more
310 # priority
310 # priority
311 if request.GET.get('default_encoding'):
311 if request.GET.get('default_encoding'):
312 context.default_encodings.insert(0, request.GET.get('default_encoding'))
312 context.default_encodings.insert(0, request.GET.get('default_encoding'))
313 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
313 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
314
314
315 # INI stored
315 # INI stored
316 context.labs_active = str2bool(
316 context.labs_active = str2bool(
317 config.get('labs_settings_active', 'false'))
317 config.get('labs_settings_active', 'false'))
318 context.visual.allow_repo_location_change = str2bool(
318 context.visual.allow_repo_location_change = str2bool(
319 config.get('allow_repo_location_change', True))
319 config.get('allow_repo_location_change', True))
320 context.visual.allow_custom_hooks_settings = str2bool(
320 context.visual.allow_custom_hooks_settings = str2bool(
321 config.get('allow_custom_hooks_settings', True))
321 config.get('allow_custom_hooks_settings', True))
322 context.debug_style = str2bool(config.get('debug_style', False))
322 context.debug_style = str2bool(config.get('debug_style', False))
323
323
324 context.rhodecode_instanceid = config.get('instance_id')
324 context.rhodecode_instanceid = config.get('instance_id')
325
325
326 # AppEnlight
326 # AppEnlight
327 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
327 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
328 context.appenlight_api_public_key = config.get(
328 context.appenlight_api_public_key = config.get(
329 'appenlight.api_public_key', '')
329 'appenlight.api_public_key', '')
330 context.appenlight_server_url = config.get('appenlight.server_url', '')
330 context.appenlight_server_url = config.get('appenlight.server_url', '')
331
331
332 # JS template context
332 # JS template context
333 context.template_context = {
333 context.template_context = {
334 'repo_name': None,
334 'repo_name': None,
335 'repo_type': None,
335 'repo_type': None,
336 'repo_landing_commit': None,
336 'repo_landing_commit': None,
337 'rhodecode_user': {
337 'rhodecode_user': {
338 'username': None,
338 'username': None,
339 'email': None,
339 'email': None,
340 'notification_status': False
340 'notification_status': False
341 },
341 },
342 'visual': {
342 'visual': {
343 'default_renderer': None
343 'default_renderer': None
344 },
344 },
345 'commit_data': {
345 'commit_data': {
346 'commit_id': None
346 'commit_id': None
347 },
347 },
348 'pull_request_data': {'pull_request_id': None},
348 'pull_request_data': {'pull_request_id': None},
349 'timeago': {
349 'timeago': {
350 'refresh_time': 120 * 1000,
350 'refresh_time': 120 * 1000,
351 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
351 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
352 },
352 },
353 'pylons_dispatch': {
353 'pylons_dispatch': {
354 # 'controller': request.environ['pylons.routes_dict']['controller'],
354 # 'controller': request.environ['pylons.routes_dict']['controller'],
355 # 'action': request.environ['pylons.routes_dict']['action'],
355 # 'action': request.environ['pylons.routes_dict']['action'],
356 },
356 },
357 'pyramid_dispatch': {
357 'pyramid_dispatch': {
358
358
359 },
359 },
360 'extra': {'plugins': {}}
360 'extra': {'plugins': {}}
361 }
361 }
362 # END CONFIG VARS
362 # END CONFIG VARS
363
363
364 # TODO: This dosn't work when called from pylons compatibility tween.
364 # TODO: This dosn't work when called from pylons compatibility tween.
365 # Fix this and remove it from base controller.
365 # Fix this and remove it from base controller.
366 # context.repo_name = get_repo_slug(request) # can be empty
366 # context.repo_name = get_repo_slug(request) # can be empty
367
367
368 diffmode = 'sideside'
368 diffmode = 'sideside'
369 if request.GET.get('diffmode'):
369 if request.GET.get('diffmode'):
370 if request.GET['diffmode'] == 'unified':
370 if request.GET['diffmode'] == 'unified':
371 diffmode = 'unified'
371 diffmode = 'unified'
372 elif request.session.get('diffmode'):
372 elif request.session.get('diffmode'):
373 diffmode = request.session['diffmode']
373 diffmode = request.session['diffmode']
374
374
375 context.diffmode = diffmode
375 context.diffmode = diffmode
376
376
377 if request.session.get('diffmode') != diffmode:
377 if request.session.get('diffmode') != diffmode:
378 request.session['diffmode'] = diffmode
378 request.session['diffmode'] = diffmode
379
379
380 context.csrf_token = auth.get_csrf_token()
380 context.csrf_token = auth.get_csrf_token()
381 context.backends = rhodecode.BACKENDS.keys()
381 context.backends = rhodecode.BACKENDS.keys()
382 context.backends.sort()
382 context.backends.sort()
383 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(
383 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(
384 context.rhodecode_user.user_id)
384 context.rhodecode_user.user_id)
385
385
386 context.pyramid_request = pyramid.threadlocal.get_current_request()
386 context.pyramid_request = pyramid.threadlocal.get_current_request()
387
387
388
388
389 def get_auth_user(environ):
389 def get_auth_user(environ):
390 ip_addr = get_ip_addr(environ)
390 ip_addr = get_ip_addr(environ)
391 # make sure that we update permissions each time we call controller
391 # make sure that we update permissions each time we call controller
392 _auth_token = (request.GET.get('auth_token', '') or
392 _auth_token = (request.GET.get('auth_token', '') or
393 request.GET.get('api_key', ''))
393 request.GET.get('api_key', ''))
394
394
395 if _auth_token:
395 if _auth_token:
396 # when using API_KEY we are sure user exists.
396 # when using API_KEY we assume user exists, and
397 # doesn't need auth based on cookies.
397 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
398 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
398 authenticated = False
399 authenticated = False
399 else:
400 else:
400 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
401 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
401 try:
402 try:
402 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
403 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
403 ip_addr=ip_addr)
404 ip_addr=ip_addr)
404 except UserCreationError as e:
405 except UserCreationError as e:
405 h.flash(e, 'error')
406 h.flash(e, 'error')
406 # container auth or other auth functions that create users
407 # container auth or other auth functions that create users
407 # on the fly can throw this exception signaling that there's
408 # on the fly can throw this exception signaling that there's
408 # issue with user creation, explanation should be provided
409 # issue with user creation, explanation should be provided
409 # in Exception itself. We then create a simple blank
410 # in Exception itself. We then create a simple blank
410 # AuthUser
411 # AuthUser
411 auth_user = AuthUser(ip_addr=ip_addr)
412 auth_user = AuthUser(ip_addr=ip_addr)
412
413
413 if password_changed(auth_user, session):
414 if password_changed(auth_user, session):
414 session.invalidate()
415 session.invalidate()
415 cookie_store = CookieStoreWrapper(
416 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
416 session.get('rhodecode_user'))
417 auth_user = AuthUser(ip_addr=ip_addr)
417 auth_user = AuthUser(ip_addr=ip_addr)
418
418
419 authenticated = cookie_store.get('is_authenticated')
419 authenticated = cookie_store.get('is_authenticated')
420
420
421 if not auth_user.is_authenticated and auth_user.is_user_object:
421 if not auth_user.is_authenticated and auth_user.is_user_object:
422 # user is not authenticated and not empty
422 # user is not authenticated and not empty
423 auth_user.set_authenticated(authenticated)
423 auth_user.set_authenticated(authenticated)
424
424
425 return auth_user
425 return auth_user
426
426
427
427
428 class BaseController(WSGIController):
428 class BaseController(WSGIController):
429
429
430 def __before__(self):
430 def __before__(self):
431 """
431 """
432 __before__ is called before controller methods and after __call__
432 __before__ is called before controller methods and after __call__
433 """
433 """
434 # on each call propagate settings calls into global settings.
434 # on each call propagate settings calls into global settings.
435 set_rhodecode_config(config)
435 set_rhodecode_config(config)
436 attach_context_attributes(c, request)
436 attach_context_attributes(c, request)
437
437
438 # TODO: Remove this when fixed in attach_context_attributes()
438 # TODO: Remove this when fixed in attach_context_attributes()
439 c.repo_name = get_repo_slug(request) # can be empty
439 c.repo_name = get_repo_slug(request) # can be empty
440
440
441 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
441 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
442 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
442 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
443 self.sa = meta.Session
443 self.sa = meta.Session
444 self.scm_model = ScmModel(self.sa)
444 self.scm_model = ScmModel(self.sa)
445
445
446 default_lang = c.language
446 default_lang = c.language
447 user_lang = c.language
447 user_lang = c.language
448 try:
448 try:
449 user_obj = self._rhodecode_user.get_instance()
449 user_obj = self._rhodecode_user.get_instance()
450 if user_obj:
450 if user_obj:
451 user_lang = user_obj.user_data.get('language')
451 user_lang = user_obj.user_data.get('language')
452 except Exception:
452 except Exception:
453 log.exception('Failed to fetch user language for user %s',
453 log.exception('Failed to fetch user language for user %s',
454 self._rhodecode_user)
454 self._rhodecode_user)
455
455
456 if user_lang and user_lang != default_lang:
456 if user_lang and user_lang != default_lang:
457 log.debug('set language to %s for user %s', user_lang,
457 log.debug('set language to %s for user %s', user_lang,
458 self._rhodecode_user)
458 self._rhodecode_user)
459 translation.set_lang(user_lang)
459 translation.set_lang(user_lang)
460
460
461 def _dispatch_redirect(self, with_url, environ, start_response):
461 def _dispatch_redirect(self, with_url, environ, start_response):
462 resp = HTTPFound(with_url)
462 resp = HTTPFound(with_url)
463 environ['SCRIPT_NAME'] = '' # handle prefix middleware
463 environ['SCRIPT_NAME'] = '' # handle prefix middleware
464 environ['PATH_INFO'] = with_url
464 environ['PATH_INFO'] = with_url
465 return resp(environ, start_response)
465 return resp(environ, start_response)
466
466
467 def __call__(self, environ, start_response):
467 def __call__(self, environ, start_response):
468 """Invoke the Controller"""
468 """Invoke the Controller"""
469 # WSGIController.__call__ dispatches to the Controller method
469 # WSGIController.__call__ dispatches to the Controller method
470 # the request is routed to. This routing information is
470 # the request is routed to. This routing information is
471 # available in environ['pylons.routes_dict']
471 # available in environ['pylons.routes_dict']
472 from rhodecode.lib import helpers as h
472 from rhodecode.lib import helpers as h
473
473
474 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
474 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
475 if environ.get('debugtoolbar.wants_pylons_context', False):
475 if environ.get('debugtoolbar.wants_pylons_context', False):
476 environ['debugtoolbar.pylons_context'] = c._current_obj()
476 environ['debugtoolbar.pylons_context'] = c._current_obj()
477
477
478 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
478 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
479 environ['pylons.routes_dict']['action']])
479 environ['pylons.routes_dict']['action']])
480
480
481 self.rc_config = SettingsModel().get_all_settings(cache=True)
481 self.rc_config = SettingsModel().get_all_settings(cache=True)
482 self.ip_addr = get_ip_addr(environ)
482 self.ip_addr = get_ip_addr(environ)
483
483
484 # The rhodecode auth user is looked up and passed through the
484 # The rhodecode auth user is looked up and passed through the
485 # environ by the pylons compatibility tween in pyramid.
485 # environ by the pylons compatibility tween in pyramid.
486 # So we can just grab it from there.
486 # So we can just grab it from there.
487 auth_user = environ['rc_auth_user']
487 auth_user = environ['rc_auth_user']
488
488
489 # set globals for auth user
489 # set globals for auth user
490 request.user = auth_user
490 request.user = auth_user
491 c.rhodecode_user = self._rhodecode_user = auth_user
491 c.rhodecode_user = self._rhodecode_user = auth_user
492
492
493 log.info('IP: %s User: %s accessed %s [%s]' % (
493 log.info('IP: %s User: %s accessed %s [%s]' % (
494 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
494 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
495 _route_name)
495 _route_name)
496 )
496 )
497
497
498 # TODO: Maybe this should be move to pyramid to cover all views.
498 # TODO: Maybe this should be move to pyramid to cover all views.
499 # check user attributes for password change flag
499 # check user attributes for password change flag
500 user_obj = auth_user.get_instance()
500 user_obj = auth_user.get_instance()
501 if user_obj and user_obj.user_data.get('force_password_change'):
501 if user_obj and user_obj.user_data.get('force_password_change'):
502 h.flash('You are required to change your password', 'warning',
502 h.flash('You are required to change your password', 'warning',
503 ignore_duplicate=True)
503 ignore_duplicate=True)
504
504
505 skip_user_check_urls = [
505 skip_user_check_urls = [
506 'error.document', 'login.logout', 'login.index',
506 'error.document', 'login.logout', 'login.index',
507 'admin/my_account.my_account_password',
507 'admin/my_account.my_account_password',
508 'admin/my_account.my_account_password_update'
508 'admin/my_account.my_account_password_update'
509 ]
509 ]
510 if _route_name not in skip_user_check_urls:
510 if _route_name not in skip_user_check_urls:
511 return self._dispatch_redirect(
511 return self._dispatch_redirect(
512 url('my_account_password'), environ, start_response)
512 url('my_account_password'), environ, start_response)
513
513
514 return WSGIController.__call__(self, environ, start_response)
514 return WSGIController.__call__(self, environ, start_response)
515
515
516
516
517 class BaseRepoController(BaseController):
517 class BaseRepoController(BaseController):
518 """
518 """
519 Base class for controllers responsible for loading all needed data for
519 Base class for controllers responsible for loading all needed data for
520 repository loaded items are
520 repository loaded items are
521
521
522 c.rhodecode_repo: instance of scm repository
522 c.rhodecode_repo: instance of scm repository
523 c.rhodecode_db_repo: instance of db
523 c.rhodecode_db_repo: instance of db
524 c.repository_requirements_missing: shows that repository specific data
524 c.repository_requirements_missing: shows that repository specific data
525 could not be displayed due to the missing requirements
525 could not be displayed due to the missing requirements
526 c.repository_pull_requests: show number of open pull requests
526 c.repository_pull_requests: show number of open pull requests
527 """
527 """
528
528
529 def __before__(self):
529 def __before__(self):
530 super(BaseRepoController, self).__before__()
530 super(BaseRepoController, self).__before__()
531 if c.repo_name: # extracted from routes
531 if c.repo_name: # extracted from routes
532 db_repo = Repository.get_by_repo_name(c.repo_name)
532 db_repo = Repository.get_by_repo_name(c.repo_name)
533 if not db_repo:
533 if not db_repo:
534 return
534 return
535
535
536 log.debug(
536 log.debug(
537 'Found repository in database %s with state `%s`',
537 'Found repository in database %s with state `%s`',
538 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
538 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
539 route = getattr(request.environ.get('routes.route'), 'name', '')
539 route = getattr(request.environ.get('routes.route'), 'name', '')
540
540
541 # allow to delete repos that are somehow damages in filesystem
541 # allow to delete repos that are somehow damages in filesystem
542 if route in ['delete_repo']:
542 if route in ['delete_repo']:
543 return
543 return
544
544
545 if db_repo.repo_state in [Repository.STATE_PENDING]:
545 if db_repo.repo_state in [Repository.STATE_PENDING]:
546 if route in ['repo_creating_home']:
546 if route in ['repo_creating_home']:
547 return
547 return
548 check_url = url('repo_creating_home', repo_name=c.repo_name)
548 check_url = url('repo_creating_home', repo_name=c.repo_name)
549 return redirect(check_url)
549 return redirect(check_url)
550
550
551 self.rhodecode_db_repo = db_repo
551 self.rhodecode_db_repo = db_repo
552
552
553 missing_requirements = False
553 missing_requirements = False
554 try:
554 try:
555 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
555 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
556 except RepositoryRequirementError as e:
556 except RepositoryRequirementError as e:
557 missing_requirements = True
557 missing_requirements = True
558 self._handle_missing_requirements(e)
558 self._handle_missing_requirements(e)
559
559
560 if self.rhodecode_repo is None and not missing_requirements:
560 if self.rhodecode_repo is None and not missing_requirements:
561 log.error('%s this repository is present in database but it '
561 log.error('%s this repository is present in database but it '
562 'cannot be created as an scm instance', c.repo_name)
562 'cannot be created as an scm instance', c.repo_name)
563
563
564 h.flash(_(
564 h.flash(_(
565 "The repository at %(repo_name)s cannot be located.") %
565 "The repository at %(repo_name)s cannot be located.") %
566 {'repo_name': c.repo_name},
566 {'repo_name': c.repo_name},
567 category='error', ignore_duplicate=True)
567 category='error', ignore_duplicate=True)
568 redirect(url('home'))
568 redirect(url('home'))
569
569
570 # update last change according to VCS data
570 # update last change according to VCS data
571 if not missing_requirements:
571 if not missing_requirements:
572 commit = db_repo.get_commit(
572 commit = db_repo.get_commit(
573 pre_load=["author", "date", "message", "parents"])
573 pre_load=["author", "date", "message", "parents"])
574 db_repo.update_commit_cache(commit)
574 db_repo.update_commit_cache(commit)
575
575
576 # Prepare context
576 # Prepare context
577 c.rhodecode_db_repo = db_repo
577 c.rhodecode_db_repo = db_repo
578 c.rhodecode_repo = self.rhodecode_repo
578 c.rhodecode_repo = self.rhodecode_repo
579 c.repository_requirements_missing = missing_requirements
579 c.repository_requirements_missing = missing_requirements
580
580
581 self._update_global_counters(self.scm_model, db_repo)
581 self._update_global_counters(self.scm_model, db_repo)
582
582
583 def _update_global_counters(self, scm_model, db_repo):
583 def _update_global_counters(self, scm_model, db_repo):
584 """
584 """
585 Base variables that are exposed to every page of repository
585 Base variables that are exposed to every page of repository
586 """
586 """
587 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
587 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
588
588
589 def _handle_missing_requirements(self, error):
589 def _handle_missing_requirements(self, error):
590 self.rhodecode_repo = None
590 self.rhodecode_repo = None
591 log.error(
591 log.error(
592 'Requirements are missing for repository %s: %s',
592 'Requirements are missing for repository %s: %s',
593 c.repo_name, error.message)
593 c.repo_name, error.message)
594
594
595 summary_url = url('summary_home', repo_name=c.repo_name)
595 summary_url = url('summary_home', repo_name=c.repo_name)
596 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
596 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
597 settings_update_url = url('repo', repo_name=c.repo_name)
597 settings_update_url = url('repo', repo_name=c.repo_name)
598 path = request.path
598 path = request.path
599 should_redirect = (
599 should_redirect = (
600 path not in (summary_url, settings_update_url)
600 path not in (summary_url, settings_update_url)
601 and '/settings' not in path or path == statistics_url
601 and '/settings' not in path or path == statistics_url
602 )
602 )
603 if should_redirect:
603 if should_redirect:
604 redirect(summary_url)
604 redirect(summary_url)
@@ -1,95 +1,97 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 import logging
22 import logging
23 import pylons
23 import pylons
24 import rhodecode
24 import rhodecode
25
25
26 from pylons.i18n.translation import _get_translator
26 from pylons.i18n.translation import _get_translator
27 from pylons.util import ContextObj
27 from pylons.util import ContextObj
28 from routes.util import URLGenerator
28 from routes.util import URLGenerator
29
29
30 from rhodecode.lib.base import attach_context_attributes, get_auth_user
30 from rhodecode.lib.base import attach_context_attributes, get_auth_user
31 from rhodecode.lib.middleware.vcs import (
31 from rhodecode.lib.middleware.vcs import (
32 detect_vcs_request, VCS_TYPE_KEY, VCS_TYPE_SKIP)
32 detect_vcs_request, VCS_TYPE_KEY, VCS_TYPE_SKIP)
33 from rhodecode.model import meta
33 from rhodecode.model import meta
34
34
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 def pylons_compatibility_tween_factory(handler, registry):
39 def pylons_compatibility_tween_factory(handler, registry):
40
40
41 def pylons_compatibility_tween(request):
41 def pylons_compatibility_tween(request):
42 """
42 """
43 While migrating from pylons to pyramid we need to call some pylons code
43 While migrating from pylons to pyramid we need to call some pylons code
44 from pyramid. For example while rendering an old template that uses the
44 from pyramid. For example while rendering an old template that uses the
45 'c' or 'h' objects. This tween sets up the needed pylons globals.
45 'c' or 'h' objects. This tween sets up the needed pylons globals.
46 """
46 """
47 config = rhodecode.CONFIG
47 config = rhodecode.CONFIG
48 environ = request.environ
48 environ = request.environ
49 session = request.session
49 session = request.session
50
50
51 vcs_handler = detect_vcs_request(
51 vcs_handler = detect_vcs_request(
52 request.environ, request.registry.settings.get('vcs.backends'))
52 request.environ, request.registry.settings.get('vcs.backends'))
53
53
54 if vcs_handler:
54 if vcs_handler:
55 # save detected VCS type for later re-use
55 request.environ[VCS_TYPE_KEY] = vcs_handler.SCM
56 request.environ[VCS_TYPE_KEY] = vcs_handler.SCM
56 return handler(request)
57 return handler(request)
57
58
59 # mark that we didn't detect an VCS, and we can skip detection later on
58 request.environ[VCS_TYPE_KEY] = VCS_TYPE_SKIP
60 request.environ[VCS_TYPE_KEY] = VCS_TYPE_SKIP
59
61
60 # Setup pylons globals.
62 # Setup pylons globals.
61 pylons.config._push_object(config)
63 pylons.config._push_object(config)
62 pylons.request._push_object(request)
64 pylons.request._push_object(request)
63 pylons.session._push_object(session)
65 pylons.session._push_object(session)
64
66
65 session_key = (
67 session_key = (
66 config['pylons.environ_config'].get('session', 'beaker.session'))
68 config['pylons.environ_config'].get('session', 'beaker.session'))
67 environ[session_key] = session
69 environ[session_key] = session
68 pylons.url._push_object(URLGenerator(config['routes.map'],
70 pylons.url._push_object(URLGenerator(config['routes.map'],
69 environ))
71 environ))
70
72
71 # TODO: Maybe we should use the language from pyramid.
73 # TODO: Maybe we should use the language from pyramid.
72 translator = _get_translator(config.get('lang'))
74 translator = _get_translator(config.get('lang'))
73 pylons.translator._push_object(translator)
75 pylons.translator._push_object(translator)
74
76
75 # Get the rhodecode auth user object and make it available.
77 # Get the rhodecode auth user object and make it available.
76 auth_user = get_auth_user(environ)
78 auth_user = get_auth_user(environ)
77 request.user = auth_user
79 request.user = auth_user
78 environ['rc_auth_user'] = auth_user
80 environ['rc_auth_user'] = auth_user
79
81
80 # Setup the pylons context object ('c')
82 # Setup the pylons context object ('c')
81 context = ContextObj()
83 context = ContextObj()
82 context.rhodecode_user = auth_user
84 context.rhodecode_user = auth_user
83 attach_context_attributes(context, request)
85 attach_context_attributes(context, request)
84 pylons.tmpl_context._push_object(context)
86 pylons.tmpl_context._push_object(context)
85 return handler(request)
87 return handler(request)
86
88
87 return pylons_compatibility_tween
89 return pylons_compatibility_tween
88
90
89
91
90 def includeme(config):
92 def includeme(config):
91 config.add_subscriber('rhodecode.subscribers.add_renderer_globals',
93 config.add_subscriber('rhodecode.subscribers.add_renderer_globals',
92 'pyramid.events.BeforeRender')
94 'pyramid.events.BeforeRender')
93 config.add_subscriber('rhodecode.subscribers.add_localizer',
95 config.add_subscriber('rhodecode.subscribers.add_localizer',
94 'pyramid.events.NewRequest')
96 'pyramid.events.NewRequest')
95 config.add_tween('rhodecode.tweens.pylons_compatibility_tween_factory')
97 config.add_tween('rhodecode.tweens.pylons_compatibility_tween_factory')
General Comments 0
You need to be logged in to leave comments. Login now