##// END OF EJS Templates
core: fixed cython compat inspect that caused some API calls to not work correctly.
marcink -
r4184:ac8c6020 stable
parent child Browse files
Show More
@@ -1,554 +1,555 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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
22 import itertools
21 import itertools
23 import logging
22 import logging
24 import sys
23 import sys
25 import types
24 import types
26 import fnmatch
25 import fnmatch
27
26
28 import decorator
27 import decorator
29 import venusian
28 import venusian
30 from collections import OrderedDict
29 from collections import OrderedDict
31
30
32 from pyramid.exceptions import ConfigurationError
31 from pyramid.exceptions import ConfigurationError
33 from pyramid.renderers import render
32 from pyramid.renderers import render
34 from pyramid.response import Response
33 from pyramid.response import Response
35 from pyramid.httpexceptions import HTTPNotFound
34 from pyramid.httpexceptions import HTTPNotFound
36
35
37 from rhodecode.api.exc import (
36 from rhodecode.api.exc import (
38 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
37 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
39 from rhodecode.apps._base import TemplateArgs
38 from rhodecode.apps._base import TemplateArgs
40 from rhodecode.lib.auth import AuthUser
39 from rhodecode.lib.auth import AuthUser
41 from rhodecode.lib.base import get_ip_addr, attach_context_attributes
40 from rhodecode.lib.base import get_ip_addr, attach_context_attributes
42 from rhodecode.lib.exc_tracking import store_exception
41 from rhodecode.lib.exc_tracking import store_exception
43 from rhodecode.lib.ext_json import json
42 from rhodecode.lib.ext_json import json
44 from rhodecode.lib.utils2 import safe_str
43 from rhodecode.lib.utils2 import safe_str
45 from rhodecode.lib.plugins.utils import get_plugin_settings
44 from rhodecode.lib.plugins.utils import get_plugin_settings
46 from rhodecode.model.db import User, UserApiKeys
45 from rhodecode.model.db import User, UserApiKeys
47
46
48 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
49
48
50 DEFAULT_RENDERER = 'jsonrpc_renderer'
49 DEFAULT_RENDERER = 'jsonrpc_renderer'
51 DEFAULT_URL = '/_admin/apiv2'
50 DEFAULT_URL = '/_admin/apiv2'
52
51
53
52
54 def find_methods(jsonrpc_methods, pattern):
53 def find_methods(jsonrpc_methods, pattern):
55 matches = OrderedDict()
54 matches = OrderedDict()
56 if not isinstance(pattern, (list, tuple)):
55 if not isinstance(pattern, (list, tuple)):
57 pattern = [pattern]
56 pattern = [pattern]
58
57
59 for single_pattern in pattern:
58 for single_pattern in pattern:
60 for method_name, method in jsonrpc_methods.items():
59 for method_name, method in jsonrpc_methods.items():
61 if fnmatch.fnmatch(method_name, single_pattern):
60 if fnmatch.fnmatch(method_name, single_pattern):
62 matches[method_name] = method
61 matches[method_name] = method
63 return matches
62 return matches
64
63
65
64
66 class ExtJsonRenderer(object):
65 class ExtJsonRenderer(object):
67 """
66 """
68 Custom renderer that mkaes use of our ext_json lib
67 Custom renderer that mkaes use of our ext_json lib
69
68
70 """
69 """
71
70
72 def __init__(self, serializer=json.dumps, **kw):
71 def __init__(self, serializer=json.dumps, **kw):
73 """ Any keyword arguments will be passed to the ``serializer``
72 """ Any keyword arguments will be passed to the ``serializer``
74 function."""
73 function."""
75 self.serializer = serializer
74 self.serializer = serializer
76 self.kw = kw
75 self.kw = kw
77
76
78 def __call__(self, info):
77 def __call__(self, info):
79 """ Returns a plain JSON-encoded string with content-type
78 """ Returns a plain JSON-encoded string with content-type
80 ``application/json``. The content-type may be overridden by
79 ``application/json``. The content-type may be overridden by
81 setting ``request.response.content_type``."""
80 setting ``request.response.content_type``."""
82
81
83 def _render(value, system):
82 def _render(value, system):
84 request = system.get('request')
83 request = system.get('request')
85 if request is not None:
84 if request is not None:
86 response = request.response
85 response = request.response
87 ct = response.content_type
86 ct = response.content_type
88 if ct == response.default_content_type:
87 if ct == response.default_content_type:
89 response.content_type = 'application/json'
88 response.content_type = 'application/json'
90
89
91 return self.serializer(value, **self.kw)
90 return self.serializer(value, **self.kw)
92
91
93 return _render
92 return _render
94
93
95
94
96 def jsonrpc_response(request, result):
95 def jsonrpc_response(request, result):
97 rpc_id = getattr(request, 'rpc_id', None)
96 rpc_id = getattr(request, 'rpc_id', None)
98 response = request.response
97 response = request.response
99
98
100 # store content_type before render is called
99 # store content_type before render is called
101 ct = response.content_type
100 ct = response.content_type
102
101
103 ret_value = ''
102 ret_value = ''
104 if rpc_id:
103 if rpc_id:
105 ret_value = {
104 ret_value = {
106 'id': rpc_id,
105 'id': rpc_id,
107 'result': result,
106 'result': result,
108 'error': None,
107 'error': None,
109 }
108 }
110
109
111 # fetch deprecation warnings, and store it inside results
110 # fetch deprecation warnings, and store it inside results
112 deprecation = getattr(request, 'rpc_deprecation', None)
111 deprecation = getattr(request, 'rpc_deprecation', None)
113 if deprecation:
112 if deprecation:
114 ret_value['DEPRECATION_WARNING'] = deprecation
113 ret_value['DEPRECATION_WARNING'] = deprecation
115
114
116 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
115 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
117 response.body = safe_str(raw_body, response.charset)
116 response.body = safe_str(raw_body, response.charset)
118
117
119 if ct == response.default_content_type:
118 if ct == response.default_content_type:
120 response.content_type = 'application/json'
119 response.content_type = 'application/json'
121
120
122 return response
121 return response
123
122
124
123
125 def jsonrpc_error(request, message, retid=None, code=None, headers=None):
124 def jsonrpc_error(request, message, retid=None, code=None, headers=None):
126 """
125 """
127 Generate a Response object with a JSON-RPC error body
126 Generate a Response object with a JSON-RPC error body
128
127
129 :param code:
128 :param code:
130 :param retid:
129 :param retid:
131 :param message:
130 :param message:
132 """
131 """
133 err_dict = {'id': retid, 'result': None, 'error': message}
132 err_dict = {'id': retid, 'result': None, 'error': message}
134 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
133 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
135
134
136 return Response(
135 return Response(
137 body=body,
136 body=body,
138 status=code,
137 status=code,
139 content_type='application/json',
138 content_type='application/json',
140 headerlist=headers
139 headerlist=headers
141 )
140 )
142
141
143
142
144 def exception_view(exc, request):
143 def exception_view(exc, request):
145 rpc_id = getattr(request, 'rpc_id', None)
144 rpc_id = getattr(request, 'rpc_id', None)
146
145
147 if isinstance(exc, JSONRPCError):
146 if isinstance(exc, JSONRPCError):
148 fault_message = safe_str(exc.message)
147 fault_message = safe_str(exc.message)
149 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
148 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
150 elif isinstance(exc, JSONRPCValidationError):
149 elif isinstance(exc, JSONRPCValidationError):
151 colander_exc = exc.colander_exception
150 colander_exc = exc.colander_exception
152 # TODO(marcink): think maybe of nicer way to serialize errors ?
151 # TODO(marcink): think maybe of nicer way to serialize errors ?
153 fault_message = colander_exc.asdict()
152 fault_message = colander_exc.asdict()
154 log.debug('json-rpc colander error rpc_id:%s "%s"', rpc_id, fault_message)
153 log.debug('json-rpc colander error rpc_id:%s "%s"', rpc_id, fault_message)
155 elif isinstance(exc, JSONRPCForbidden):
154 elif isinstance(exc, JSONRPCForbidden):
156 fault_message = 'Access was denied to this resource.'
155 fault_message = 'Access was denied to this resource.'
157 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
156 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
158 elif isinstance(exc, HTTPNotFound):
157 elif isinstance(exc, HTTPNotFound):
159 method = request.rpc_method
158 method = request.rpc_method
160 log.debug('json-rpc method `%s` not found in list of '
159 log.debug('json-rpc method `%s` not found in list of '
161 'api calls: %s, rpc_id:%s',
160 'api calls: %s, rpc_id:%s',
162 method, request.registry.jsonrpc_methods.keys(), rpc_id)
161 method, request.registry.jsonrpc_methods.keys(), rpc_id)
163
162
164 similar = 'none'
163 similar = 'none'
165 try:
164 try:
166 similar_paterns = ['*{}*'.format(x) for x in method.split('_')]
165 similar_paterns = ['*{}*'.format(x) for x in method.split('_')]
167 similar_found = find_methods(
166 similar_found = find_methods(
168 request.registry.jsonrpc_methods, similar_paterns)
167 request.registry.jsonrpc_methods, similar_paterns)
169 similar = ', '.join(similar_found.keys()) or similar
168 similar = ', '.join(similar_found.keys()) or similar
170 except Exception:
169 except Exception:
171 # make the whole above block safe
170 # make the whole above block safe
172 pass
171 pass
173
172
174 fault_message = "No such method: {}. Similar methods: {}".format(
173 fault_message = "No such method: {}. Similar methods: {}".format(
175 method, similar)
174 method, similar)
176 else:
175 else:
177 fault_message = 'undefined error'
176 fault_message = 'undefined error'
178 exc_info = exc.exc_info()
177 exc_info = exc.exc_info()
179 store_exception(id(exc_info), exc_info, prefix='rhodecode-api')
178 store_exception(id(exc_info), exc_info, prefix='rhodecode-api')
180
179
181 return jsonrpc_error(request, fault_message, rpc_id)
180 return jsonrpc_error(request, fault_message, rpc_id)
182
181
183
182
184 def request_view(request):
183 def request_view(request):
185 """
184 """
186 Main request handling method. It handles all logic to call a specific
185 Main request handling method. It handles all logic to call a specific
187 exposed method
186 exposed method
188 """
187 """
188 # cython compatible inspect
189 from rhodecode.config.patches import inspect_getargspec
190 inspect = inspect_getargspec()
189
191
190 # check if we can find this session using api_key, get_by_auth_token
192 # check if we can find this session using api_key, get_by_auth_token
191 # search not expired tokens only
193 # search not expired tokens only
192
193 try:
194 try:
194 api_user = User.get_by_auth_token(request.rpc_api_key)
195 api_user = User.get_by_auth_token(request.rpc_api_key)
195
196
196 if api_user is None:
197 if api_user is None:
197 return jsonrpc_error(
198 return jsonrpc_error(
198 request, retid=request.rpc_id, message='Invalid API KEY')
199 request, retid=request.rpc_id, message='Invalid API KEY')
199
200
200 if not api_user.active:
201 if not api_user.active:
201 return jsonrpc_error(
202 return jsonrpc_error(
202 request, retid=request.rpc_id,
203 request, retid=request.rpc_id,
203 message='Request from this user not allowed')
204 message='Request from this user not allowed')
204
205
205 # check if we are allowed to use this IP
206 # check if we are allowed to use this IP
206 auth_u = AuthUser(
207 auth_u = AuthUser(
207 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
208 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
208 if not auth_u.ip_allowed:
209 if not auth_u.ip_allowed:
209 return jsonrpc_error(
210 return jsonrpc_error(
210 request, retid=request.rpc_id,
211 request, retid=request.rpc_id,
211 message='Request from IP:%s not allowed' % (
212 message='Request from IP:%s not allowed' % (
212 request.rpc_ip_addr,))
213 request.rpc_ip_addr,))
213 else:
214 else:
214 log.info('Access for IP:%s allowed', request.rpc_ip_addr)
215 log.info('Access for IP:%s allowed', request.rpc_ip_addr)
215
216
216 # register our auth-user
217 # register our auth-user
217 request.rpc_user = auth_u
218 request.rpc_user = auth_u
218 request.environ['rc_auth_user_id'] = auth_u.user_id
219 request.environ['rc_auth_user_id'] = auth_u.user_id
219
220
220 # now check if token is valid for API
221 # now check if token is valid for API
221 auth_token = request.rpc_api_key
222 auth_token = request.rpc_api_key
222 token_match = api_user.authenticate_by_token(
223 token_match = api_user.authenticate_by_token(
223 auth_token, roles=[UserApiKeys.ROLE_API])
224 auth_token, roles=[UserApiKeys.ROLE_API])
224 invalid_token = not token_match
225 invalid_token = not token_match
225
226
226 log.debug('Checking if API KEY is valid with proper role')
227 log.debug('Checking if API KEY is valid with proper role')
227 if invalid_token:
228 if invalid_token:
228 return jsonrpc_error(
229 return jsonrpc_error(
229 request, retid=request.rpc_id,
230 request, retid=request.rpc_id,
230 message='API KEY invalid or, has bad role for an API call')
231 message='API KEY invalid or, has bad role for an API call')
231
232
232 except Exception:
233 except Exception:
233 log.exception('Error on API AUTH')
234 log.exception('Error on API AUTH')
234 return jsonrpc_error(
235 return jsonrpc_error(
235 request, retid=request.rpc_id, message='Invalid API KEY')
236 request, retid=request.rpc_id, message='Invalid API KEY')
236
237
237 method = request.rpc_method
238 method = request.rpc_method
238 func = request.registry.jsonrpc_methods[method]
239 func = request.registry.jsonrpc_methods[method]
239
240
240 # now that we have a method, add request._req_params to
241 # now that we have a method, add request._req_params to
241 # self.kargs and dispatch control to WGIController
242 # self.kargs and dispatch control to WGIController
242 argspec = inspect.getargspec(func)
243 argspec = inspect.getargspec(func)
243 arglist = argspec[0]
244 arglist = argspec[0]
244 defaults = map(type, argspec[3] or [])
245 defaults = map(type, argspec[3] or [])
245 default_empty = types.NotImplementedType
246 default_empty = types.NotImplementedType
246
247
247 # kw arguments required by this method
248 # kw arguments required by this method
248 func_kwargs = dict(itertools.izip_longest(
249 func_kwargs = dict(itertools.izip_longest(
249 reversed(arglist), reversed(defaults), fillvalue=default_empty))
250 reversed(arglist), reversed(defaults), fillvalue=default_empty))
250
251
251 # This attribute will need to be first param of a method that uses
252 # This attribute will need to be first param of a method that uses
252 # api_key, which is translated to instance of user at that name
253 # api_key, which is translated to instance of user at that name
253 user_var = 'apiuser'
254 user_var = 'apiuser'
254 request_var = 'request'
255 request_var = 'request'
255
256
256 for arg in [user_var, request_var]:
257 for arg in [user_var, request_var]:
257 if arg not in arglist:
258 if arg not in arglist:
258 return jsonrpc_error(
259 return jsonrpc_error(
259 request,
260 request,
260 retid=request.rpc_id,
261 retid=request.rpc_id,
261 message='This method [%s] does not support '
262 message='This method [%s] does not support '
262 'required parameter `%s`' % (func.__name__, arg))
263 'required parameter `%s`' % (func.__name__, arg))
263
264
264 # get our arglist and check if we provided them as args
265 # get our arglist and check if we provided them as args
265 for arg, default in func_kwargs.items():
266 for arg, default in func_kwargs.items():
266 if arg in [user_var, request_var]:
267 if arg in [user_var, request_var]:
267 # user_var and request_var are pre-hardcoded parameters and we
268 # user_var and request_var are pre-hardcoded parameters and we
268 # don't need to do any translation
269 # don't need to do any translation
269 continue
270 continue
270
271
271 # skip the required param check if it's default value is
272 # skip the required param check if it's default value is
272 # NotImplementedType (default_empty)
273 # NotImplementedType (default_empty)
273 if default == default_empty and arg not in request.rpc_params:
274 if default == default_empty and arg not in request.rpc_params:
274 return jsonrpc_error(
275 return jsonrpc_error(
275 request,
276 request,
276 retid=request.rpc_id,
277 retid=request.rpc_id,
277 message=('Missing non optional `%s` arg in JSON DATA' % arg)
278 message=('Missing non optional `%s` arg in JSON DATA' % arg)
278 )
279 )
279
280
280 # sanitize extra passed arguments
281 # sanitize extra passed arguments
281 for k in request.rpc_params.keys()[:]:
282 for k in request.rpc_params.keys()[:]:
282 if k not in func_kwargs:
283 if k not in func_kwargs:
283 del request.rpc_params[k]
284 del request.rpc_params[k]
284
285
285 call_params = request.rpc_params
286 call_params = request.rpc_params
286 call_params.update({
287 call_params.update({
287 'request': request,
288 'request': request,
288 'apiuser': auth_u
289 'apiuser': auth_u
289 })
290 })
290
291
291 # register some common functions for usage
292 # register some common functions for usage
292 attach_context_attributes(TemplateArgs(), request, request.rpc_user.user_id)
293 attach_context_attributes(TemplateArgs(), request, request.rpc_user.user_id)
293
294
294 try:
295 try:
295 ret_value = func(**call_params)
296 ret_value = func(**call_params)
296 return jsonrpc_response(request, ret_value)
297 return jsonrpc_response(request, ret_value)
297 except JSONRPCBaseError:
298 except JSONRPCBaseError:
298 raise
299 raise
299 except Exception:
300 except Exception:
300 log.exception('Unhandled exception occurred on api call: %s', func)
301 log.exception('Unhandled exception occurred on api call: %s', func)
301 exc_info = sys.exc_info()
302 exc_info = sys.exc_info()
302 exc_id, exc_type_name = store_exception(
303 exc_id, exc_type_name = store_exception(
303 id(exc_info), exc_info, prefix='rhodecode-api')
304 id(exc_info), exc_info, prefix='rhodecode-api')
304 error_headers = [('RhodeCode-Exception-Id', str(exc_id)),
305 error_headers = [('RhodeCode-Exception-Id', str(exc_id)),
305 ('RhodeCode-Exception-Type', str(exc_type_name))]
306 ('RhodeCode-Exception-Type', str(exc_type_name))]
306 return jsonrpc_error(
307 return jsonrpc_error(
307 request, retid=request.rpc_id, message='Internal server error',
308 request, retid=request.rpc_id, message='Internal server error',
308 headers=error_headers)
309 headers=error_headers)
309
310
310
311
311 def setup_request(request):
312 def setup_request(request):
312 """
313 """
313 Parse a JSON-RPC request body. It's used inside the predicates method
314 Parse a JSON-RPC request body. It's used inside the predicates method
314 to validate and bootstrap requests for usage in rpc calls.
315 to validate and bootstrap requests for usage in rpc calls.
315
316
316 We need to raise JSONRPCError here if we want to return some errors back to
317 We need to raise JSONRPCError here if we want to return some errors back to
317 user.
318 user.
318 """
319 """
319
320
320 log.debug('Executing setup request: %r', request)
321 log.debug('Executing setup request: %r', request)
321 request.rpc_ip_addr = get_ip_addr(request.environ)
322 request.rpc_ip_addr = get_ip_addr(request.environ)
322 # TODO(marcink): deprecate GET at some point
323 # TODO(marcink): deprecate GET at some point
323 if request.method not in ['POST', 'GET']:
324 if request.method not in ['POST', 'GET']:
324 log.debug('unsupported request method "%s"', request.method)
325 log.debug('unsupported request method "%s"', request.method)
325 raise JSONRPCError(
326 raise JSONRPCError(
326 'unsupported request method "%s". Please use POST' % request.method)
327 'unsupported request method "%s". Please use POST' % request.method)
327
328
328 if 'CONTENT_LENGTH' not in request.environ:
329 if 'CONTENT_LENGTH' not in request.environ:
329 log.debug("No Content-Length")
330 log.debug("No Content-Length")
330 raise JSONRPCError("Empty body, No Content-Length in request")
331 raise JSONRPCError("Empty body, No Content-Length in request")
331
332
332 else:
333 else:
333 length = request.environ['CONTENT_LENGTH']
334 length = request.environ['CONTENT_LENGTH']
334 log.debug('Content-Length: %s', length)
335 log.debug('Content-Length: %s', length)
335
336
336 if length == 0:
337 if length == 0:
337 log.debug("Content-Length is 0")
338 log.debug("Content-Length is 0")
338 raise JSONRPCError("Content-Length is 0")
339 raise JSONRPCError("Content-Length is 0")
339
340
340 raw_body = request.body
341 raw_body = request.body
341 log.debug("Loading JSON body now")
342 log.debug("Loading JSON body now")
342 try:
343 try:
343 json_body = json.loads(raw_body)
344 json_body = json.loads(raw_body)
344 except ValueError as e:
345 except ValueError as e:
345 # catch JSON errors Here
346 # catch JSON errors Here
346 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
347 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
347
348
348 request.rpc_id = json_body.get('id')
349 request.rpc_id = json_body.get('id')
349 request.rpc_method = json_body.get('method')
350 request.rpc_method = json_body.get('method')
350
351
351 # check required base parameters
352 # check required base parameters
352 try:
353 try:
353 api_key = json_body.get('api_key')
354 api_key = json_body.get('api_key')
354 if not api_key:
355 if not api_key:
355 api_key = json_body.get('auth_token')
356 api_key = json_body.get('auth_token')
356
357
357 if not api_key:
358 if not api_key:
358 raise KeyError('api_key or auth_token')
359 raise KeyError('api_key or auth_token')
359
360
360 # TODO(marcink): support passing in token in request header
361 # TODO(marcink): support passing in token in request header
361
362
362 request.rpc_api_key = api_key
363 request.rpc_api_key = api_key
363 request.rpc_id = json_body['id']
364 request.rpc_id = json_body['id']
364 request.rpc_method = json_body['method']
365 request.rpc_method = json_body['method']
365 request.rpc_params = json_body['args'] \
366 request.rpc_params = json_body['args'] \
366 if isinstance(json_body['args'], dict) else {}
367 if isinstance(json_body['args'], dict) else {}
367
368
368 log.debug('method: %s, params: %.10240r', request.rpc_method, request.rpc_params)
369 log.debug('method: %s, params: %.10240r', request.rpc_method, request.rpc_params)
369 except KeyError as e:
370 except KeyError as e:
370 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
371 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
371
372
372 log.debug('setup complete, now handling method:%s rpcid:%s',
373 log.debug('setup complete, now handling method:%s rpcid:%s',
373 request.rpc_method, request.rpc_id, )
374 request.rpc_method, request.rpc_id, )
374
375
375
376
376 class RoutePredicate(object):
377 class RoutePredicate(object):
377 def __init__(self, val, config):
378 def __init__(self, val, config):
378 self.val = val
379 self.val = val
379
380
380 def text(self):
381 def text(self):
381 return 'jsonrpc route = %s' % self.val
382 return 'jsonrpc route = %s' % self.val
382
383
383 phash = text
384 phash = text
384
385
385 def __call__(self, info, request):
386 def __call__(self, info, request):
386 if self.val:
387 if self.val:
387 # potentially setup and bootstrap our call
388 # potentially setup and bootstrap our call
388 setup_request(request)
389 setup_request(request)
389
390
390 # Always return True so that even if it isn't a valid RPC it
391 # Always return True so that even if it isn't a valid RPC it
391 # will fall through to the underlaying handlers like notfound_view
392 # will fall through to the underlaying handlers like notfound_view
392 return True
393 return True
393
394
394
395
395 class NotFoundPredicate(object):
396 class NotFoundPredicate(object):
396 def __init__(self, val, config):
397 def __init__(self, val, config):
397 self.val = val
398 self.val = val
398 self.methods = config.registry.jsonrpc_methods
399 self.methods = config.registry.jsonrpc_methods
399
400
400 def text(self):
401 def text(self):
401 return 'jsonrpc method not found = {}.'.format(self.val)
402 return 'jsonrpc method not found = {}.'.format(self.val)
402
403
403 phash = text
404 phash = text
404
405
405 def __call__(self, info, request):
406 def __call__(self, info, request):
406 return hasattr(request, 'rpc_method')
407 return hasattr(request, 'rpc_method')
407
408
408
409
409 class MethodPredicate(object):
410 class MethodPredicate(object):
410 def __init__(self, val, config):
411 def __init__(self, val, config):
411 self.method = val
412 self.method = val
412
413
413 def text(self):
414 def text(self):
414 return 'jsonrpc method = %s' % self.method
415 return 'jsonrpc method = %s' % self.method
415
416
416 phash = text
417 phash = text
417
418
418 def __call__(self, context, request):
419 def __call__(self, context, request):
419 # we need to explicitly return False here, so pyramid doesn't try to
420 # we need to explicitly return False here, so pyramid doesn't try to
420 # execute our view directly. We need our main handler to execute things
421 # execute our view directly. We need our main handler to execute things
421 return getattr(request, 'rpc_method') == self.method
422 return getattr(request, 'rpc_method') == self.method
422
423
423
424
424 def add_jsonrpc_method(config, view, **kwargs):
425 def add_jsonrpc_method(config, view, **kwargs):
425 # pop the method name
426 # pop the method name
426 method = kwargs.pop('method', None)
427 method = kwargs.pop('method', None)
427
428
428 if method is None:
429 if method is None:
429 raise ConfigurationError(
430 raise ConfigurationError(
430 'Cannot register a JSON-RPC method without specifying the "method"')
431 'Cannot register a JSON-RPC method without specifying the "method"')
431
432
432 # we define custom predicate, to enable to detect conflicting methods,
433 # we define custom predicate, to enable to detect conflicting methods,
433 # those predicates are kind of "translation" from the decorator variables
434 # those predicates are kind of "translation" from the decorator variables
434 # to internal predicates names
435 # to internal predicates names
435
436
436 kwargs['jsonrpc_method'] = method
437 kwargs['jsonrpc_method'] = method
437
438
438 # register our view into global view store for validation
439 # register our view into global view store for validation
439 config.registry.jsonrpc_methods[method] = view
440 config.registry.jsonrpc_methods[method] = view
440
441
441 # we're using our main request_view handler, here, so each method
442 # we're using our main request_view handler, here, so each method
442 # has a unified handler for itself
443 # has a unified handler for itself
443 config.add_view(request_view, route_name='apiv2', **kwargs)
444 config.add_view(request_view, route_name='apiv2', **kwargs)
444
445
445
446
446 class jsonrpc_method(object):
447 class jsonrpc_method(object):
447 """
448 """
448 decorator that works similar to @add_view_config decorator,
449 decorator that works similar to @add_view_config decorator,
449 but tailored for our JSON RPC
450 but tailored for our JSON RPC
450 """
451 """
451
452
452 venusian = venusian # for testing injection
453 venusian = venusian # for testing injection
453
454
454 def __init__(self, method=None, **kwargs):
455 def __init__(self, method=None, **kwargs):
455 self.method = method
456 self.method = method
456 self.kwargs = kwargs
457 self.kwargs = kwargs
457
458
458 def __call__(self, wrapped):
459 def __call__(self, wrapped):
459 kwargs = self.kwargs.copy()
460 kwargs = self.kwargs.copy()
460 kwargs['method'] = self.method or wrapped.__name__
461 kwargs['method'] = self.method or wrapped.__name__
461 depth = kwargs.pop('_depth', 0)
462 depth = kwargs.pop('_depth', 0)
462
463
463 def callback(context, name, ob):
464 def callback(context, name, ob):
464 config = context.config.with_package(info.module)
465 config = context.config.with_package(info.module)
465 config.add_jsonrpc_method(view=ob, **kwargs)
466 config.add_jsonrpc_method(view=ob, **kwargs)
466
467
467 info = venusian.attach(wrapped, callback, category='pyramid',
468 info = venusian.attach(wrapped, callback, category='pyramid',
468 depth=depth + 1)
469 depth=depth + 1)
469 if info.scope == 'class':
470 if info.scope == 'class':
470 # ensure that attr is set if decorating a class method
471 # ensure that attr is set if decorating a class method
471 kwargs.setdefault('attr', wrapped.__name__)
472 kwargs.setdefault('attr', wrapped.__name__)
472
473
473 kwargs['_info'] = info.codeinfo # fbo action_method
474 kwargs['_info'] = info.codeinfo # fbo action_method
474 return wrapped
475 return wrapped
475
476
476
477
477 class jsonrpc_deprecated_method(object):
478 class jsonrpc_deprecated_method(object):
478 """
479 """
479 Marks method as deprecated, adds log.warning, and inject special key to
480 Marks method as deprecated, adds log.warning, and inject special key to
480 the request variable to mark method as deprecated.
481 the request variable to mark method as deprecated.
481 Also injects special docstring that extract_docs will catch to mark
482 Also injects special docstring that extract_docs will catch to mark
482 method as deprecated.
483 method as deprecated.
483
484
484 :param use_method: specify which method should be used instead of
485 :param use_method: specify which method should be used instead of
485 the decorated one
486 the decorated one
486
487
487 Use like::
488 Use like::
488
489
489 @jsonrpc_method()
490 @jsonrpc_method()
490 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
491 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
491 def old_func(request, apiuser, arg1, arg2):
492 def old_func(request, apiuser, arg1, arg2):
492 ...
493 ...
493 """
494 """
494
495
495 def __init__(self, use_method, deprecated_at_version):
496 def __init__(self, use_method, deprecated_at_version):
496 self.use_method = use_method
497 self.use_method = use_method
497 self.deprecated_at_version = deprecated_at_version
498 self.deprecated_at_version = deprecated_at_version
498 self.deprecated_msg = ''
499 self.deprecated_msg = ''
499
500
500 def __call__(self, func):
501 def __call__(self, func):
501 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
502 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
502 method=self.use_method)
503 method=self.use_method)
503
504
504 docstring = """\n
505 docstring = """\n
505 .. deprecated:: {version}
506 .. deprecated:: {version}
506
507
507 {deprecation_message}
508 {deprecation_message}
508
509
509 {original_docstring}
510 {original_docstring}
510 """
511 """
511 func.__doc__ = docstring.format(
512 func.__doc__ = docstring.format(
512 version=self.deprecated_at_version,
513 version=self.deprecated_at_version,
513 deprecation_message=self.deprecated_msg,
514 deprecation_message=self.deprecated_msg,
514 original_docstring=func.__doc__)
515 original_docstring=func.__doc__)
515 return decorator.decorator(self.__wrapper, func)
516 return decorator.decorator(self.__wrapper, func)
516
517
517 def __wrapper(self, func, *fargs, **fkwargs):
518 def __wrapper(self, func, *fargs, **fkwargs):
518 log.warning('DEPRECATED API CALL on function %s, please '
519 log.warning('DEPRECATED API CALL on function %s, please '
519 'use `%s` instead', func, self.use_method)
520 'use `%s` instead', func, self.use_method)
520 # alter function docstring to mark as deprecated, this is picked up
521 # alter function docstring to mark as deprecated, this is picked up
521 # via fabric file that generates API DOC.
522 # via fabric file that generates API DOC.
522 result = func(*fargs, **fkwargs)
523 result = func(*fargs, **fkwargs)
523
524
524 request = fargs[0]
525 request = fargs[0]
525 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
526 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
526 return result
527 return result
527
528
528
529
529 def includeme(config):
530 def includeme(config):
530 plugin_module = 'rhodecode.api'
531 plugin_module = 'rhodecode.api'
531 plugin_settings = get_plugin_settings(
532 plugin_settings = get_plugin_settings(
532 plugin_module, config.registry.settings)
533 plugin_module, config.registry.settings)
533
534
534 if not hasattr(config.registry, 'jsonrpc_methods'):
535 if not hasattr(config.registry, 'jsonrpc_methods'):
535 config.registry.jsonrpc_methods = OrderedDict()
536 config.registry.jsonrpc_methods = OrderedDict()
536
537
537 # match filter by given method only
538 # match filter by given method only
538 config.add_view_predicate('jsonrpc_method', MethodPredicate)
539 config.add_view_predicate('jsonrpc_method', MethodPredicate)
539 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
540 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
540
541
541 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
542 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
542 serializer=json.dumps, indent=4))
543 serializer=json.dumps, indent=4))
543 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
544 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
544
545
545 config.add_route_predicate(
546 config.add_route_predicate(
546 'jsonrpc_call', RoutePredicate)
547 'jsonrpc_call', RoutePredicate)
547
548
548 config.add_route(
549 config.add_route(
549 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
550 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
550
551
551 config.scan(plugin_module, ignore='rhodecode.api.tests')
552 config.scan(plugin_module, ignore='rhodecode.api.tests')
552 # register some exception handling view
553 # register some exception handling view
553 config.add_view(exception_view, context=JSONRPCBaseError)
554 config.add_view(exception_view, context=JSONRPCBaseError)
554 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
555 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
@@ -1,419 +1,421 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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
22 import logging
21 import logging
23 import itertools
22 import itertools
24 import base64
23 import base64
25
24
26 from pyramid import compat
25 from pyramid import compat
27
26
28 from rhodecode.api import (
27 from rhodecode.api import (
29 jsonrpc_method, JSONRPCError, JSONRPCForbidden, find_methods)
28 jsonrpc_method, JSONRPCError, JSONRPCForbidden, find_methods)
30
29
31 from rhodecode.api.utils import (
30 from rhodecode.api.utils import (
32 Optional, OAttr, has_superadmin_permission, get_user_or_error)
31 Optional, OAttr, has_superadmin_permission, get_user_or_error)
33 from rhodecode.lib.utils import repo2db_mapper
32 from rhodecode.lib.utils import repo2db_mapper
34 from rhodecode.lib import system_info
33 from rhodecode.lib import system_info
35 from rhodecode.lib import user_sessions
34 from rhodecode.lib import user_sessions
36 from rhodecode.lib import exc_tracking
35 from rhodecode.lib import exc_tracking
37 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.utils2 import safe_int
37 from rhodecode.lib.utils2 import safe_int
39 from rhodecode.model.db import UserIpMap
38 from rhodecode.model.db import UserIpMap
40 from rhodecode.model.scm import ScmModel
39 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.settings import VcsSettingsModel
40 from rhodecode.model.settings import VcsSettingsModel
42 from rhodecode.apps.file_store import utils
41 from rhodecode.apps.file_store import utils
43 from rhodecode.apps.file_store.exceptions import FileNotAllowedException, \
42 from rhodecode.apps.file_store.exceptions import FileNotAllowedException, \
44 FileOverSizeException
43 FileOverSizeException
45
44
46 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
47
46
48
47
49 @jsonrpc_method()
48 @jsonrpc_method()
50 def get_server_info(request, apiuser):
49 def get_server_info(request, apiuser):
51 """
50 """
52 Returns the |RCE| server information.
51 Returns the |RCE| server information.
53
52
54 This includes the running version of |RCE| and all installed
53 This includes the running version of |RCE| and all installed
55 packages. This command takes the following options:
54 packages. This command takes the following options:
56
55
57 :param apiuser: This is filled automatically from the |authtoken|.
56 :param apiuser: This is filled automatically from the |authtoken|.
58 :type apiuser: AuthUser
57 :type apiuser: AuthUser
59
58
60 Example output:
59 Example output:
61
60
62 .. code-block:: bash
61 .. code-block:: bash
63
62
64 id : <id_given_in_input>
63 id : <id_given_in_input>
65 result : {
64 result : {
66 'modules': [<module name>,...]
65 'modules': [<module name>,...]
67 'py_version': <python version>,
66 'py_version': <python version>,
68 'platform': <platform type>,
67 'platform': <platform type>,
69 'rhodecode_version': <rhodecode version>
68 'rhodecode_version': <rhodecode version>
70 }
69 }
71 error : null
70 error : null
72 """
71 """
73
72
74 if not has_superadmin_permission(apiuser):
73 if not has_superadmin_permission(apiuser):
75 raise JSONRPCForbidden()
74 raise JSONRPCForbidden()
76
75
77 server_info = ScmModel().get_server_info(request.environ)
76 server_info = ScmModel().get_server_info(request.environ)
78 # rhodecode-index requires those
77 # rhodecode-index requires those
79
78
80 server_info['index_storage'] = server_info['search']['value']['location']
79 server_info['index_storage'] = server_info['search']['value']['location']
81 server_info['storage'] = server_info['storage']['value']['path']
80 server_info['storage'] = server_info['storage']['value']['path']
82
81
83 return server_info
82 return server_info
84
83
85
84
86 @jsonrpc_method()
85 @jsonrpc_method()
87 def get_repo_store(request, apiuser):
86 def get_repo_store(request, apiuser):
88 """
87 """
89 Returns the |RCE| repository storage information.
88 Returns the |RCE| repository storage information.
90
89
91 :param apiuser: This is filled automatically from the |authtoken|.
90 :param apiuser: This is filled automatically from the |authtoken|.
92 :type apiuser: AuthUser
91 :type apiuser: AuthUser
93
92
94 Example output:
93 Example output:
95
94
96 .. code-block:: bash
95 .. code-block:: bash
97
96
98 id : <id_given_in_input>
97 id : <id_given_in_input>
99 result : {
98 result : {
100 'modules': [<module name>,...]
99 'modules': [<module name>,...]
101 'py_version': <python version>,
100 'py_version': <python version>,
102 'platform': <platform type>,
101 'platform': <platform type>,
103 'rhodecode_version': <rhodecode version>
102 'rhodecode_version': <rhodecode version>
104 }
103 }
105 error : null
104 error : null
106 """
105 """
107
106
108 if not has_superadmin_permission(apiuser):
107 if not has_superadmin_permission(apiuser):
109 raise JSONRPCForbidden()
108 raise JSONRPCForbidden()
110
109
111 path = VcsSettingsModel().get_repos_location()
110 path = VcsSettingsModel().get_repos_location()
112 return {"path": path}
111 return {"path": path}
113
112
114
113
115 @jsonrpc_method()
114 @jsonrpc_method()
116 def get_ip(request, apiuser, userid=Optional(OAttr('apiuser'))):
115 def get_ip(request, apiuser, userid=Optional(OAttr('apiuser'))):
117 """
116 """
118 Displays the IP Address as seen from the |RCE| server.
117 Displays the IP Address as seen from the |RCE| server.
119
118
120 * This command displays the IP Address, as well as all the defined IP
119 * This command displays the IP Address, as well as all the defined IP
121 addresses for the specified user. If the ``userid`` is not set, the
120 addresses for the specified user. If the ``userid`` is not set, the
122 data returned is for the user calling the method.
121 data returned is for the user calling the method.
123
122
124 This command can only be run using an |authtoken| with admin rights to
123 This command can only be run using an |authtoken| with admin rights to
125 the specified repository.
124 the specified repository.
126
125
127 This command takes the following options:
126 This command takes the following options:
128
127
129 :param apiuser: This is filled automatically from |authtoken|.
128 :param apiuser: This is filled automatically from |authtoken|.
130 :type apiuser: AuthUser
129 :type apiuser: AuthUser
131 :param userid: Sets the userid for which associated IP Address data
130 :param userid: Sets the userid for which associated IP Address data
132 is returned.
131 is returned.
133 :type userid: Optional(str or int)
132 :type userid: Optional(str or int)
134
133
135 Example output:
134 Example output:
136
135
137 .. code-block:: bash
136 .. code-block:: bash
138
137
139 id : <id_given_in_input>
138 id : <id_given_in_input>
140 result : {
139 result : {
141 "server_ip_addr": "<ip_from_clien>",
140 "server_ip_addr": "<ip_from_clien>",
142 "user_ips": [
141 "user_ips": [
143 {
142 {
144 "ip_addr": "<ip_with_mask>",
143 "ip_addr": "<ip_with_mask>",
145 "ip_range": ["<start_ip>", "<end_ip>"],
144 "ip_range": ["<start_ip>", "<end_ip>"],
146 },
145 },
147 ...
146 ...
148 ]
147 ]
149 }
148 }
150
149
151 """
150 """
152 if not has_superadmin_permission(apiuser):
151 if not has_superadmin_permission(apiuser):
153 raise JSONRPCForbidden()
152 raise JSONRPCForbidden()
154
153
155 userid = Optional.extract(userid, evaluate_locals=locals())
154 userid = Optional.extract(userid, evaluate_locals=locals())
156 userid = getattr(userid, 'user_id', userid)
155 userid = getattr(userid, 'user_id', userid)
157
156
158 user = get_user_or_error(userid)
157 user = get_user_or_error(userid)
159 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
158 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
160 return {
159 return {
161 'server_ip_addr': request.rpc_ip_addr,
160 'server_ip_addr': request.rpc_ip_addr,
162 'user_ips': ips
161 'user_ips': ips
163 }
162 }
164
163
165
164
166 @jsonrpc_method()
165 @jsonrpc_method()
167 def rescan_repos(request, apiuser, remove_obsolete=Optional(False)):
166 def rescan_repos(request, apiuser, remove_obsolete=Optional(False)):
168 """
167 """
169 Triggers a rescan of the specified repositories.
168 Triggers a rescan of the specified repositories.
170
169
171 * If the ``remove_obsolete`` option is set, it also deletes repositories
170 * If the ``remove_obsolete`` option is set, it also deletes repositories
172 that are found in the database but not on the file system, so called
171 that are found in the database but not on the file system, so called
173 "clean zombies".
172 "clean zombies".
174
173
175 This command can only be run using an |authtoken| with admin rights to
174 This command can only be run using an |authtoken| with admin rights to
176 the specified repository.
175 the specified repository.
177
176
178 This command takes the following options:
177 This command takes the following options:
179
178
180 :param apiuser: This is filled automatically from the |authtoken|.
179 :param apiuser: This is filled automatically from the |authtoken|.
181 :type apiuser: AuthUser
180 :type apiuser: AuthUser
182 :param remove_obsolete: Deletes repositories from the database that
181 :param remove_obsolete: Deletes repositories from the database that
183 are not found on the filesystem.
182 are not found on the filesystem.
184 :type remove_obsolete: Optional(``True`` | ``False``)
183 :type remove_obsolete: Optional(``True`` | ``False``)
185
184
186 Example output:
185 Example output:
187
186
188 .. code-block:: bash
187 .. code-block:: bash
189
188
190 id : <id_given_in_input>
189 id : <id_given_in_input>
191 result : {
190 result : {
192 'added': [<added repository name>,...]
191 'added': [<added repository name>,...]
193 'removed': [<removed repository name>,...]
192 'removed': [<removed repository name>,...]
194 }
193 }
195 error : null
194 error : null
196
195
197 Example error output:
196 Example error output:
198
197
199 .. code-block:: bash
198 .. code-block:: bash
200
199
201 id : <id_given_in_input>
200 id : <id_given_in_input>
202 result : null
201 result : null
203 error : {
202 error : {
204 'Error occurred during rescan repositories action'
203 'Error occurred during rescan repositories action'
205 }
204 }
206
205
207 """
206 """
208 if not has_superadmin_permission(apiuser):
207 if not has_superadmin_permission(apiuser):
209 raise JSONRPCForbidden()
208 raise JSONRPCForbidden()
210
209
211 try:
210 try:
212 rm_obsolete = Optional.extract(remove_obsolete)
211 rm_obsolete = Optional.extract(remove_obsolete)
213 added, removed = repo2db_mapper(ScmModel().repo_scan(),
212 added, removed = repo2db_mapper(ScmModel().repo_scan(),
214 remove_obsolete=rm_obsolete)
213 remove_obsolete=rm_obsolete)
215 return {'added': added, 'removed': removed}
214 return {'added': added, 'removed': removed}
216 except Exception:
215 except Exception:
217 log.exception('Failed to run repo rescann')
216 log.exception('Failed to run repo rescann')
218 raise JSONRPCError(
217 raise JSONRPCError(
219 'Error occurred during rescan repositories action'
218 'Error occurred during rescan repositories action'
220 )
219 )
221
220
222
221
223 @jsonrpc_method()
222 @jsonrpc_method()
224 def cleanup_sessions(request, apiuser, older_then=Optional(60)):
223 def cleanup_sessions(request, apiuser, older_then=Optional(60)):
225 """
224 """
226 Triggers a session cleanup action.
225 Triggers a session cleanup action.
227
226
228 If the ``older_then`` option is set, only sessions that hasn't been
227 If the ``older_then`` option is set, only sessions that hasn't been
229 accessed in the given number of days will be removed.
228 accessed in the given number of days will be removed.
230
229
231 This command can only be run using an |authtoken| with admin rights to
230 This command can only be run using an |authtoken| with admin rights to
232 the specified repository.
231 the specified repository.
233
232
234 This command takes the following options:
233 This command takes the following options:
235
234
236 :param apiuser: This is filled automatically from the |authtoken|.
235 :param apiuser: This is filled automatically from the |authtoken|.
237 :type apiuser: AuthUser
236 :type apiuser: AuthUser
238 :param older_then: Deletes session that hasn't been accessed
237 :param older_then: Deletes session that hasn't been accessed
239 in given number of days.
238 in given number of days.
240 :type older_then: Optional(int)
239 :type older_then: Optional(int)
241
240
242 Example output:
241 Example output:
243
242
244 .. code-block:: bash
243 .. code-block:: bash
245
244
246 id : <id_given_in_input>
245 id : <id_given_in_input>
247 result: {
246 result: {
248 "backend": "<type of backend>",
247 "backend": "<type of backend>",
249 "sessions_removed": <number_of_removed_sessions>
248 "sessions_removed": <number_of_removed_sessions>
250 }
249 }
251 error : null
250 error : null
252
251
253 Example error output:
252 Example error output:
254
253
255 .. code-block:: bash
254 .. code-block:: bash
256
255
257 id : <id_given_in_input>
256 id : <id_given_in_input>
258 result : null
257 result : null
259 error : {
258 error : {
260 'Error occurred during session cleanup'
259 'Error occurred during session cleanup'
261 }
260 }
262
261
263 """
262 """
264 if not has_superadmin_permission(apiuser):
263 if not has_superadmin_permission(apiuser):
265 raise JSONRPCForbidden()
264 raise JSONRPCForbidden()
266
265
267 older_then = safe_int(Optional.extract(older_then)) or 60
266 older_then = safe_int(Optional.extract(older_then)) or 60
268 older_than_seconds = 60 * 60 * 24 * older_then
267 older_than_seconds = 60 * 60 * 24 * older_then
269
268
270 config = system_info.rhodecode_config().get_value()['value']['config']
269 config = system_info.rhodecode_config().get_value()['value']['config']
271 session_model = user_sessions.get_session_handler(
270 session_model = user_sessions.get_session_handler(
272 config.get('beaker.session.type', 'memory'))(config)
271 config.get('beaker.session.type', 'memory'))(config)
273
272
274 backend = session_model.SESSION_TYPE
273 backend = session_model.SESSION_TYPE
275 try:
274 try:
276 cleaned = session_model.clean_sessions(
275 cleaned = session_model.clean_sessions(
277 older_than_seconds=older_than_seconds)
276 older_than_seconds=older_than_seconds)
278 return {'sessions_removed': cleaned, 'backend': backend}
277 return {'sessions_removed': cleaned, 'backend': backend}
279 except user_sessions.CleanupCommand as msg:
278 except user_sessions.CleanupCommand as msg:
280 return {'cleanup_command': msg.message, 'backend': backend}
279 return {'cleanup_command': msg.message, 'backend': backend}
281 except Exception as e:
280 except Exception as e:
282 log.exception('Failed session cleanup')
281 log.exception('Failed session cleanup')
283 raise JSONRPCError(
282 raise JSONRPCError(
284 'Error occurred during session cleanup'
283 'Error occurred during session cleanup'
285 )
284 )
286
285
287
286
288 @jsonrpc_method()
287 @jsonrpc_method()
289 def get_method(request, apiuser, pattern=Optional('*')):
288 def get_method(request, apiuser, pattern=Optional('*')):
290 """
289 """
291 Returns list of all available API methods. By default match pattern
290 Returns list of all available API methods. By default match pattern
292 os "*" but any other pattern can be specified. eg *comment* will return
291 os "*" but any other pattern can be specified. eg *comment* will return
293 all methods with comment inside them. If just single method is matched
292 all methods with comment inside them. If just single method is matched
294 returned data will also include method specification
293 returned data will also include method specification
295
294
296 This command can only be run using an |authtoken| with admin rights to
295 This command can only be run using an |authtoken| with admin rights to
297 the specified repository.
296 the specified repository.
298
297
299 This command takes the following options:
298 This command takes the following options:
300
299
301 :param apiuser: This is filled automatically from the |authtoken|.
300 :param apiuser: This is filled automatically from the |authtoken|.
302 :type apiuser: AuthUser
301 :type apiuser: AuthUser
303 :param pattern: pattern to match method names against
302 :param pattern: pattern to match method names against
304 :type pattern: Optional("*")
303 :type pattern: Optional("*")
305
304
306 Example output:
305 Example output:
307
306
308 .. code-block:: bash
307 .. code-block:: bash
309
308
310 id : <id_given_in_input>
309 id : <id_given_in_input>
311 "result": [
310 "result": [
312 "changeset_comment",
311 "changeset_comment",
313 "comment_pull_request",
312 "comment_pull_request",
314 "comment_commit"
313 "comment_commit"
315 ]
314 ]
316 error : null
315 error : null
317
316
318 .. code-block:: bash
317 .. code-block:: bash
319
318
320 id : <id_given_in_input>
319 id : <id_given_in_input>
321 "result": [
320 "result": [
322 "comment_commit",
321 "comment_commit",
323 {
322 {
324 "apiuser": "<RequiredType>",
323 "apiuser": "<RequiredType>",
325 "comment_type": "<Optional:u'note'>",
324 "comment_type": "<Optional:u'note'>",
326 "commit_id": "<RequiredType>",
325 "commit_id": "<RequiredType>",
327 "message": "<RequiredType>",
326 "message": "<RequiredType>",
328 "repoid": "<RequiredType>",
327 "repoid": "<RequiredType>",
329 "request": "<RequiredType>",
328 "request": "<RequiredType>",
330 "resolves_comment_id": "<Optional:None>",
329 "resolves_comment_id": "<Optional:None>",
331 "status": "<Optional:None>",
330 "status": "<Optional:None>",
332 "userid": "<Optional:<OptionalAttr:apiuser>>"
331 "userid": "<Optional:<OptionalAttr:apiuser>>"
333 }
332 }
334 ]
333 ]
335 error : null
334 error : null
336 """
335 """
336 from rhodecode.config.patches import inspect_getargspec
337 inspect = inspect_getargspec()
338
337 if not has_superadmin_permission(apiuser):
339 if not has_superadmin_permission(apiuser):
338 raise JSONRPCForbidden()
340 raise JSONRPCForbidden()
339
341
340 pattern = Optional.extract(pattern)
342 pattern = Optional.extract(pattern)
341
343
342 matches = find_methods(request.registry.jsonrpc_methods, pattern)
344 matches = find_methods(request.registry.jsonrpc_methods, pattern)
343
345
344 args_desc = []
346 args_desc = []
345 if len(matches) == 1:
347 if len(matches) == 1:
346 func = matches[matches.keys()[0]]
348 func = matches[matches.keys()[0]]
347
349
348 argspec = inspect.getargspec(func)
350 argspec = inspect.getargspec(func)
349 arglist = argspec[0]
351 arglist = argspec[0]
350 defaults = map(repr, argspec[3] or [])
352 defaults = map(repr, argspec[3] or [])
351
353
352 default_empty = '<RequiredType>'
354 default_empty = '<RequiredType>'
353
355
354 # kw arguments required by this method
356 # kw arguments required by this method
355 func_kwargs = dict(itertools.izip_longest(
357 func_kwargs = dict(itertools.izip_longest(
356 reversed(arglist), reversed(defaults), fillvalue=default_empty))
358 reversed(arglist), reversed(defaults), fillvalue=default_empty))
357 args_desc.append(func_kwargs)
359 args_desc.append(func_kwargs)
358
360
359 return matches.keys() + args_desc
361 return matches.keys() + args_desc
360
362
361
363
362 @jsonrpc_method()
364 @jsonrpc_method()
363 def store_exception(request, apiuser, exc_data_json, prefix=Optional('rhodecode')):
365 def store_exception(request, apiuser, exc_data_json, prefix=Optional('rhodecode')):
364 """
366 """
365 Stores sent exception inside the built-in exception tracker in |RCE| server.
367 Stores sent exception inside the built-in exception tracker in |RCE| server.
366
368
367 This command can only be run using an |authtoken| with admin rights to
369 This command can only be run using an |authtoken| with admin rights to
368 the specified repository.
370 the specified repository.
369
371
370 This command takes the following options:
372 This command takes the following options:
371
373
372 :param apiuser: This is filled automatically from the |authtoken|.
374 :param apiuser: This is filled automatically from the |authtoken|.
373 :type apiuser: AuthUser
375 :type apiuser: AuthUser
374
376
375 :param exc_data_json: JSON data with exception e.g
377 :param exc_data_json: JSON data with exception e.g
376 {"exc_traceback": "Value `1` is not allowed", "exc_type_name": "ValueError"}
378 {"exc_traceback": "Value `1` is not allowed", "exc_type_name": "ValueError"}
377 :type exc_data_json: JSON data
379 :type exc_data_json: JSON data
378
380
379 :param prefix: prefix for error type, e.g 'rhodecode', 'vcsserver', 'rhodecode-tools'
381 :param prefix: prefix for error type, e.g 'rhodecode', 'vcsserver', 'rhodecode-tools'
380 :type prefix: Optional("rhodecode")
382 :type prefix: Optional("rhodecode")
381
383
382 Example output:
384 Example output:
383
385
384 .. code-block:: bash
386 .. code-block:: bash
385
387
386 id : <id_given_in_input>
388 id : <id_given_in_input>
387 "result": {
389 "result": {
388 "exc_id": 139718459226384,
390 "exc_id": 139718459226384,
389 "exc_url": "http://localhost:8080/_admin/settings/exceptions/139718459226384"
391 "exc_url": "http://localhost:8080/_admin/settings/exceptions/139718459226384"
390 }
392 }
391 error : null
393 error : null
392 """
394 """
393 if not has_superadmin_permission(apiuser):
395 if not has_superadmin_permission(apiuser):
394 raise JSONRPCForbidden()
396 raise JSONRPCForbidden()
395
397
396 prefix = Optional.extract(prefix)
398 prefix = Optional.extract(prefix)
397 exc_id = exc_tracking.generate_id()
399 exc_id = exc_tracking.generate_id()
398
400
399 try:
401 try:
400 exc_data = json.loads(exc_data_json)
402 exc_data = json.loads(exc_data_json)
401 except Exception:
403 except Exception:
402 log.error('Failed to parse JSON: %r', exc_data_json)
404 log.error('Failed to parse JSON: %r', exc_data_json)
403 raise JSONRPCError('Failed to parse JSON data from exc_data_json field. '
405 raise JSONRPCError('Failed to parse JSON data from exc_data_json field. '
404 'Please make sure it contains a valid JSON.')
406 'Please make sure it contains a valid JSON.')
405
407
406 try:
408 try:
407 exc_traceback = exc_data['exc_traceback']
409 exc_traceback = exc_data['exc_traceback']
408 exc_type_name = exc_data['exc_type_name']
410 exc_type_name = exc_data['exc_type_name']
409 except KeyError as err:
411 except KeyError as err:
410 raise JSONRPCError('Missing exc_traceback, or exc_type_name '
412 raise JSONRPCError('Missing exc_traceback, or exc_type_name '
411 'in exc_data_json field. Missing: {}'.format(err))
413 'in exc_data_json field. Missing: {}'.format(err))
412
414
413 exc_tracking._store_exception(
415 exc_tracking._store_exception(
414 exc_id=exc_id, exc_traceback=exc_traceback,
416 exc_id=exc_id, exc_traceback=exc_traceback,
415 exc_type_name=exc_type_name, prefix=prefix)
417 exc_type_name=exc_type_name, prefix=prefix)
416
418
417 exc_url = request.route_url(
419 exc_url = request.route_url(
418 'admin_settings_exception_tracker_show', exception_id=exc_id)
420 'admin_settings_exception_tracker_show', exception_id=exc_id)
419 return {'exc_id': exc_id, 'exc_url': exc_url}
421 return {'exc_id': exc_id, 'exc_url': exc_url}
@@ -1,97 +1,99 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 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 Compatibility patches.
22 Compatibility patches.
23
23
24 Please keep the following principles in mind:
24 Please keep the following principles in mind:
25
25
26 * Keep imports local, so that importing this module does not cause too many
26 * Keep imports local, so that importing this module does not cause too many
27 side effects by itself.
27 side effects by itself.
28
28
29 * Try to make patches idempotent, calling them multiple times should not do
29 * Try to make patches idempotent, calling them multiple times should not do
30 harm. If that is not possible, ensure that the second call explodes.
30 harm. If that is not possible, ensure that the second call explodes.
31
31
32 """
32 """
33
33
34
34
35 def inspect_getargspec():
35 def inspect_getargspec():
36 """
36 """
37 Pyramid rely on inspect.getargspec to lookup the signature of
37 Pyramid rely on inspect.getargspec to lookup the signature of
38 view functions. This is not compatible with cython, therefore we replace
38 view functions. This is not compatible with cython, therefore we replace
39 getargspec with a custom version.
39 getargspec with a custom version.
40 Code is inspired by the inspect module from Python-3.4
40 Code is inspired by the inspect module from Python-3.4
41 """
41 """
42 import inspect
42 import inspect
43
43
44 def _isCython(func):
44 def _isCython(func):
45 """
45 """
46 Private helper that checks if a function is a cython function.
46 Private helper that checks if a function is a cython function.
47 """
47 """
48 return func.__class__.__name__ == 'cython_function_or_method'
48 return func.__class__.__name__ == 'cython_function_or_method'
49
49
50 def unwrap(func):
50 def unwrap(func):
51 """
51 """
52 Get the object wrapped by *func*.
52 Get the object wrapped by *func*.
53
53
54 Follows the chain of :attr:`__wrapped__` attributes returning the last
54 Follows the chain of :attr:`__wrapped__` attributes returning the last
55 object in the chain.
55 object in the chain.
56
56
57 *stop* is an optional callback accepting an object in the wrapper chain
57 *stop* is an optional callback accepting an object in the wrapper chain
58 as its sole argument that allows the unwrapping to be terminated early
58 as its sole argument that allows the unwrapping to be terminated early
59 if the callback returns a true value. If the callback never returns a
59 if the callback returns a true value. If the callback never returns a
60 true value, the last object in the chain is returned as usual. For
60 true value, the last object in the chain is returned as usual. For
61 example, :func:`signature` uses this to stop unwrapping if any object
61 example, :func:`signature` uses this to stop unwrapping if any object
62 in the chain has a ``__signature__`` attribute defined.
62 in the chain has a ``__signature__`` attribute defined.
63
63
64 :exc:`ValueError` is raised if a cycle is encountered.
64 :exc:`ValueError` is raised if a cycle is encountered.
65 """
65 """
66 f = func # remember the original func for error reporting
66 f = func # remember the original func for error reporting
67 memo = {id(f)} # Memoise by id to tolerate non-hashable objects
67 memo = {id(f)} # Memoise by id to tolerate non-hashable objects
68 while hasattr(func, '__wrapped__'):
68 while hasattr(func, '__wrapped__'):
69 func = func.__wrapped__
69 func = func.__wrapped__
70 id_func = id(func)
70 id_func = id(func)
71 if id_func in memo:
71 if id_func in memo:
72 raise ValueError('wrapper loop when unwrapping {!r}'.format(f))
72 raise ValueError('wrapper loop when unwrapping {!r}'.format(f))
73 memo.add(id_func)
73 memo.add(id_func)
74 return func
74 return func
75
75
76 def custom_getargspec(func):
76 def custom_getargspec(func):
77 """
77 """
78 Get the names and default values of a function's arguments.
78 Get the names and default values of a function's arguments.
79
79
80 A tuple of four things is returned: (args, varargs, varkw, defaults).
80 A tuple of four things is returned: (args, varargs, varkw, defaults).
81 'args' is a list of the argument names (it may contain nested lists).
81 'args' is a list of the argument names (it may contain nested lists).
82 'varargs' and 'varkw' are the names of the * and ** arguments or None.
82 'varargs' and 'varkw' are the names of the * and ** arguments or None.
83 'defaults' is an n-tuple of the default values of the last n arguments.
83 'defaults' is an n-tuple of the default values of the last n arguments.
84 """
84 """
85
85
86 func = unwrap(func)
86 func = unwrap(func)
87
87
88 if inspect.ismethod(func):
88 if inspect.ismethod(func):
89 func = func.im_func
89 func = func.im_func
90 if not inspect.isfunction(func):
90 if not inspect.isfunction(func):
91 if not _isCython(func):
91 if not _isCython(func):
92 raise TypeError('{!r} is not a Python or Cython function'
92 raise TypeError('{!r} is not a Python or Cython function'
93 .format(func))
93 .format(func))
94 args, varargs, varkw = inspect.getargs(func.func_code)
94 args, varargs, varkw = inspect.getargs(func.func_code)
95 return inspect.ArgSpec(args, varargs, varkw, func.func_defaults)
95 return inspect.ArgSpec(args, varargs, varkw, func.func_defaults)
96
96
97 inspect.getargspec = custom_getargspec
97 inspect.getargspec = custom_getargspec
98
99 return inspect
@@ -1,2413 +1,2413 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 os
25 import os
26 import time
26 import time
27 import inspect
28 import collections
27 import collections
29 import fnmatch
28 import fnmatch
30 import hashlib
29 import hashlib
31 import itertools
30 import itertools
32 import logging
31 import logging
33 import random
32 import random
34 import traceback
33 import traceback
35 from functools import wraps
34 from functools import wraps
36
35
37 import ipaddress
36 import ipaddress
38
37
39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
38 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
40 from sqlalchemy.orm.exc import ObjectDeletedError
39 from sqlalchemy.orm.exc import ObjectDeletedError
41 from sqlalchemy.orm import joinedload
40 from sqlalchemy.orm import joinedload
42 from zope.cachedescriptors.property import Lazy as LazyProperty
41 from zope.cachedescriptors.property import Lazy as LazyProperty
43
42
44 import rhodecode
43 import rhodecode
45 from rhodecode.model import meta
44 from rhodecode.model import meta
46 from rhodecode.model.meta import Session
45 from rhodecode.model.meta import Session
47 from rhodecode.model.user import UserModel
46 from rhodecode.model.user import UserModel
48 from rhodecode.model.db import (
47 from rhodecode.model.db import (
49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
48 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
49 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
51 from rhodecode.lib import rc_cache
50 from rhodecode.lib import rc_cache
52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
51 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
53 from rhodecode.lib.utils import (
52 from rhodecode.lib.utils import (
54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
53 get_repo_slug, get_repo_group_slug, get_user_group_slug)
55 from rhodecode.lib.caching_query import FromCache
54 from rhodecode.lib.caching_query import FromCache
56
55
57
56
58 if rhodecode.is_unix:
57 if rhodecode.is_unix:
59 import bcrypt
58 import bcrypt
60
59
61 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
62
61
63 csrf_token_key = "csrf_token"
62 csrf_token_key = "csrf_token"
64
63
65
64
66 class PasswordGenerator(object):
65 class PasswordGenerator(object):
67 """
66 """
68 This is a simple class for generating password from different sets of
67 This is a simple class for generating password from different sets of
69 characters
68 characters
70 usage::
69 usage::
71 passwd_gen = PasswordGenerator()
70 passwd_gen = PasswordGenerator()
72 #print 8-letter password containing only big and small letters
71 #print 8-letter password containing only big and small letters
73 of alphabet
72 of alphabet
74 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
73 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
75 """
74 """
76 ALPHABETS_NUM = r'''1234567890'''
75 ALPHABETS_NUM = r'''1234567890'''
77 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
76 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
78 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
77 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
79 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
78 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
80 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
79 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
81 + ALPHABETS_NUM + ALPHABETS_SPECIAL
80 + ALPHABETS_NUM + ALPHABETS_SPECIAL
82 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
81 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
83 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
82 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
84 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
83 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
85 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
84 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
86
85
87 def __init__(self, passwd=''):
86 def __init__(self, passwd=''):
88 self.passwd = passwd
87 self.passwd = passwd
89
88
90 def gen_password(self, length, type_=None):
89 def gen_password(self, length, type_=None):
91 if type_ is None:
90 if type_ is None:
92 type_ = self.ALPHABETS_FULL
91 type_ = self.ALPHABETS_FULL
93 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
92 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
94 return self.passwd
93 return self.passwd
95
94
96
95
97 class _RhodeCodeCryptoBase(object):
96 class _RhodeCodeCryptoBase(object):
98 ENC_PREF = None
97 ENC_PREF = None
99
98
100 def hash_create(self, str_):
99 def hash_create(self, str_):
101 """
100 """
102 hash the string using
101 hash the string using
103
102
104 :param str_: password to hash
103 :param str_: password to hash
105 """
104 """
106 raise NotImplementedError
105 raise NotImplementedError
107
106
108 def hash_check_with_upgrade(self, password, hashed):
107 def hash_check_with_upgrade(self, password, hashed):
109 """
108 """
110 Returns tuple in which first element is boolean that states that
109 Returns tuple in which first element is boolean that states that
111 given password matches it's hashed version, and the second is new hash
110 given password matches it's hashed version, and the second is new hash
112 of the password, in case this password should be migrated to new
111 of the password, in case this password should be migrated to new
113 cipher.
112 cipher.
114 """
113 """
115 checked_hash = self.hash_check(password, hashed)
114 checked_hash = self.hash_check(password, hashed)
116 return checked_hash, None
115 return checked_hash, None
117
116
118 def hash_check(self, password, hashed):
117 def hash_check(self, password, hashed):
119 """
118 """
120 Checks matching password with it's hashed value.
119 Checks matching password with it's hashed value.
121
120
122 :param password: password
121 :param password: password
123 :param hashed: password in hashed form
122 :param hashed: password in hashed form
124 """
123 """
125 raise NotImplementedError
124 raise NotImplementedError
126
125
127 def _assert_bytes(self, value):
126 def _assert_bytes(self, value):
128 """
127 """
129 Passing in an `unicode` object can lead to hard to detect issues
128 Passing in an `unicode` object can lead to hard to detect issues
130 if passwords contain non-ascii characters. Doing a type check
129 if passwords contain non-ascii characters. Doing a type check
131 during runtime, so that such mistakes are detected early on.
130 during runtime, so that such mistakes are detected early on.
132 """
131 """
133 if not isinstance(value, str):
132 if not isinstance(value, str):
134 raise TypeError(
133 raise TypeError(
135 "Bytestring required as input, got %r." % (value, ))
134 "Bytestring required as input, got %r." % (value, ))
136
135
137
136
138 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
137 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
139 ENC_PREF = ('$2a$10', '$2b$10')
138 ENC_PREF = ('$2a$10', '$2b$10')
140
139
141 def hash_create(self, str_):
140 def hash_create(self, str_):
142 self._assert_bytes(str_)
141 self._assert_bytes(str_)
143 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
142 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
144
143
145 def hash_check_with_upgrade(self, password, hashed):
144 def hash_check_with_upgrade(self, password, hashed):
146 """
145 """
147 Returns tuple in which first element is boolean that states that
146 Returns tuple in which first element is boolean that states that
148 given password matches it's hashed version, and the second is new hash
147 given password matches it's hashed version, and the second is new hash
149 of the password, in case this password should be migrated to new
148 of the password, in case this password should be migrated to new
150 cipher.
149 cipher.
151
150
152 This implements special upgrade logic which works like that:
151 This implements special upgrade logic which works like that:
153 - check if the given password == bcrypted hash, if yes then we
152 - check if the given password == bcrypted hash, if yes then we
154 properly used password and it was already in bcrypt. Proceed
153 properly used password and it was already in bcrypt. Proceed
155 without any changes
154 without any changes
156 - if bcrypt hash check is not working try with sha256. If hash compare
155 - if bcrypt hash check is not working try with sha256. If hash compare
157 is ok, it means we using correct but old hashed password. indicate
156 is ok, it means we using correct but old hashed password. indicate
158 hash change and proceed
157 hash change and proceed
159 """
158 """
160
159
161 new_hash = None
160 new_hash = None
162
161
163 # regular pw check
162 # regular pw check
164 password_match_bcrypt = self.hash_check(password, hashed)
163 password_match_bcrypt = self.hash_check(password, hashed)
165
164
166 # now we want to know if the password was maybe from sha256
165 # now we want to know if the password was maybe from sha256
167 # basically calling _RhodeCodeCryptoSha256().hash_check()
166 # basically calling _RhodeCodeCryptoSha256().hash_check()
168 if not password_match_bcrypt:
167 if not password_match_bcrypt:
169 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
168 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
170 new_hash = self.hash_create(password) # make new bcrypt hash
169 new_hash = self.hash_create(password) # make new bcrypt hash
171 password_match_bcrypt = True
170 password_match_bcrypt = True
172
171
173 return password_match_bcrypt, new_hash
172 return password_match_bcrypt, new_hash
174
173
175 def hash_check(self, password, hashed):
174 def hash_check(self, password, hashed):
176 """
175 """
177 Checks matching password with it's hashed value.
176 Checks matching password with it's hashed value.
178
177
179 :param password: password
178 :param password: password
180 :param hashed: password in hashed form
179 :param hashed: password in hashed form
181 """
180 """
182 self._assert_bytes(password)
181 self._assert_bytes(password)
183 try:
182 try:
184 return bcrypt.hashpw(password, hashed) == hashed
183 return bcrypt.hashpw(password, hashed) == hashed
185 except ValueError as e:
184 except ValueError as e:
186 # we're having a invalid salt here probably, we should not crash
185 # we're having a invalid salt here probably, we should not crash
187 # just return with False as it would be a wrong password.
186 # just return with False as it would be a wrong password.
188 log.debug('Failed to check password hash using bcrypt %s',
187 log.debug('Failed to check password hash using bcrypt %s',
189 safe_str(e))
188 safe_str(e))
190
189
191 return False
190 return False
192
191
193
192
194 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
193 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
195 ENC_PREF = '_'
194 ENC_PREF = '_'
196
195
197 def hash_create(self, str_):
196 def hash_create(self, str_):
198 self._assert_bytes(str_)
197 self._assert_bytes(str_)
199 return hashlib.sha256(str_).hexdigest()
198 return hashlib.sha256(str_).hexdigest()
200
199
201 def hash_check(self, password, hashed):
200 def hash_check(self, password, hashed):
202 """
201 """
203 Checks matching password with it's hashed value.
202 Checks matching password with it's hashed value.
204
203
205 :param password: password
204 :param password: password
206 :param hashed: password in hashed form
205 :param hashed: password in hashed form
207 """
206 """
208 self._assert_bytes(password)
207 self._assert_bytes(password)
209 return hashlib.sha256(password).hexdigest() == hashed
208 return hashlib.sha256(password).hexdigest() == hashed
210
209
211
210
212 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
211 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
213 ENC_PREF = '_'
212 ENC_PREF = '_'
214
213
215 def hash_create(self, str_):
214 def hash_create(self, str_):
216 self._assert_bytes(str_)
215 self._assert_bytes(str_)
217 return sha1(str_)
216 return sha1(str_)
218
217
219 def hash_check(self, password, hashed):
218 def hash_check(self, password, hashed):
220 """
219 """
221 Checks matching password with it's hashed value.
220 Checks matching password with it's hashed value.
222
221
223 :param password: password
222 :param password: password
224 :param hashed: password in hashed form
223 :param hashed: password in hashed form
225 """
224 """
226 self._assert_bytes(password)
225 self._assert_bytes(password)
227 return sha1(password) == hashed
226 return sha1(password) == hashed
228
227
229
228
230 def crypto_backend():
229 def crypto_backend():
231 """
230 """
232 Return the matching crypto backend.
231 Return the matching crypto backend.
233
232
234 Selection is based on if we run tests or not, we pick sha1-test backend to run
233 Selection is based on if we run tests or not, we pick sha1-test backend to run
235 tests faster since BCRYPT is expensive to calculate
234 tests faster since BCRYPT is expensive to calculate
236 """
235 """
237 if rhodecode.is_test:
236 if rhodecode.is_test:
238 RhodeCodeCrypto = _RhodeCodeCryptoTest()
237 RhodeCodeCrypto = _RhodeCodeCryptoTest()
239 else:
238 else:
240 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
239 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
241
240
242 return RhodeCodeCrypto
241 return RhodeCodeCrypto
243
242
244
243
245 def get_crypt_password(password):
244 def get_crypt_password(password):
246 """
245 """
247 Create the hash of `password` with the active crypto backend.
246 Create the hash of `password` with the active crypto backend.
248
247
249 :param password: The cleartext password.
248 :param password: The cleartext password.
250 :type password: unicode
249 :type password: unicode
251 """
250 """
252 password = safe_str(password)
251 password = safe_str(password)
253 return crypto_backend().hash_create(password)
252 return crypto_backend().hash_create(password)
254
253
255
254
256 def check_password(password, hashed):
255 def check_password(password, hashed):
257 """
256 """
258 Check if the value in `password` matches the hash in `hashed`.
257 Check if the value in `password` matches the hash in `hashed`.
259
258
260 :param password: The cleartext password.
259 :param password: The cleartext password.
261 :type password: unicode
260 :type password: unicode
262
261
263 :param hashed: The expected hashed version of the password.
262 :param hashed: The expected hashed version of the password.
264 :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.
265 """
264 """
266 password = safe_str(password)
265 password = safe_str(password)
267 return crypto_backend().hash_check(password, hashed)
266 return crypto_backend().hash_check(password, hashed)
268
267
269
268
270 def generate_auth_token(data, salt=None):
269 def generate_auth_token(data, salt=None):
271 """
270 """
272 Generates API KEY from given string
271 Generates API KEY from given string
273 """
272 """
274
273
275 if salt is None:
274 if salt is None:
276 salt = os.urandom(16)
275 salt = os.urandom(16)
277 return hashlib.sha1(safe_str(data) + salt).hexdigest()
276 return hashlib.sha1(safe_str(data) + salt).hexdigest()
278
277
279
278
280 def get_came_from(request):
279 def get_came_from(request):
281 """
280 """
282 get query_string+path from request sanitized after removing auth_token
281 get query_string+path from request sanitized after removing auth_token
283 """
282 """
284 _req = request
283 _req = request
285
284
286 path = _req.path
285 path = _req.path
287 if 'auth_token' in _req.GET:
286 if 'auth_token' in _req.GET:
288 # sanitize the request and remove auth_token for redirection
287 # sanitize the request and remove auth_token for redirection
289 _req.GET.pop('auth_token')
288 _req.GET.pop('auth_token')
290 qs = _req.query_string
289 qs = _req.query_string
291 if qs:
290 if qs:
292 path += '?' + qs
291 path += '?' + qs
293
292
294 return path
293 return path
295
294
296
295
297 class CookieStoreWrapper(object):
296 class CookieStoreWrapper(object):
298
297
299 def __init__(self, cookie_store):
298 def __init__(self, cookie_store):
300 self.cookie_store = cookie_store
299 self.cookie_store = cookie_store
301
300
302 def __repr__(self):
301 def __repr__(self):
303 return 'CookieStore<%s>' % (self.cookie_store)
302 return 'CookieStore<%s>' % (self.cookie_store)
304
303
305 def get(self, key, other=None):
304 def get(self, key, other=None):
306 if isinstance(self.cookie_store, dict):
305 if isinstance(self.cookie_store, dict):
307 return self.cookie_store.get(key, other)
306 return self.cookie_store.get(key, other)
308 elif isinstance(self.cookie_store, AuthUser):
307 elif isinstance(self.cookie_store, AuthUser):
309 return self.cookie_store.__dict__.get(key, other)
308 return self.cookie_store.__dict__.get(key, other)
310
309
311
310
312 def _cached_perms_data(user_id, scope, user_is_admin,
311 def _cached_perms_data(user_id, scope, user_is_admin,
313 user_inherit_default_permissions, explicit, algo,
312 user_inherit_default_permissions, explicit, algo,
314 calculate_super_admin):
313 calculate_super_admin):
315
314
316 permissions = PermissionCalculator(
315 permissions = PermissionCalculator(
317 user_id, scope, user_is_admin, user_inherit_default_permissions,
316 user_id, scope, user_is_admin, user_inherit_default_permissions,
318 explicit, algo, calculate_super_admin)
317 explicit, algo, calculate_super_admin)
319 return permissions.calculate()
318 return permissions.calculate()
320
319
321
320
322 class PermOrigin(object):
321 class PermOrigin(object):
323 SUPER_ADMIN = 'superadmin'
322 SUPER_ADMIN = 'superadmin'
324 ARCHIVED = 'archived'
323 ARCHIVED = 'archived'
325
324
326 REPO_USER = 'user:%s'
325 REPO_USER = 'user:%s'
327 REPO_USERGROUP = 'usergroup:%s'
326 REPO_USERGROUP = 'usergroup:%s'
328 REPO_OWNER = 'repo.owner'
327 REPO_OWNER = 'repo.owner'
329 REPO_DEFAULT = 'repo.default'
328 REPO_DEFAULT = 'repo.default'
330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
329 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
331 REPO_PRIVATE = 'repo.private'
330 REPO_PRIVATE = 'repo.private'
332
331
333 REPOGROUP_USER = 'user:%s'
332 REPOGROUP_USER = 'user:%s'
334 REPOGROUP_USERGROUP = 'usergroup:%s'
333 REPOGROUP_USERGROUP = 'usergroup:%s'
335 REPOGROUP_OWNER = 'group.owner'
334 REPOGROUP_OWNER = 'group.owner'
336 REPOGROUP_DEFAULT = 'group.default'
335 REPOGROUP_DEFAULT = 'group.default'
337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
336 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
338
337
339 USERGROUP_USER = 'user:%s'
338 USERGROUP_USER = 'user:%s'
340 USERGROUP_USERGROUP = 'usergroup:%s'
339 USERGROUP_USERGROUP = 'usergroup:%s'
341 USERGROUP_OWNER = 'usergroup.owner'
340 USERGROUP_OWNER = 'usergroup.owner'
342 USERGROUP_DEFAULT = 'usergroup.default'
341 USERGROUP_DEFAULT = 'usergroup.default'
343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
342 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
344
343
345
344
346 class PermOriginDict(dict):
345 class PermOriginDict(dict):
347 """
346 """
348 A special dict used for tracking permissions along with their origins.
347 A special dict used for tracking permissions along with their origins.
349
348
350 `__setitem__` has been overridden to expect a tuple(perm, origin)
349 `__setitem__` has been overridden to expect a tuple(perm, origin)
351 `__getitem__` will return only the perm
350 `__getitem__` will return only the perm
352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
351 `.perm_origin_stack` will return the stack of (perm, origin) set per key
353
352
354 >>> perms = PermOriginDict()
353 >>> perms = PermOriginDict()
355 >>> perms['resource'] = 'read', 'default', 1
354 >>> perms['resource'] = 'read', 'default', 1
356 >>> perms['resource']
355 >>> perms['resource']
357 'read'
356 'read'
358 >>> perms['resource'] = 'write', 'admin', 2
357 >>> perms['resource'] = 'write', 'admin', 2
359 >>> perms['resource']
358 >>> perms['resource']
360 'write'
359 'write'
361 >>> perms.perm_origin_stack
360 >>> perms.perm_origin_stack
362 {'resource': [('read', 'default', 1), ('write', 'admin', 2)]}
361 {'resource': [('read', 'default', 1), ('write', 'admin', 2)]}
363 """
362 """
364
363
365 def __init__(self, *args, **kw):
364 def __init__(self, *args, **kw):
366 dict.__init__(self, *args, **kw)
365 dict.__init__(self, *args, **kw)
367 self.perm_origin_stack = collections.OrderedDict()
366 self.perm_origin_stack = collections.OrderedDict()
368
367
369 def __setitem__(self, key, (perm, origin, obj_id)):
368 def __setitem__(self, key, (perm, origin, obj_id)):
370 self.perm_origin_stack.setdefault(key, []).append(
369 self.perm_origin_stack.setdefault(key, []).append(
371 (perm, origin, obj_id))
370 (perm, origin, obj_id))
372 dict.__setitem__(self, key, perm)
371 dict.__setitem__(self, key, perm)
373
372
374
373
375 class BranchPermOriginDict(PermOriginDict):
374 class BranchPermOriginDict(PermOriginDict):
376 """
375 """
377 Dedicated branch permissions dict, with tracking of patterns and origins.
376 Dedicated branch permissions dict, with tracking of patterns and origins.
378
377
379 >>> perms = BranchPermOriginDict()
378 >>> perms = BranchPermOriginDict()
380 >>> perms['resource'] = '*pattern', 'read', 'default'
379 >>> perms['resource'] = '*pattern', 'read', 'default'
381 >>> perms['resource']
380 >>> perms['resource']
382 {'*pattern': 'read'}
381 {'*pattern': 'read'}
383 >>> perms['resource'] = '*pattern', 'write', 'admin'
382 >>> perms['resource'] = '*pattern', 'write', 'admin'
384 >>> perms['resource']
383 >>> perms['resource']
385 {'*pattern': 'write'}
384 {'*pattern': 'write'}
386 >>> perms.perm_origin_stack
385 >>> perms.perm_origin_stack
387 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
386 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
388 """
387 """
389 def __setitem__(self, key, (pattern, perm, origin)):
388 def __setitem__(self, key, (pattern, perm, origin)):
390
389
391 self.perm_origin_stack.setdefault(key, {}) \
390 self.perm_origin_stack.setdefault(key, {}) \
392 .setdefault(pattern, []).append((perm, origin))
391 .setdefault(pattern, []).append((perm, origin))
393
392
394 if key in self:
393 if key in self:
395 self[key].__setitem__(pattern, perm)
394 self[key].__setitem__(pattern, perm)
396 else:
395 else:
397 patterns = collections.OrderedDict()
396 patterns = collections.OrderedDict()
398 patterns[pattern] = perm
397 patterns[pattern] = perm
399 dict.__setitem__(self, key, patterns)
398 dict.__setitem__(self, key, patterns)
400
399
401
400
402 class PermissionCalculator(object):
401 class PermissionCalculator(object):
403
402
404 def __init__(
403 def __init__(
405 self, user_id, scope, user_is_admin,
404 self, user_id, scope, user_is_admin,
406 user_inherit_default_permissions, explicit, algo,
405 user_inherit_default_permissions, explicit, algo,
407 calculate_super_admin_as_user=False):
406 calculate_super_admin_as_user=False):
408
407
409 self.user_id = user_id
408 self.user_id = user_id
410 self.user_is_admin = user_is_admin
409 self.user_is_admin = user_is_admin
411 self.inherit_default_permissions = user_inherit_default_permissions
410 self.inherit_default_permissions = user_inherit_default_permissions
412 self.explicit = explicit
411 self.explicit = explicit
413 self.algo = algo
412 self.algo = algo
414 self.calculate_super_admin_as_user = calculate_super_admin_as_user
413 self.calculate_super_admin_as_user = calculate_super_admin_as_user
415
414
416 scope = scope or {}
415 scope = scope or {}
417 self.scope_repo_id = scope.get('repo_id')
416 self.scope_repo_id = scope.get('repo_id')
418 self.scope_repo_group_id = scope.get('repo_group_id')
417 self.scope_repo_group_id = scope.get('repo_group_id')
419 self.scope_user_group_id = scope.get('user_group_id')
418 self.scope_user_group_id = scope.get('user_group_id')
420
419
421 self.default_user_id = User.get_default_user(cache=True).user_id
420 self.default_user_id = User.get_default_user(cache=True).user_id
422
421
423 self.permissions_repositories = PermOriginDict()
422 self.permissions_repositories = PermOriginDict()
424 self.permissions_repository_groups = PermOriginDict()
423 self.permissions_repository_groups = PermOriginDict()
425 self.permissions_user_groups = PermOriginDict()
424 self.permissions_user_groups = PermOriginDict()
426 self.permissions_repository_branches = BranchPermOriginDict()
425 self.permissions_repository_branches = BranchPermOriginDict()
427 self.permissions_global = set()
426 self.permissions_global = set()
428
427
429 self.default_repo_perms = Permission.get_default_repo_perms(
428 self.default_repo_perms = Permission.get_default_repo_perms(
430 self.default_user_id, self.scope_repo_id)
429 self.default_user_id, self.scope_repo_id)
431 self.default_repo_groups_perms = Permission.get_default_group_perms(
430 self.default_repo_groups_perms = Permission.get_default_group_perms(
432 self.default_user_id, self.scope_repo_group_id)
431 self.default_user_id, self.scope_repo_group_id)
433 self.default_user_group_perms = \
432 self.default_user_group_perms = \
434 Permission.get_default_user_group_perms(
433 Permission.get_default_user_group_perms(
435 self.default_user_id, self.scope_user_group_id)
434 self.default_user_id, self.scope_user_group_id)
436
435
437 # default branch perms
436 # default branch perms
438 self.default_branch_repo_perms = \
437 self.default_branch_repo_perms = \
439 Permission.get_default_repo_branch_perms(
438 Permission.get_default_repo_branch_perms(
440 self.default_user_id, self.scope_repo_id)
439 self.default_user_id, self.scope_repo_id)
441
440
442 def calculate(self):
441 def calculate(self):
443 if self.user_is_admin and not self.calculate_super_admin_as_user:
442 if self.user_is_admin and not self.calculate_super_admin_as_user:
444 return self._calculate_admin_permissions()
443 return self._calculate_admin_permissions()
445
444
446 self._calculate_global_default_permissions()
445 self._calculate_global_default_permissions()
447 self._calculate_global_permissions()
446 self._calculate_global_permissions()
448 self._calculate_default_permissions()
447 self._calculate_default_permissions()
449 self._calculate_repository_permissions()
448 self._calculate_repository_permissions()
450 self._calculate_repository_branch_permissions()
449 self._calculate_repository_branch_permissions()
451 self._calculate_repository_group_permissions()
450 self._calculate_repository_group_permissions()
452 self._calculate_user_group_permissions()
451 self._calculate_user_group_permissions()
453 return self._permission_structure()
452 return self._permission_structure()
454
453
455 def _calculate_admin_permissions(self):
454 def _calculate_admin_permissions(self):
456 """
455 """
457 admin user have all default rights for repositories
456 admin user have all default rights for repositories
458 and groups set to admin
457 and groups set to admin
459 """
458 """
460 self.permissions_global.add('hg.admin')
459 self.permissions_global.add('hg.admin')
461 self.permissions_global.add('hg.create.write_on_repogroup.true')
460 self.permissions_global.add('hg.create.write_on_repogroup.true')
462
461
463 # repositories
462 # repositories
464 for perm in self.default_repo_perms:
463 for perm in self.default_repo_perms:
465 r_k = perm.UserRepoToPerm.repository.repo_name
464 r_k = perm.UserRepoToPerm.repository.repo_name
466 obj_id = perm.UserRepoToPerm.repository.repo_id
465 obj_id = perm.UserRepoToPerm.repository.repo_id
467 archived = perm.UserRepoToPerm.repository.archived
466 archived = perm.UserRepoToPerm.repository.archived
468 p = 'repository.admin'
467 p = 'repository.admin'
469 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN, obj_id
468 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN, obj_id
470 # special case for archived repositories, which we block still even for
469 # special case for archived repositories, which we block still even for
471 # super admins
470 # super admins
472 if archived:
471 if archived:
473 p = 'repository.read'
472 p = 'repository.read'
474 self.permissions_repositories[r_k] = p, PermOrigin.ARCHIVED, obj_id
473 self.permissions_repositories[r_k] = p, PermOrigin.ARCHIVED, obj_id
475
474
476 # repository groups
475 # repository groups
477 for perm in self.default_repo_groups_perms:
476 for perm in self.default_repo_groups_perms:
478 rg_k = perm.UserRepoGroupToPerm.group.group_name
477 rg_k = perm.UserRepoGroupToPerm.group.group_name
479 obj_id = perm.UserRepoGroupToPerm.group.group_id
478 obj_id = perm.UserRepoGroupToPerm.group.group_id
480 p = 'group.admin'
479 p = 'group.admin'
481 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN, obj_id
480 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN, obj_id
482
481
483 # user groups
482 # user groups
484 for perm in self.default_user_group_perms:
483 for perm in self.default_user_group_perms:
485 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
484 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
486 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
485 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
487 p = 'usergroup.admin'
486 p = 'usergroup.admin'
488 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN, obj_id
487 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN, obj_id
489
488
490 # branch permissions
489 # branch permissions
491 # since super-admin also can have custom rule permissions
490 # since super-admin also can have custom rule permissions
492 # we *always* need to calculate those inherited from default, and also explicit
491 # we *always* need to calculate those inherited from default, and also explicit
493 self._calculate_default_permissions_repository_branches(
492 self._calculate_default_permissions_repository_branches(
494 user_inherit_object_permissions=False)
493 user_inherit_object_permissions=False)
495 self._calculate_repository_branch_permissions()
494 self._calculate_repository_branch_permissions()
496
495
497 return self._permission_structure()
496 return self._permission_structure()
498
497
499 def _calculate_global_default_permissions(self):
498 def _calculate_global_default_permissions(self):
500 """
499 """
501 global permissions taken from the default user
500 global permissions taken from the default user
502 """
501 """
503 default_global_perms = UserToPerm.query()\
502 default_global_perms = UserToPerm.query()\
504 .filter(UserToPerm.user_id == self.default_user_id)\
503 .filter(UserToPerm.user_id == self.default_user_id)\
505 .options(joinedload(UserToPerm.permission))
504 .options(joinedload(UserToPerm.permission))
506
505
507 for perm in default_global_perms:
506 for perm in default_global_perms:
508 self.permissions_global.add(perm.permission.permission_name)
507 self.permissions_global.add(perm.permission.permission_name)
509
508
510 if self.user_is_admin:
509 if self.user_is_admin:
511 self.permissions_global.add('hg.admin')
510 self.permissions_global.add('hg.admin')
512 self.permissions_global.add('hg.create.write_on_repogroup.true')
511 self.permissions_global.add('hg.create.write_on_repogroup.true')
513
512
514 def _calculate_global_permissions(self):
513 def _calculate_global_permissions(self):
515 """
514 """
516 Set global system permissions with user permissions or permissions
515 Set global system permissions with user permissions or permissions
517 taken from the user groups of the current user.
516 taken from the user groups of the current user.
518
517
519 The permissions include repo creating, repo group creating, forking
518 The permissions include repo creating, repo group creating, forking
520 etc.
519 etc.
521 """
520 """
522
521
523 # now we read the defined permissions and overwrite what we have set
522 # now we read the defined permissions and overwrite what we have set
524 # before those can be configured from groups or users explicitly.
523 # before those can be configured from groups or users explicitly.
525
524
526 # In case we want to extend this list we should make sure
525 # In case we want to extend this list we should make sure
527 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
526 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
528 _configurable = frozenset([
527 _configurable = frozenset([
529 'hg.fork.none', 'hg.fork.repository',
528 'hg.fork.none', 'hg.fork.repository',
530 'hg.create.none', 'hg.create.repository',
529 'hg.create.none', 'hg.create.repository',
531 'hg.usergroup.create.false', 'hg.usergroup.create.true',
530 'hg.usergroup.create.false', 'hg.usergroup.create.true',
532 'hg.repogroup.create.false', 'hg.repogroup.create.true',
531 'hg.repogroup.create.false', 'hg.repogroup.create.true',
533 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
532 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
534 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
533 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
535 ])
534 ])
536
535
537 # USER GROUPS comes first user group global permissions
536 # USER GROUPS comes first user group global permissions
538 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
537 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
539 .options(joinedload(UserGroupToPerm.permission))\
538 .options(joinedload(UserGroupToPerm.permission))\
540 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
539 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
541 UserGroupMember.users_group_id))\
540 UserGroupMember.users_group_id))\
542 .filter(UserGroupMember.user_id == self.user_id)\
541 .filter(UserGroupMember.user_id == self.user_id)\
543 .order_by(UserGroupToPerm.users_group_id)\
542 .order_by(UserGroupToPerm.users_group_id)\
544 .all()
543 .all()
545
544
546 # need to group here by groups since user can be in more than
545 # need to group here by groups since user can be in more than
547 # one group, so we get all groups
546 # one group, so we get all groups
548 _explicit_grouped_perms = [
547 _explicit_grouped_perms = [
549 [x, list(y)] for x, y in
548 [x, list(y)] for x, y in
550 itertools.groupby(user_perms_from_users_groups,
549 itertools.groupby(user_perms_from_users_groups,
551 lambda _x: _x.users_group)]
550 lambda _x: _x.users_group)]
552
551
553 for gr, perms in _explicit_grouped_perms:
552 for gr, perms in _explicit_grouped_perms:
554 # since user can be in multiple groups iterate over them and
553 # since user can be in multiple groups iterate over them and
555 # select the lowest permissions first (more explicit)
554 # select the lowest permissions first (more explicit)
556 # TODO(marcink): do this^^
555 # TODO(marcink): do this^^
557
556
558 # group doesn't inherit default permissions so we actually set them
557 # group doesn't inherit default permissions so we actually set them
559 if not gr.inherit_default_permissions:
558 if not gr.inherit_default_permissions:
560 # NEED TO IGNORE all previously set configurable permissions
559 # NEED TO IGNORE all previously set configurable permissions
561 # and replace them with explicitly set from this user
560 # and replace them with explicitly set from this user
562 # group permissions
561 # group permissions
563 self.permissions_global = self.permissions_global.difference(
562 self.permissions_global = self.permissions_global.difference(
564 _configurable)
563 _configurable)
565 for perm in perms:
564 for perm in perms:
566 self.permissions_global.add(perm.permission.permission_name)
565 self.permissions_global.add(perm.permission.permission_name)
567
566
568 # user explicit global permissions
567 # user explicit global permissions
569 user_perms = Session().query(UserToPerm)\
568 user_perms = Session().query(UserToPerm)\
570 .options(joinedload(UserToPerm.permission))\
569 .options(joinedload(UserToPerm.permission))\
571 .filter(UserToPerm.user_id == self.user_id).all()
570 .filter(UserToPerm.user_id == self.user_id).all()
572
571
573 if not self.inherit_default_permissions:
572 if not self.inherit_default_permissions:
574 # NEED TO IGNORE all configurable permissions and
573 # NEED TO IGNORE all configurable permissions and
575 # replace them with explicitly set from this user permissions
574 # replace them with explicitly set from this user permissions
576 self.permissions_global = self.permissions_global.difference(
575 self.permissions_global = self.permissions_global.difference(
577 _configurable)
576 _configurable)
578 for perm in user_perms:
577 for perm in user_perms:
579 self.permissions_global.add(perm.permission.permission_name)
578 self.permissions_global.add(perm.permission.permission_name)
580
579
581 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
580 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
582 for perm in self.default_repo_perms:
581 for perm in self.default_repo_perms:
583 r_k = perm.UserRepoToPerm.repository.repo_name
582 r_k = perm.UserRepoToPerm.repository.repo_name
584 obj_id = perm.UserRepoToPerm.repository.repo_id
583 obj_id = perm.UserRepoToPerm.repository.repo_id
585 archived = perm.UserRepoToPerm.repository.archived
584 archived = perm.UserRepoToPerm.repository.archived
586 p = perm.Permission.permission_name
585 p = perm.Permission.permission_name
587 o = PermOrigin.REPO_DEFAULT
586 o = PermOrigin.REPO_DEFAULT
588 self.permissions_repositories[r_k] = p, o, obj_id
587 self.permissions_repositories[r_k] = p, o, obj_id
589
588
590 # if we decide this user isn't inheriting permissions from
589 # if we decide this user isn't inheriting permissions from
591 # default user we set him to .none so only explicit
590 # default user we set him to .none so only explicit
592 # permissions work
591 # permissions work
593 if not user_inherit_object_permissions:
592 if not user_inherit_object_permissions:
594 p = 'repository.none'
593 p = 'repository.none'
595 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
594 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
596 self.permissions_repositories[r_k] = p, o, obj_id
595 self.permissions_repositories[r_k] = p, o, obj_id
597
596
598 if perm.Repository.private and not (
597 if perm.Repository.private and not (
599 perm.Repository.user_id == self.user_id):
598 perm.Repository.user_id == self.user_id):
600 # disable defaults for private repos,
599 # disable defaults for private repos,
601 p = 'repository.none'
600 p = 'repository.none'
602 o = PermOrigin.REPO_PRIVATE
601 o = PermOrigin.REPO_PRIVATE
603 self.permissions_repositories[r_k] = p, o, obj_id
602 self.permissions_repositories[r_k] = p, o, obj_id
604
603
605 elif perm.Repository.user_id == self.user_id:
604 elif perm.Repository.user_id == self.user_id:
606 # set admin if owner
605 # set admin if owner
607 p = 'repository.admin'
606 p = 'repository.admin'
608 o = PermOrigin.REPO_OWNER
607 o = PermOrigin.REPO_OWNER
609 self.permissions_repositories[r_k] = p, o, obj_id
608 self.permissions_repositories[r_k] = p, o, obj_id
610
609
611 if self.user_is_admin:
610 if self.user_is_admin:
612 p = 'repository.admin'
611 p = 'repository.admin'
613 o = PermOrigin.SUPER_ADMIN
612 o = PermOrigin.SUPER_ADMIN
614 self.permissions_repositories[r_k] = p, o, obj_id
613 self.permissions_repositories[r_k] = p, o, obj_id
615
614
616 # finally in case of archived repositories, we downgrade higher
615 # finally in case of archived repositories, we downgrade higher
617 # permissions to read
616 # permissions to read
618 if archived:
617 if archived:
619 current_perm = self.permissions_repositories[r_k]
618 current_perm = self.permissions_repositories[r_k]
620 if current_perm in ['repository.write', 'repository.admin']:
619 if current_perm in ['repository.write', 'repository.admin']:
621 p = 'repository.read'
620 p = 'repository.read'
622 o = PermOrigin.ARCHIVED
621 o = PermOrigin.ARCHIVED
623 self.permissions_repositories[r_k] = p, o, obj_id
622 self.permissions_repositories[r_k] = p, o, obj_id
624
623
625 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
624 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
626 for perm in self.default_branch_repo_perms:
625 for perm in self.default_branch_repo_perms:
627
626
628 r_k = perm.UserRepoToPerm.repository.repo_name
627 r_k = perm.UserRepoToPerm.repository.repo_name
629 p = perm.Permission.permission_name
628 p = perm.Permission.permission_name
630 pattern = perm.UserToRepoBranchPermission.branch_pattern
629 pattern = perm.UserToRepoBranchPermission.branch_pattern
631 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
630 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
632
631
633 if not self.explicit:
632 if not self.explicit:
634 cur_perm = self.permissions_repository_branches.get(r_k)
633 cur_perm = self.permissions_repository_branches.get(r_k)
635 if cur_perm:
634 if cur_perm:
636 cur_perm = cur_perm[pattern]
635 cur_perm = cur_perm[pattern]
637 cur_perm = cur_perm or 'branch.none'
636 cur_perm = cur_perm or 'branch.none'
638
637
639 p = self._choose_permission(p, cur_perm)
638 p = self._choose_permission(p, cur_perm)
640
639
641 # NOTE(marcink): register all pattern/perm instances in this
640 # NOTE(marcink): register all pattern/perm instances in this
642 # special dict that aggregates entries
641 # special dict that aggregates entries
643 self.permissions_repository_branches[r_k] = pattern, p, o
642 self.permissions_repository_branches[r_k] = pattern, p, o
644
643
645 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
644 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
646 for perm in self.default_repo_groups_perms:
645 for perm in self.default_repo_groups_perms:
647 rg_k = perm.UserRepoGroupToPerm.group.group_name
646 rg_k = perm.UserRepoGroupToPerm.group.group_name
648 obj_id = perm.UserRepoGroupToPerm.group.group_id
647 obj_id = perm.UserRepoGroupToPerm.group.group_id
649 p = perm.Permission.permission_name
648 p = perm.Permission.permission_name
650 o = PermOrigin.REPOGROUP_DEFAULT
649 o = PermOrigin.REPOGROUP_DEFAULT
651 self.permissions_repository_groups[rg_k] = p, o, obj_id
650 self.permissions_repository_groups[rg_k] = p, o, obj_id
652
651
653 # if we decide this user isn't inheriting permissions from default
652 # if we decide this user isn't inheriting permissions from default
654 # user we set him to .none so only explicit permissions work
653 # user we set him to .none so only explicit permissions work
655 if not user_inherit_object_permissions:
654 if not user_inherit_object_permissions:
656 p = 'group.none'
655 p = 'group.none'
657 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
656 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
658 self.permissions_repository_groups[rg_k] = p, o, obj_id
657 self.permissions_repository_groups[rg_k] = p, o, obj_id
659
658
660 if perm.RepoGroup.user_id == self.user_id:
659 if perm.RepoGroup.user_id == self.user_id:
661 # set admin if owner
660 # set admin if owner
662 p = 'group.admin'
661 p = 'group.admin'
663 o = PermOrigin.REPOGROUP_OWNER
662 o = PermOrigin.REPOGROUP_OWNER
664 self.permissions_repository_groups[rg_k] = p, o, obj_id
663 self.permissions_repository_groups[rg_k] = p, o, obj_id
665
664
666 if self.user_is_admin:
665 if self.user_is_admin:
667 p = 'group.admin'
666 p = 'group.admin'
668 o = PermOrigin.SUPER_ADMIN
667 o = PermOrigin.SUPER_ADMIN
669 self.permissions_repository_groups[rg_k] = p, o, obj_id
668 self.permissions_repository_groups[rg_k] = p, o, obj_id
670
669
671 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
670 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
672 for perm in self.default_user_group_perms:
671 for perm in self.default_user_group_perms:
673 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
672 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
674 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
673 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
675 p = perm.Permission.permission_name
674 p = perm.Permission.permission_name
676 o = PermOrigin.USERGROUP_DEFAULT
675 o = PermOrigin.USERGROUP_DEFAULT
677 self.permissions_user_groups[u_k] = p, o, obj_id
676 self.permissions_user_groups[u_k] = p, o, obj_id
678
677
679 # if we decide this user isn't inheriting permissions from default
678 # if we decide this user isn't inheriting permissions from default
680 # user we set him to .none so only explicit permissions work
679 # user we set him to .none so only explicit permissions work
681 if not user_inherit_object_permissions:
680 if not user_inherit_object_permissions:
682 p = 'usergroup.none'
681 p = 'usergroup.none'
683 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
682 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
684 self.permissions_user_groups[u_k] = p, o, obj_id
683 self.permissions_user_groups[u_k] = p, o, obj_id
685
684
686 if perm.UserGroup.user_id == self.user_id:
685 if perm.UserGroup.user_id == self.user_id:
687 # set admin if owner
686 # set admin if owner
688 p = 'usergroup.admin'
687 p = 'usergroup.admin'
689 o = PermOrigin.USERGROUP_OWNER
688 o = PermOrigin.USERGROUP_OWNER
690 self.permissions_user_groups[u_k] = p, o, obj_id
689 self.permissions_user_groups[u_k] = p, o, obj_id
691
690
692 if self.user_is_admin:
691 if self.user_is_admin:
693 p = 'usergroup.admin'
692 p = 'usergroup.admin'
694 o = PermOrigin.SUPER_ADMIN
693 o = PermOrigin.SUPER_ADMIN
695 self.permissions_user_groups[u_k] = p, o, obj_id
694 self.permissions_user_groups[u_k] = p, o, obj_id
696
695
697 def _calculate_default_permissions(self):
696 def _calculate_default_permissions(self):
698 """
697 """
699 Set default user permissions for repositories, repository branches,
698 Set default user permissions for repositories, repository branches,
700 repository groups, user groups taken from the default user.
699 repository groups, user groups taken from the default user.
701
700
702 Calculate inheritance of object permissions based on what we have now
701 Calculate inheritance of object permissions based on what we have now
703 in GLOBAL permissions. We check if .false is in GLOBAL since this is
702 in GLOBAL permissions. We check if .false is in GLOBAL since this is
704 explicitly set. Inherit is the opposite of .false being there.
703 explicitly set. Inherit is the opposite of .false being there.
705
704
706 .. note::
705 .. note::
707
706
708 the syntax is little bit odd but what we need to check here is
707 the syntax is little bit odd but what we need to check here is
709 the opposite of .false permission being in the list so even for
708 the opposite of .false permission being in the list so even for
710 inconsistent state when both .true/.false is there
709 inconsistent state when both .true/.false is there
711 .false is more important
710 .false is more important
712
711
713 """
712 """
714 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
713 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
715 in self.permissions_global)
714 in self.permissions_global)
716
715
717 # default permissions inherited from `default` user permissions
716 # default permissions inherited from `default` user permissions
718 self._calculate_default_permissions_repositories(
717 self._calculate_default_permissions_repositories(
719 user_inherit_object_permissions)
718 user_inherit_object_permissions)
720
719
721 self._calculate_default_permissions_repository_branches(
720 self._calculate_default_permissions_repository_branches(
722 user_inherit_object_permissions)
721 user_inherit_object_permissions)
723
722
724 self._calculate_default_permissions_repository_groups(
723 self._calculate_default_permissions_repository_groups(
725 user_inherit_object_permissions)
724 user_inherit_object_permissions)
726
725
727 self._calculate_default_permissions_user_groups(
726 self._calculate_default_permissions_user_groups(
728 user_inherit_object_permissions)
727 user_inherit_object_permissions)
729
728
730 def _calculate_repository_permissions(self):
729 def _calculate_repository_permissions(self):
731 """
730 """
732 Repository access permissions for the current user.
731 Repository access permissions for the current user.
733
732
734 Check if the user is part of user groups for this repository and
733 Check if the user is part of user groups for this repository and
735 fill in the permission from it. `_choose_permission` decides of which
734 fill in the permission from it. `_choose_permission` decides of which
736 permission should be selected based on selected method.
735 permission should be selected based on selected method.
737 """
736 """
738
737
739 # user group for repositories permissions
738 # user group for repositories permissions
740 user_repo_perms_from_user_group = Permission\
739 user_repo_perms_from_user_group = Permission\
741 .get_default_repo_perms_from_user_group(
740 .get_default_repo_perms_from_user_group(
742 self.user_id, self.scope_repo_id)
741 self.user_id, self.scope_repo_id)
743
742
744 multiple_counter = collections.defaultdict(int)
743 multiple_counter = collections.defaultdict(int)
745 for perm in user_repo_perms_from_user_group:
744 for perm in user_repo_perms_from_user_group:
746 r_k = perm.UserGroupRepoToPerm.repository.repo_name
745 r_k = perm.UserGroupRepoToPerm.repository.repo_name
747 obj_id = perm.UserGroupRepoToPerm.repository.repo_id
746 obj_id = perm.UserGroupRepoToPerm.repository.repo_id
748 multiple_counter[r_k] += 1
747 multiple_counter[r_k] += 1
749 p = perm.Permission.permission_name
748 p = perm.Permission.permission_name
750 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
749 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
751 .users_group.users_group_name
750 .users_group.users_group_name
752
751
753 if multiple_counter[r_k] > 1:
752 if multiple_counter[r_k] > 1:
754 cur_perm = self.permissions_repositories[r_k]
753 cur_perm = self.permissions_repositories[r_k]
755 p = self._choose_permission(p, cur_perm)
754 p = self._choose_permission(p, cur_perm)
756
755
757 self.permissions_repositories[r_k] = p, o, obj_id
756 self.permissions_repositories[r_k] = p, o, obj_id
758
757
759 if perm.Repository.user_id == self.user_id:
758 if perm.Repository.user_id == self.user_id:
760 # set admin if owner
759 # set admin if owner
761 p = 'repository.admin'
760 p = 'repository.admin'
762 o = PermOrigin.REPO_OWNER
761 o = PermOrigin.REPO_OWNER
763 self.permissions_repositories[r_k] = p, o, obj_id
762 self.permissions_repositories[r_k] = p, o, obj_id
764
763
765 if self.user_is_admin:
764 if self.user_is_admin:
766 p = 'repository.admin'
765 p = 'repository.admin'
767 o = PermOrigin.SUPER_ADMIN
766 o = PermOrigin.SUPER_ADMIN
768 self.permissions_repositories[r_k] = p, o, obj_id
767 self.permissions_repositories[r_k] = p, o, obj_id
769
768
770 # user explicit permissions for repositories, overrides any specified
769 # user explicit permissions for repositories, overrides any specified
771 # by the group permission
770 # by the group permission
772 user_repo_perms = Permission.get_default_repo_perms(
771 user_repo_perms = Permission.get_default_repo_perms(
773 self.user_id, self.scope_repo_id)
772 self.user_id, self.scope_repo_id)
774 for perm in user_repo_perms:
773 for perm in user_repo_perms:
775 r_k = perm.UserRepoToPerm.repository.repo_name
774 r_k = perm.UserRepoToPerm.repository.repo_name
776 obj_id = perm.UserRepoToPerm.repository.repo_id
775 obj_id = perm.UserRepoToPerm.repository.repo_id
777 p = perm.Permission.permission_name
776 p = perm.Permission.permission_name
778 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
777 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
779
778
780 if not self.explicit:
779 if not self.explicit:
781 cur_perm = self.permissions_repositories.get(
780 cur_perm = self.permissions_repositories.get(
782 r_k, 'repository.none')
781 r_k, 'repository.none')
783 p = self._choose_permission(p, cur_perm)
782 p = self._choose_permission(p, cur_perm)
784
783
785 self.permissions_repositories[r_k] = p, o, obj_id
784 self.permissions_repositories[r_k] = p, o, obj_id
786
785
787 if perm.Repository.user_id == self.user_id:
786 if perm.Repository.user_id == self.user_id:
788 # set admin if owner
787 # set admin if owner
789 p = 'repository.admin'
788 p = 'repository.admin'
790 o = PermOrigin.REPO_OWNER
789 o = PermOrigin.REPO_OWNER
791 self.permissions_repositories[r_k] = p, o, obj_id
790 self.permissions_repositories[r_k] = p, o, obj_id
792
791
793 if self.user_is_admin:
792 if self.user_is_admin:
794 p = 'repository.admin'
793 p = 'repository.admin'
795 o = PermOrigin.SUPER_ADMIN
794 o = PermOrigin.SUPER_ADMIN
796 self.permissions_repositories[r_k] = p, o, obj_id
795 self.permissions_repositories[r_k] = p, o, obj_id
797
796
798 def _calculate_repository_branch_permissions(self):
797 def _calculate_repository_branch_permissions(self):
799 # user group for repositories permissions
798 # user group for repositories permissions
800 user_repo_branch_perms_from_user_group = Permission\
799 user_repo_branch_perms_from_user_group = Permission\
801 .get_default_repo_branch_perms_from_user_group(
800 .get_default_repo_branch_perms_from_user_group(
802 self.user_id, self.scope_repo_id)
801 self.user_id, self.scope_repo_id)
803
802
804 multiple_counter = collections.defaultdict(int)
803 multiple_counter = collections.defaultdict(int)
805 for perm in user_repo_branch_perms_from_user_group:
804 for perm in user_repo_branch_perms_from_user_group:
806 r_k = perm.UserGroupRepoToPerm.repository.repo_name
805 r_k = perm.UserGroupRepoToPerm.repository.repo_name
807 p = perm.Permission.permission_name
806 p = perm.Permission.permission_name
808 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
807 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
809 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
808 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
810 .users_group.users_group_name
809 .users_group.users_group_name
811
810
812 multiple_counter[r_k] += 1
811 multiple_counter[r_k] += 1
813 if multiple_counter[r_k] > 1:
812 if multiple_counter[r_k] > 1:
814 cur_perm = self.permissions_repository_branches[r_k][pattern]
813 cur_perm = self.permissions_repository_branches[r_k][pattern]
815 p = self._choose_permission(p, cur_perm)
814 p = self._choose_permission(p, cur_perm)
816
815
817 self.permissions_repository_branches[r_k] = pattern, p, o
816 self.permissions_repository_branches[r_k] = pattern, p, o
818
817
819 # user explicit branch permissions for repositories, overrides
818 # user explicit branch permissions for repositories, overrides
820 # any specified by the group permission
819 # any specified by the group permission
821 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
820 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
822 self.user_id, self.scope_repo_id)
821 self.user_id, self.scope_repo_id)
823
822
824 for perm in user_repo_branch_perms:
823 for perm in user_repo_branch_perms:
825
824
826 r_k = perm.UserRepoToPerm.repository.repo_name
825 r_k = perm.UserRepoToPerm.repository.repo_name
827 p = perm.Permission.permission_name
826 p = perm.Permission.permission_name
828 pattern = perm.UserToRepoBranchPermission.branch_pattern
827 pattern = perm.UserToRepoBranchPermission.branch_pattern
829 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
828 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
830
829
831 if not self.explicit:
830 if not self.explicit:
832 cur_perm = self.permissions_repository_branches.get(r_k)
831 cur_perm = self.permissions_repository_branches.get(r_k)
833 if cur_perm:
832 if cur_perm:
834 cur_perm = cur_perm[pattern]
833 cur_perm = cur_perm[pattern]
835 cur_perm = cur_perm or 'branch.none'
834 cur_perm = cur_perm or 'branch.none'
836 p = self._choose_permission(p, cur_perm)
835 p = self._choose_permission(p, cur_perm)
837
836
838 # NOTE(marcink): register all pattern/perm instances in this
837 # NOTE(marcink): register all pattern/perm instances in this
839 # special dict that aggregates entries
838 # special dict that aggregates entries
840 self.permissions_repository_branches[r_k] = pattern, p, o
839 self.permissions_repository_branches[r_k] = pattern, p, o
841
840
842 def _calculate_repository_group_permissions(self):
841 def _calculate_repository_group_permissions(self):
843 """
842 """
844 Repository group permissions for the current user.
843 Repository group permissions for the current user.
845
844
846 Check if the user is part of user groups for repository groups and
845 Check if the user is part of user groups for repository groups and
847 fill in the permissions from it. `_choose_permission` decides of which
846 fill in the permissions from it. `_choose_permission` decides of which
848 permission should be selected based on selected method.
847 permission should be selected based on selected method.
849 """
848 """
850 # user group for repo groups permissions
849 # user group for repo groups permissions
851 user_repo_group_perms_from_user_group = Permission\
850 user_repo_group_perms_from_user_group = Permission\
852 .get_default_group_perms_from_user_group(
851 .get_default_group_perms_from_user_group(
853 self.user_id, self.scope_repo_group_id)
852 self.user_id, self.scope_repo_group_id)
854
853
855 multiple_counter = collections.defaultdict(int)
854 multiple_counter = collections.defaultdict(int)
856 for perm in user_repo_group_perms_from_user_group:
855 for perm in user_repo_group_perms_from_user_group:
857 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
856 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
858 obj_id = perm.UserGroupRepoGroupToPerm.group.group_id
857 obj_id = perm.UserGroupRepoGroupToPerm.group.group_id
859 multiple_counter[rg_k] += 1
858 multiple_counter[rg_k] += 1
860 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
859 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
861 .users_group.users_group_name
860 .users_group.users_group_name
862 p = perm.Permission.permission_name
861 p = perm.Permission.permission_name
863
862
864 if multiple_counter[rg_k] > 1:
863 if multiple_counter[rg_k] > 1:
865 cur_perm = self.permissions_repository_groups[rg_k]
864 cur_perm = self.permissions_repository_groups[rg_k]
866 p = self._choose_permission(p, cur_perm)
865 p = self._choose_permission(p, cur_perm)
867 self.permissions_repository_groups[rg_k] = p, o, obj_id
866 self.permissions_repository_groups[rg_k] = p, o, obj_id
868
867
869 if perm.RepoGroup.user_id == self.user_id:
868 if perm.RepoGroup.user_id == self.user_id:
870 # set admin if owner, even for member of other user group
869 # set admin if owner, even for member of other user group
871 p = 'group.admin'
870 p = 'group.admin'
872 o = PermOrigin.REPOGROUP_OWNER
871 o = PermOrigin.REPOGROUP_OWNER
873 self.permissions_repository_groups[rg_k] = p, o, obj_id
872 self.permissions_repository_groups[rg_k] = p, o, obj_id
874
873
875 if self.user_is_admin:
874 if self.user_is_admin:
876 p = 'group.admin'
875 p = 'group.admin'
877 o = PermOrigin.SUPER_ADMIN
876 o = PermOrigin.SUPER_ADMIN
878 self.permissions_repository_groups[rg_k] = p, o, obj_id
877 self.permissions_repository_groups[rg_k] = p, o, obj_id
879
878
880 # user explicit permissions for repository groups
879 # user explicit permissions for repository groups
881 user_repo_groups_perms = Permission.get_default_group_perms(
880 user_repo_groups_perms = Permission.get_default_group_perms(
882 self.user_id, self.scope_repo_group_id)
881 self.user_id, self.scope_repo_group_id)
883 for perm in user_repo_groups_perms:
882 for perm in user_repo_groups_perms:
884 rg_k = perm.UserRepoGroupToPerm.group.group_name
883 rg_k = perm.UserRepoGroupToPerm.group.group_name
885 obj_id = perm.UserRepoGroupToPerm.group.group_id
884 obj_id = perm.UserRepoGroupToPerm.group.group_id
886 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
885 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
887 .user.username
886 .user.username
888 p = perm.Permission.permission_name
887 p = perm.Permission.permission_name
889
888
890 if not self.explicit:
889 if not self.explicit:
891 cur_perm = self.permissions_repository_groups.get(rg_k, 'group.none')
890 cur_perm = self.permissions_repository_groups.get(rg_k, 'group.none')
892 p = self._choose_permission(p, cur_perm)
891 p = self._choose_permission(p, cur_perm)
893
892
894 self.permissions_repository_groups[rg_k] = p, o, obj_id
893 self.permissions_repository_groups[rg_k] = p, o, obj_id
895
894
896 if perm.RepoGroup.user_id == self.user_id:
895 if perm.RepoGroup.user_id == self.user_id:
897 # set admin if owner
896 # set admin if owner
898 p = 'group.admin'
897 p = 'group.admin'
899 o = PermOrigin.REPOGROUP_OWNER
898 o = PermOrigin.REPOGROUP_OWNER
900 self.permissions_repository_groups[rg_k] = p, o, obj_id
899 self.permissions_repository_groups[rg_k] = p, o, obj_id
901
900
902 if self.user_is_admin:
901 if self.user_is_admin:
903 p = 'group.admin'
902 p = 'group.admin'
904 o = PermOrigin.SUPER_ADMIN
903 o = PermOrigin.SUPER_ADMIN
905 self.permissions_repository_groups[rg_k] = p, o, obj_id
904 self.permissions_repository_groups[rg_k] = p, o, obj_id
906
905
907 def _calculate_user_group_permissions(self):
906 def _calculate_user_group_permissions(self):
908 """
907 """
909 User group permissions for the current user.
908 User group permissions for the current user.
910 """
909 """
911 # user group for user group permissions
910 # user group for user group permissions
912 user_group_from_user_group = Permission\
911 user_group_from_user_group = Permission\
913 .get_default_user_group_perms_from_user_group(
912 .get_default_user_group_perms_from_user_group(
914 self.user_id, self.scope_user_group_id)
913 self.user_id, self.scope_user_group_id)
915
914
916 multiple_counter = collections.defaultdict(int)
915 multiple_counter = collections.defaultdict(int)
917 for perm in user_group_from_user_group:
916 for perm in user_group_from_user_group:
918 ug_k = perm.UserGroupUserGroupToPerm.target_user_group.users_group_name
917 ug_k = perm.UserGroupUserGroupToPerm.target_user_group.users_group_name
919 obj_id = perm.UserGroupUserGroupToPerm.target_user_group.users_group_id
918 obj_id = perm.UserGroupUserGroupToPerm.target_user_group.users_group_id
920 multiple_counter[ug_k] += 1
919 multiple_counter[ug_k] += 1
921 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
920 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
922 .user_group.users_group_name
921 .user_group.users_group_name
923 p = perm.Permission.permission_name
922 p = perm.Permission.permission_name
924
923
925 if multiple_counter[ug_k] > 1:
924 if multiple_counter[ug_k] > 1:
926 cur_perm = self.permissions_user_groups[ug_k]
925 cur_perm = self.permissions_user_groups[ug_k]
927 p = self._choose_permission(p, cur_perm)
926 p = self._choose_permission(p, cur_perm)
928
927
929 self.permissions_user_groups[ug_k] = p, o, obj_id
928 self.permissions_user_groups[ug_k] = p, o, obj_id
930
929
931 if perm.UserGroup.user_id == self.user_id:
930 if perm.UserGroup.user_id == self.user_id:
932 # set admin if owner, even for member of other user group
931 # set admin if owner, even for member of other user group
933 p = 'usergroup.admin'
932 p = 'usergroup.admin'
934 o = PermOrigin.USERGROUP_OWNER
933 o = PermOrigin.USERGROUP_OWNER
935 self.permissions_user_groups[ug_k] = p, o, obj_id
934 self.permissions_user_groups[ug_k] = p, o, obj_id
936
935
937 if self.user_is_admin:
936 if self.user_is_admin:
938 p = 'usergroup.admin'
937 p = 'usergroup.admin'
939 o = PermOrigin.SUPER_ADMIN
938 o = PermOrigin.SUPER_ADMIN
940 self.permissions_user_groups[ug_k] = p, o, obj_id
939 self.permissions_user_groups[ug_k] = p, o, obj_id
941
940
942 # user explicit permission for user groups
941 # user explicit permission for user groups
943 user_user_groups_perms = Permission.get_default_user_group_perms(
942 user_user_groups_perms = Permission.get_default_user_group_perms(
944 self.user_id, self.scope_user_group_id)
943 self.user_id, self.scope_user_group_id)
945 for perm in user_user_groups_perms:
944 for perm in user_user_groups_perms:
946 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
945 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
947 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
946 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
948 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
947 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
949 .user.username
948 .user.username
950 p = perm.Permission.permission_name
949 p = perm.Permission.permission_name
951
950
952 if not self.explicit:
951 if not self.explicit:
953 cur_perm = self.permissions_user_groups.get(ug_k, 'usergroup.none')
952 cur_perm = self.permissions_user_groups.get(ug_k, 'usergroup.none')
954 p = self._choose_permission(p, cur_perm)
953 p = self._choose_permission(p, cur_perm)
955
954
956 self.permissions_user_groups[ug_k] = p, o, obj_id
955 self.permissions_user_groups[ug_k] = p, o, obj_id
957
956
958 if perm.UserGroup.user_id == self.user_id:
957 if perm.UserGroup.user_id == self.user_id:
959 # set admin if owner
958 # set admin if owner
960 p = 'usergroup.admin'
959 p = 'usergroup.admin'
961 o = PermOrigin.USERGROUP_OWNER
960 o = PermOrigin.USERGROUP_OWNER
962 self.permissions_user_groups[ug_k] = p, o, obj_id
961 self.permissions_user_groups[ug_k] = p, o, obj_id
963
962
964 if self.user_is_admin:
963 if self.user_is_admin:
965 p = 'usergroup.admin'
964 p = 'usergroup.admin'
966 o = PermOrigin.SUPER_ADMIN
965 o = PermOrigin.SUPER_ADMIN
967 self.permissions_user_groups[ug_k] = p, o, obj_id
966 self.permissions_user_groups[ug_k] = p, o, obj_id
968
967
969 def _choose_permission(self, new_perm, cur_perm):
968 def _choose_permission(self, new_perm, cur_perm):
970 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
969 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
971 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
970 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
972 if self.algo == 'higherwin':
971 if self.algo == 'higherwin':
973 if new_perm_val > cur_perm_val:
972 if new_perm_val > cur_perm_val:
974 return new_perm
973 return new_perm
975 return cur_perm
974 return cur_perm
976 elif self.algo == 'lowerwin':
975 elif self.algo == 'lowerwin':
977 if new_perm_val < cur_perm_val:
976 if new_perm_val < cur_perm_val:
978 return new_perm
977 return new_perm
979 return cur_perm
978 return cur_perm
980
979
981 def _permission_structure(self):
980 def _permission_structure(self):
982 return {
981 return {
983 'global': self.permissions_global,
982 'global': self.permissions_global,
984 'repositories': self.permissions_repositories,
983 'repositories': self.permissions_repositories,
985 'repository_branches': self.permissions_repository_branches,
984 'repository_branches': self.permissions_repository_branches,
986 'repositories_groups': self.permissions_repository_groups,
985 'repositories_groups': self.permissions_repository_groups,
987 'user_groups': self.permissions_user_groups,
986 'user_groups': self.permissions_user_groups,
988 }
987 }
989
988
990
989
991 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
990 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
992 """
991 """
993 Check if given controller_name is in whitelist of auth token access
992 Check if given controller_name is in whitelist of auth token access
994 """
993 """
995 if not whitelist:
994 if not whitelist:
996 from rhodecode import CONFIG
995 from rhodecode import CONFIG
997 whitelist = aslist(
996 whitelist = aslist(
998 CONFIG.get('api_access_controllers_whitelist'), sep=',')
997 CONFIG.get('api_access_controllers_whitelist'), sep=',')
999 # backward compat translation
998 # backward compat translation
1000 compat = {
999 compat = {
1001 # old controller, new VIEW
1000 # old controller, new VIEW
1002 'ChangesetController:*': 'RepoCommitsView:*',
1001 'ChangesetController:*': 'RepoCommitsView:*',
1003 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
1002 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
1004 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
1003 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
1005 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
1004 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
1006 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
1005 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
1007 'GistsController:*': 'GistView:*',
1006 'GistsController:*': 'GistView:*',
1008 }
1007 }
1009
1008
1010 log.debug(
1009 log.debug(
1011 'Allowed views for AUTH TOKEN access: %s', whitelist)
1010 'Allowed views for AUTH TOKEN access: %s', whitelist)
1012 auth_token_access_valid = False
1011 auth_token_access_valid = False
1013
1012
1014 for entry in whitelist:
1013 for entry in whitelist:
1015 token_match = True
1014 token_match = True
1016 if entry in compat:
1015 if entry in compat:
1017 # translate from old Controllers to Pyramid Views
1016 # translate from old Controllers to Pyramid Views
1018 entry = compat[entry]
1017 entry = compat[entry]
1019
1018
1020 if '@' in entry:
1019 if '@' in entry:
1021 # specific AuthToken
1020 # specific AuthToken
1022 entry, allowed_token = entry.split('@', 1)
1021 entry, allowed_token = entry.split('@', 1)
1023 token_match = auth_token == allowed_token
1022 token_match = auth_token == allowed_token
1024
1023
1025 if fnmatch.fnmatch(view_name, entry) and token_match:
1024 if fnmatch.fnmatch(view_name, entry) and token_match:
1026 auth_token_access_valid = True
1025 auth_token_access_valid = True
1027 break
1026 break
1028
1027
1029 if auth_token_access_valid:
1028 if auth_token_access_valid:
1030 log.debug('view: `%s` matches entry in whitelist: %s',
1029 log.debug('view: `%s` matches entry in whitelist: %s',
1031 view_name, whitelist)
1030 view_name, whitelist)
1032
1031
1033 else:
1032 else:
1034 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1033 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1035 % (view_name, whitelist))
1034 % (view_name, whitelist))
1036 if auth_token:
1035 if auth_token:
1037 # if we use auth token key and don't have access it's a warning
1036 # if we use auth token key and don't have access it's a warning
1038 log.warning(msg)
1037 log.warning(msg)
1039 else:
1038 else:
1040 log.debug(msg)
1039 log.debug(msg)
1041
1040
1042 return auth_token_access_valid
1041 return auth_token_access_valid
1043
1042
1044
1043
1045 class AuthUser(object):
1044 class AuthUser(object):
1046 """
1045 """
1047 A simple object that handles all attributes of user in RhodeCode
1046 A simple object that handles all attributes of user in RhodeCode
1048
1047
1049 It does lookup based on API key,given user, or user present in session
1048 It does lookup based on API key,given user, or user present in session
1050 Then it fills all required information for such user. It also checks if
1049 Then it fills all required information for such user. It also checks if
1051 anonymous access is enabled and if so, it returns default user as logged in
1050 anonymous access is enabled and if so, it returns default user as logged in
1052 """
1051 """
1053 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1052 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1054 repo_read_perms = ['repository.read', 'repository.admin', 'repository.write']
1053 repo_read_perms = ['repository.read', 'repository.admin', 'repository.write']
1055 repo_group_read_perms = ['group.read', 'group.write', 'group.admin']
1054 repo_group_read_perms = ['group.read', 'group.write', 'group.admin']
1056 user_group_read_perms = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
1055 user_group_read_perms = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
1057
1056
1058 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1057 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1059
1058
1060 self.user_id = user_id
1059 self.user_id = user_id
1061 self._api_key = api_key
1060 self._api_key = api_key
1062
1061
1063 self.api_key = None
1062 self.api_key = None
1064 self.username = username
1063 self.username = username
1065 self.ip_addr = ip_addr
1064 self.ip_addr = ip_addr
1066 self.name = ''
1065 self.name = ''
1067 self.lastname = ''
1066 self.lastname = ''
1068 self.first_name = ''
1067 self.first_name = ''
1069 self.last_name = ''
1068 self.last_name = ''
1070 self.email = ''
1069 self.email = ''
1071 self.is_authenticated = False
1070 self.is_authenticated = False
1072 self.admin = False
1071 self.admin = False
1073 self.inherit_default_permissions = False
1072 self.inherit_default_permissions = False
1074 self.password = ''
1073 self.password = ''
1075
1074
1076 self.anonymous_user = None # propagated on propagate_data
1075 self.anonymous_user = None # propagated on propagate_data
1077 self.propagate_data()
1076 self.propagate_data()
1078 self._instance = None
1077 self._instance = None
1079 self._permissions_scoped_cache = {} # used to bind scoped calculation
1078 self._permissions_scoped_cache = {} # used to bind scoped calculation
1080
1079
1081 @LazyProperty
1080 @LazyProperty
1082 def permissions(self):
1081 def permissions(self):
1083 return self.get_perms(user=self, cache=None)
1082 return self.get_perms(user=self, cache=None)
1084
1083
1085 @LazyProperty
1084 @LazyProperty
1086 def permissions_safe(self):
1085 def permissions_safe(self):
1087 """
1086 """
1088 Filtered permissions excluding not allowed repositories
1087 Filtered permissions excluding not allowed repositories
1089 """
1088 """
1090 perms = self.get_perms(user=self, cache=None)
1089 perms = self.get_perms(user=self, cache=None)
1091
1090
1092 perms['repositories'] = {
1091 perms['repositories'] = {
1093 k: v for k, v in perms['repositories'].items()
1092 k: v for k, v in perms['repositories'].items()
1094 if v != 'repository.none'}
1093 if v != 'repository.none'}
1095 perms['repositories_groups'] = {
1094 perms['repositories_groups'] = {
1096 k: v for k, v in perms['repositories_groups'].items()
1095 k: v for k, v in perms['repositories_groups'].items()
1097 if v != 'group.none'}
1096 if v != 'group.none'}
1098 perms['user_groups'] = {
1097 perms['user_groups'] = {
1099 k: v for k, v in perms['user_groups'].items()
1098 k: v for k, v in perms['user_groups'].items()
1100 if v != 'usergroup.none'}
1099 if v != 'usergroup.none'}
1101 perms['repository_branches'] = {
1100 perms['repository_branches'] = {
1102 k: v for k, v in perms['repository_branches'].iteritems()
1101 k: v for k, v in perms['repository_branches'].iteritems()
1103 if v != 'branch.none'}
1102 if v != 'branch.none'}
1104 return perms
1103 return perms
1105
1104
1106 @LazyProperty
1105 @LazyProperty
1107 def permissions_full_details(self):
1106 def permissions_full_details(self):
1108 return self.get_perms(
1107 return self.get_perms(
1109 user=self, cache=None, calculate_super_admin=True)
1108 user=self, cache=None, calculate_super_admin=True)
1110
1109
1111 def permissions_with_scope(self, scope):
1110 def permissions_with_scope(self, scope):
1112 """
1111 """
1113 Call the get_perms function with scoped data. The scope in that function
1112 Call the get_perms function with scoped data. The scope in that function
1114 narrows the SQL calls to the given ID of objects resulting in fetching
1113 narrows the SQL calls to the given ID of objects resulting in fetching
1115 Just particular permission we want to obtain. If scope is an empty dict
1114 Just particular permission we want to obtain. If scope is an empty dict
1116 then it basically narrows the scope to GLOBAL permissions only.
1115 then it basically narrows the scope to GLOBAL permissions only.
1117
1116
1118 :param scope: dict
1117 :param scope: dict
1119 """
1118 """
1120 if 'repo_name' in scope:
1119 if 'repo_name' in scope:
1121 obj = Repository.get_by_repo_name(scope['repo_name'])
1120 obj = Repository.get_by_repo_name(scope['repo_name'])
1122 if obj:
1121 if obj:
1123 scope['repo_id'] = obj.repo_id
1122 scope['repo_id'] = obj.repo_id
1124 _scope = collections.OrderedDict()
1123 _scope = collections.OrderedDict()
1125 _scope['repo_id'] = -1
1124 _scope['repo_id'] = -1
1126 _scope['user_group_id'] = -1
1125 _scope['user_group_id'] = -1
1127 _scope['repo_group_id'] = -1
1126 _scope['repo_group_id'] = -1
1128
1127
1129 for k in sorted(scope.keys()):
1128 for k in sorted(scope.keys()):
1130 _scope[k] = scope[k]
1129 _scope[k] = scope[k]
1131
1130
1132 # store in cache to mimic how the @LazyProperty works,
1131 # store in cache to mimic how the @LazyProperty works,
1133 # the difference here is that we use the unique key calculated
1132 # the difference here is that we use the unique key calculated
1134 # from params and values
1133 # from params and values
1135 return self.get_perms(user=self, cache=None, scope=_scope)
1134 return self.get_perms(user=self, cache=None, scope=_scope)
1136
1135
1137 def get_instance(self):
1136 def get_instance(self):
1138 return User.get(self.user_id)
1137 return User.get(self.user_id)
1139
1138
1140 def propagate_data(self):
1139 def propagate_data(self):
1141 """
1140 """
1142 Fills in user data and propagates values to this instance. Maps fetched
1141 Fills in user data and propagates values to this instance. Maps fetched
1143 user attributes to this class instance attributes
1142 user attributes to this class instance attributes
1144 """
1143 """
1145 log.debug('AuthUser: starting data propagation for new potential user')
1144 log.debug('AuthUser: starting data propagation for new potential user')
1146 user_model = UserModel()
1145 user_model = UserModel()
1147 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1146 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1148 is_user_loaded = False
1147 is_user_loaded = False
1149
1148
1150 # lookup by userid
1149 # lookup by userid
1151 if self.user_id is not None and self.user_id != anon_user.user_id:
1150 if self.user_id is not None and self.user_id != anon_user.user_id:
1152 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1151 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1153 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1152 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1154
1153
1155 # try go get user by api key
1154 # try go get user by api key
1156 elif self._api_key and self._api_key != anon_user.api_key:
1155 elif self._api_key and self._api_key != anon_user.api_key:
1157 log.debug('Trying Auth User lookup by API KEY: `...%s`', self._api_key[-4:])
1156 log.debug('Trying Auth User lookup by API KEY: `...%s`', self._api_key[-4:])
1158 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1157 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1159
1158
1160 # lookup by username
1159 # lookup by username
1161 elif self.username:
1160 elif self.username:
1162 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1161 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1163 is_user_loaded = user_model.fill_data(self, username=self.username)
1162 is_user_loaded = user_model.fill_data(self, username=self.username)
1164 else:
1163 else:
1165 log.debug('No data in %s that could been used to log in', self)
1164 log.debug('No data in %s that could been used to log in', self)
1166
1165
1167 if not is_user_loaded:
1166 if not is_user_loaded:
1168 log.debug(
1167 log.debug(
1169 'Failed to load user. Fallback to default user %s', anon_user)
1168 'Failed to load user. Fallback to default user %s', anon_user)
1170 # if we cannot authenticate user try anonymous
1169 # if we cannot authenticate user try anonymous
1171 if anon_user.active:
1170 if anon_user.active:
1172 log.debug('default user is active, using it as a session user')
1171 log.debug('default user is active, using it as a session user')
1173 user_model.fill_data(self, user_id=anon_user.user_id)
1172 user_model.fill_data(self, user_id=anon_user.user_id)
1174 # then we set this user is logged in
1173 # then we set this user is logged in
1175 self.is_authenticated = True
1174 self.is_authenticated = True
1176 else:
1175 else:
1177 log.debug('default user is NOT active')
1176 log.debug('default user is NOT active')
1178 # in case of disabled anonymous user we reset some of the
1177 # in case of disabled anonymous user we reset some of the
1179 # parameters so such user is "corrupted", skipping the fill_data
1178 # parameters so such user is "corrupted", skipping the fill_data
1180 for attr in ['user_id', 'username', 'admin', 'active']:
1179 for attr in ['user_id', 'username', 'admin', 'active']:
1181 setattr(self, attr, None)
1180 setattr(self, attr, None)
1182 self.is_authenticated = False
1181 self.is_authenticated = False
1183
1182
1184 if not self.username:
1183 if not self.username:
1185 self.username = 'None'
1184 self.username = 'None'
1186
1185
1187 log.debug('AuthUser: propagated user is now %s', self)
1186 log.debug('AuthUser: propagated user is now %s', self)
1188
1187
1189 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1188 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1190 calculate_super_admin=False, cache=None):
1189 calculate_super_admin=False, cache=None):
1191 """
1190 """
1192 Fills user permission attribute with permissions taken from database
1191 Fills user permission attribute with permissions taken from database
1193 works for permissions given for repositories, and for permissions that
1192 works for permissions given for repositories, and for permissions that
1194 are granted to groups
1193 are granted to groups
1195
1194
1196 :param user: instance of User object from database
1195 :param user: instance of User object from database
1197 :param explicit: In case there are permissions both for user and a group
1196 :param explicit: In case there are permissions both for user and a group
1198 that user is part of, explicit flag will defiine if user will
1197 that user is part of, explicit flag will defiine if user will
1199 explicitly override permissions from group, if it's False it will
1198 explicitly override permissions from group, if it's False it will
1200 make decision based on the algo
1199 make decision based on the algo
1201 :param algo: algorithm to decide what permission should be choose if
1200 :param algo: algorithm to decide what permission should be choose if
1202 it's multiple defined, eg user in two different groups. It also
1201 it's multiple defined, eg user in two different groups. It also
1203 decides if explicit flag is turned off how to specify the permission
1202 decides if explicit flag is turned off how to specify the permission
1204 for case when user is in a group + have defined separate permission
1203 for case when user is in a group + have defined separate permission
1205 :param calculate_super_admin: calculate permissions for super-admin in the
1204 :param calculate_super_admin: calculate permissions for super-admin in the
1206 same way as for regular user without speedups
1205 same way as for regular user without speedups
1207 :param cache: Use caching for calculation, None = let the cache backend decide
1206 :param cache: Use caching for calculation, None = let the cache backend decide
1208 """
1207 """
1209 user_id = user.user_id
1208 user_id = user.user_id
1210 user_is_admin = user.is_admin
1209 user_is_admin = user.is_admin
1211
1210
1212 # inheritance of global permissions like create repo/fork repo etc
1211 # inheritance of global permissions like create repo/fork repo etc
1213 user_inherit_default_permissions = user.inherit_default_permissions
1212 user_inherit_default_permissions = user.inherit_default_permissions
1214
1213
1215 cache_seconds = safe_int(
1214 cache_seconds = safe_int(
1216 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1215 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1217
1216
1218 if cache is None:
1217 if cache is None:
1219 # let the backend cache decide
1218 # let the backend cache decide
1220 cache_on = cache_seconds > 0
1219 cache_on = cache_seconds > 0
1221 else:
1220 else:
1222 cache_on = cache
1221 cache_on = cache
1223
1222
1224 log.debug(
1223 log.debug(
1225 'Computing PERMISSION tree for user %s scope `%s` '
1224 'Computing PERMISSION tree for user %s scope `%s` '
1226 'with caching: %s[TTL: %ss]', user, scope, cache_on, cache_seconds or 0)
1225 'with caching: %s[TTL: %ss]', user, scope, cache_on, cache_seconds or 0)
1227
1226
1228 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1227 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1229 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1228 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1230
1229
1231 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1230 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1232 condition=cache_on)
1231 condition=cache_on)
1233 def compute_perm_tree(cache_name, cache_ver,
1232 def compute_perm_tree(cache_name, cache_ver,
1234 user_id, scope, user_is_admin,user_inherit_default_permissions,
1233 user_id, scope, user_is_admin,user_inherit_default_permissions,
1235 explicit, algo, calculate_super_admin):
1234 explicit, algo, calculate_super_admin):
1236 return _cached_perms_data(
1235 return _cached_perms_data(
1237 user_id, scope, user_is_admin, user_inherit_default_permissions,
1236 user_id, scope, user_is_admin, user_inherit_default_permissions,
1238 explicit, algo, calculate_super_admin)
1237 explicit, algo, calculate_super_admin)
1239
1238
1240 start = time.time()
1239 start = time.time()
1241 result = compute_perm_tree(
1240 result = compute_perm_tree(
1242 'permissions', 'v1', user_id, scope, user_is_admin,
1241 'permissions', 'v1', user_id, scope, user_is_admin,
1243 user_inherit_default_permissions, explicit, algo,
1242 user_inherit_default_permissions, explicit, algo,
1244 calculate_super_admin)
1243 calculate_super_admin)
1245
1244
1246 result_repr = []
1245 result_repr = []
1247 for k in result:
1246 for k in result:
1248 result_repr.append((k, len(result[k])))
1247 result_repr.append((k, len(result[k])))
1249 total = time.time() - start
1248 total = time.time() - start
1250 log.debug('PERMISSION tree for user %s computed in %.4fs: %s',
1249 log.debug('PERMISSION tree for user %s computed in %.4fs: %s',
1251 user, total, result_repr)
1250 user, total, result_repr)
1252
1251
1253 return result
1252 return result
1254
1253
1255 @property
1254 @property
1256 def is_default(self):
1255 def is_default(self):
1257 return self.username == User.DEFAULT_USER
1256 return self.username == User.DEFAULT_USER
1258
1257
1259 @property
1258 @property
1260 def is_admin(self):
1259 def is_admin(self):
1261 return self.admin
1260 return self.admin
1262
1261
1263 @property
1262 @property
1264 def is_user_object(self):
1263 def is_user_object(self):
1265 return self.user_id is not None
1264 return self.user_id is not None
1266
1265
1267 @property
1266 @property
1268 def repositories_admin(self):
1267 def repositories_admin(self):
1269 """
1268 """
1270 Returns list of repositories you're an admin of
1269 Returns list of repositories you're an admin of
1271 """
1270 """
1272 return [
1271 return [
1273 x[0] for x in self.permissions['repositories'].items()
1272 x[0] for x in self.permissions['repositories'].items()
1274 if x[1] == 'repository.admin']
1273 if x[1] == 'repository.admin']
1275
1274
1276 @property
1275 @property
1277 def repository_groups_admin(self):
1276 def repository_groups_admin(self):
1278 """
1277 """
1279 Returns list of repository groups you're an admin of
1278 Returns list of repository groups you're an admin of
1280 """
1279 """
1281 return [
1280 return [
1282 x[0] for x in self.permissions['repositories_groups'].items()
1281 x[0] for x in self.permissions['repositories_groups'].items()
1283 if x[1] == 'group.admin']
1282 if x[1] == 'group.admin']
1284
1283
1285 @property
1284 @property
1286 def user_groups_admin(self):
1285 def user_groups_admin(self):
1287 """
1286 """
1288 Returns list of user groups you're an admin of
1287 Returns list of user groups you're an admin of
1289 """
1288 """
1290 return [
1289 return [
1291 x[0] for x in self.permissions['user_groups'].items()
1290 x[0] for x in self.permissions['user_groups'].items()
1292 if x[1] == 'usergroup.admin']
1291 if x[1] == 'usergroup.admin']
1293
1292
1294 def repo_acl_ids_from_stack(self, perms=None, prefix_filter=None, cache=False):
1293 def repo_acl_ids_from_stack(self, perms=None, prefix_filter=None, cache=False):
1295 if not perms:
1294 if not perms:
1296 perms = AuthUser.repo_read_perms
1295 perms = AuthUser.repo_read_perms
1297 allowed_ids = []
1296 allowed_ids = []
1298 for k, stack_data in self.permissions['repositories'].perm_origin_stack.items():
1297 for k, stack_data in self.permissions['repositories'].perm_origin_stack.items():
1299 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1298 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1300 if prefix_filter and not k.startswith(prefix_filter):
1299 if prefix_filter and not k.startswith(prefix_filter):
1301 continue
1300 continue
1302 if perm in perms:
1301 if perm in perms:
1303 allowed_ids.append(obj_id)
1302 allowed_ids.append(obj_id)
1304 return allowed_ids
1303 return allowed_ids
1305
1304
1306 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1305 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1307 """
1306 """
1308 Returns list of repository ids that user have access to based on given
1307 Returns list of repository ids that user have access to based on given
1309 perms. The cache flag should be only used in cases that are used for
1308 perms. The cache flag should be only used in cases that are used for
1310 display purposes, NOT IN ANY CASE for permission checks.
1309 display purposes, NOT IN ANY CASE for permission checks.
1311 """
1310 """
1312 from rhodecode.model.scm import RepoList
1311 from rhodecode.model.scm import RepoList
1313 if not perms:
1312 if not perms:
1314 perms = AuthUser.repo_read_perms
1313 perms = AuthUser.repo_read_perms
1315
1314
1316 def _cached_repo_acl(user_id, perm_def, _name_filter):
1315 def _cached_repo_acl(user_id, perm_def, _name_filter):
1317 qry = Repository.query()
1316 qry = Repository.query()
1318 if _name_filter:
1317 if _name_filter:
1319 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1318 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1320 qry = qry.filter(
1319 qry = qry.filter(
1321 Repository.repo_name.ilike(ilike_expression))
1320 Repository.repo_name.ilike(ilike_expression))
1322
1321
1323 return [x.repo_id for x in
1322 return [x.repo_id for x in
1324 RepoList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1323 RepoList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1325
1324
1326 return _cached_repo_acl(self.user_id, perms, name_filter)
1325 return _cached_repo_acl(self.user_id, perms, name_filter)
1327
1326
1328 def repo_group_acl_ids_from_stack(self, perms=None, prefix_filter=None, cache=False):
1327 def repo_group_acl_ids_from_stack(self, perms=None, prefix_filter=None, cache=False):
1329 if not perms:
1328 if not perms:
1330 perms = AuthUser.repo_group_read_perms
1329 perms = AuthUser.repo_group_read_perms
1331 allowed_ids = []
1330 allowed_ids = []
1332 for k, stack_data in self.permissions['repositories_groups'].perm_origin_stack.items():
1331 for k, stack_data in self.permissions['repositories_groups'].perm_origin_stack.items():
1333 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1332 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1334 if prefix_filter and not k.startswith(prefix_filter):
1333 if prefix_filter and not k.startswith(prefix_filter):
1335 continue
1334 continue
1336 if perm in perms:
1335 if perm in perms:
1337 allowed_ids.append(obj_id)
1336 allowed_ids.append(obj_id)
1338 return allowed_ids
1337 return allowed_ids
1339
1338
1340 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1339 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1341 """
1340 """
1342 Returns list of repository group ids that user have access to based on given
1341 Returns list of repository group ids that user have access to based on given
1343 perms. The cache flag should be only used in cases that are used for
1342 perms. The cache flag should be only used in cases that are used for
1344 display purposes, NOT IN ANY CASE for permission checks.
1343 display purposes, NOT IN ANY CASE for permission checks.
1345 """
1344 """
1346 from rhodecode.model.scm import RepoGroupList
1345 from rhodecode.model.scm import RepoGroupList
1347 if not perms:
1346 if not perms:
1348 perms = AuthUser.repo_group_read_perms
1347 perms = AuthUser.repo_group_read_perms
1349
1348
1350 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1349 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1351 qry = RepoGroup.query()
1350 qry = RepoGroup.query()
1352 if _name_filter:
1351 if _name_filter:
1353 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1352 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1354 qry = qry.filter(
1353 qry = qry.filter(
1355 RepoGroup.group_name.ilike(ilike_expression))
1354 RepoGroup.group_name.ilike(ilike_expression))
1356
1355
1357 return [x.group_id for x in
1356 return [x.group_id for x in
1358 RepoGroupList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1357 RepoGroupList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1359
1358
1360 return _cached_repo_group_acl(self.user_id, perms, name_filter)
1359 return _cached_repo_group_acl(self.user_id, perms, name_filter)
1361
1360
1362 def user_group_acl_ids_from_stack(self, perms=None, cache=False):
1361 def user_group_acl_ids_from_stack(self, perms=None, cache=False):
1363 if not perms:
1362 if not perms:
1364 perms = AuthUser.user_group_read_perms
1363 perms = AuthUser.user_group_read_perms
1365 allowed_ids = []
1364 allowed_ids = []
1366 for k, stack_data in self.permissions['user_groups'].perm_origin_stack.items():
1365 for k, stack_data in self.permissions['user_groups'].perm_origin_stack.items():
1367 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1366 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1368 if perm in perms:
1367 if perm in perms:
1369 allowed_ids.append(obj_id)
1368 allowed_ids.append(obj_id)
1370 return allowed_ids
1369 return allowed_ids
1371
1370
1372 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1371 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1373 """
1372 """
1374 Returns list of user group ids that user have access to based on given
1373 Returns list of user group ids that user have access to based on given
1375 perms. The cache flag should be only used in cases that are used for
1374 perms. The cache flag should be only used in cases that are used for
1376 display purposes, NOT IN ANY CASE for permission checks.
1375 display purposes, NOT IN ANY CASE for permission checks.
1377 """
1376 """
1378 from rhodecode.model.scm import UserGroupList
1377 from rhodecode.model.scm import UserGroupList
1379 if not perms:
1378 if not perms:
1380 perms = AuthUser.user_group_read_perms
1379 perms = AuthUser.user_group_read_perms
1381
1380
1382 def _cached_user_group_acl(user_id, perm_def, name_filter):
1381 def _cached_user_group_acl(user_id, perm_def, name_filter):
1383 qry = UserGroup.query()
1382 qry = UserGroup.query()
1384 if name_filter:
1383 if name_filter:
1385 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1384 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1386 qry = qry.filter(
1385 qry = qry.filter(
1387 UserGroup.users_group_name.ilike(ilike_expression))
1386 UserGroup.users_group_name.ilike(ilike_expression))
1388
1387
1389 return [x.users_group_id for x in
1388 return [x.users_group_id for x in
1390 UserGroupList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1389 UserGroupList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1391
1390
1392 return _cached_user_group_acl(self.user_id, perms, name_filter)
1391 return _cached_user_group_acl(self.user_id, perms, name_filter)
1393
1392
1394 @property
1393 @property
1395 def ip_allowed(self):
1394 def ip_allowed(self):
1396 """
1395 """
1397 Checks if ip_addr used in constructor is allowed from defined list of
1396 Checks if ip_addr used in constructor is allowed from defined list of
1398 allowed ip_addresses for user
1397 allowed ip_addresses for user
1399
1398
1400 :returns: boolean, True if ip is in allowed ip range
1399 :returns: boolean, True if ip is in allowed ip range
1401 """
1400 """
1402 # check IP
1401 # check IP
1403 inherit = self.inherit_default_permissions
1402 inherit = self.inherit_default_permissions
1404 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1403 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1405 inherit_from_default=inherit)
1404 inherit_from_default=inherit)
1406 @property
1405 @property
1407 def personal_repo_group(self):
1406 def personal_repo_group(self):
1408 return RepoGroup.get_user_personal_repo_group(self.user_id)
1407 return RepoGroup.get_user_personal_repo_group(self.user_id)
1409
1408
1410 @LazyProperty
1409 @LazyProperty
1411 def feed_token(self):
1410 def feed_token(self):
1412 return self.get_instance().feed_token
1411 return self.get_instance().feed_token
1413
1412
1414 @LazyProperty
1413 @LazyProperty
1415 def artifact_token(self):
1414 def artifact_token(self):
1416 return self.get_instance().artifact_token
1415 return self.get_instance().artifact_token
1417
1416
1418 @classmethod
1417 @classmethod
1419 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1418 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1420 allowed_ips = AuthUser.get_allowed_ips(
1419 allowed_ips = AuthUser.get_allowed_ips(
1421 user_id, cache=True, inherit_from_default=inherit_from_default)
1420 user_id, cache=True, inherit_from_default=inherit_from_default)
1422 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1421 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1423 log.debug('IP:%s for user %s is in range of %s',
1422 log.debug('IP:%s for user %s is in range of %s',
1424 ip_addr, user_id, allowed_ips)
1423 ip_addr, user_id, allowed_ips)
1425 return True
1424 return True
1426 else:
1425 else:
1427 log.info('Access for IP:%s forbidden for user %s, '
1426 log.info('Access for IP:%s forbidden for user %s, '
1428 'not in %s', ip_addr, user_id, allowed_ips)
1427 'not in %s', ip_addr, user_id, allowed_ips)
1429 return False
1428 return False
1430
1429
1431 def get_branch_permissions(self, repo_name, perms=None):
1430 def get_branch_permissions(self, repo_name, perms=None):
1432 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1431 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1433 branch_perms = perms.get('repository_branches', {})
1432 branch_perms = perms.get('repository_branches', {})
1434 if not branch_perms:
1433 if not branch_perms:
1435 return {}
1434 return {}
1436 repo_branch_perms = branch_perms.get(repo_name)
1435 repo_branch_perms = branch_perms.get(repo_name)
1437 return repo_branch_perms or {}
1436 return repo_branch_perms or {}
1438
1437
1439 def get_rule_and_branch_permission(self, repo_name, branch_name):
1438 def get_rule_and_branch_permission(self, repo_name, branch_name):
1440 """
1439 """
1441 Check if this AuthUser has defined any permissions for branches. If any of
1440 Check if this AuthUser has defined any permissions for branches. If any of
1442 the rules match in order, we return the matching permissions
1441 the rules match in order, we return the matching permissions
1443 """
1442 """
1444
1443
1445 rule = default_perm = ''
1444 rule = default_perm = ''
1446
1445
1447 repo_branch_perms = self.get_branch_permissions(repo_name=repo_name)
1446 repo_branch_perms = self.get_branch_permissions(repo_name=repo_name)
1448 if not repo_branch_perms:
1447 if not repo_branch_perms:
1449 return rule, default_perm
1448 return rule, default_perm
1450
1449
1451 # now calculate the permissions
1450 # now calculate the permissions
1452 for pattern, branch_perm in repo_branch_perms.items():
1451 for pattern, branch_perm in repo_branch_perms.items():
1453 if fnmatch.fnmatch(branch_name, pattern):
1452 if fnmatch.fnmatch(branch_name, pattern):
1454 rule = '`{}`=>{}'.format(pattern, branch_perm)
1453 rule = '`{}`=>{}'.format(pattern, branch_perm)
1455 return rule, branch_perm
1454 return rule, branch_perm
1456
1455
1457 return rule, default_perm
1456 return rule, default_perm
1458
1457
1459 def __repr__(self):
1458 def __repr__(self):
1460 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1459 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1461 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1460 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1462
1461
1463 def set_authenticated(self, authenticated=True):
1462 def set_authenticated(self, authenticated=True):
1464 if self.user_id != self.anonymous_user.user_id:
1463 if self.user_id != self.anonymous_user.user_id:
1465 self.is_authenticated = authenticated
1464 self.is_authenticated = authenticated
1466
1465
1467 def get_cookie_store(self):
1466 def get_cookie_store(self):
1468 return {
1467 return {
1469 'username': self.username,
1468 'username': self.username,
1470 'password': md5(self.password or ''),
1469 'password': md5(self.password or ''),
1471 'user_id': self.user_id,
1470 'user_id': self.user_id,
1472 'is_authenticated': self.is_authenticated
1471 'is_authenticated': self.is_authenticated
1473 }
1472 }
1474
1473
1475 @classmethod
1474 @classmethod
1476 def from_cookie_store(cls, cookie_store):
1475 def from_cookie_store(cls, cookie_store):
1477 """
1476 """
1478 Creates AuthUser from a cookie store
1477 Creates AuthUser from a cookie store
1479
1478
1480 :param cls:
1479 :param cls:
1481 :param cookie_store:
1480 :param cookie_store:
1482 """
1481 """
1483 user_id = cookie_store.get('user_id')
1482 user_id = cookie_store.get('user_id')
1484 username = cookie_store.get('username')
1483 username = cookie_store.get('username')
1485 api_key = cookie_store.get('api_key')
1484 api_key = cookie_store.get('api_key')
1486 return AuthUser(user_id, api_key, username)
1485 return AuthUser(user_id, api_key, username)
1487
1486
1488 @classmethod
1487 @classmethod
1489 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1488 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1490 _set = set()
1489 _set = set()
1491
1490
1492 if inherit_from_default:
1491 if inherit_from_default:
1493 def_user_id = User.get_default_user(cache=True).user_id
1492 def_user_id = User.get_default_user(cache=True).user_id
1494 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1493 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1495 if cache:
1494 if cache:
1496 default_ips = default_ips.options(
1495 default_ips = default_ips.options(
1497 FromCache("sql_cache_short", "get_user_ips_default"))
1496 FromCache("sql_cache_short", "get_user_ips_default"))
1498
1497
1499 # populate from default user
1498 # populate from default user
1500 for ip in default_ips:
1499 for ip in default_ips:
1501 try:
1500 try:
1502 _set.add(ip.ip_addr)
1501 _set.add(ip.ip_addr)
1503 except ObjectDeletedError:
1502 except ObjectDeletedError:
1504 # since we use heavy caching sometimes it happens that
1503 # since we use heavy caching sometimes it happens that
1505 # we get deleted objects here, we just skip them
1504 # we get deleted objects here, we just skip them
1506 pass
1505 pass
1507
1506
1508 # NOTE:(marcink) we don't want to load any rules for empty
1507 # NOTE:(marcink) we don't want to load any rules for empty
1509 # user_id which is the case of access of non logged users when anonymous
1508 # user_id which is the case of access of non logged users when anonymous
1510 # access is disabled
1509 # access is disabled
1511 user_ips = []
1510 user_ips = []
1512 if user_id:
1511 if user_id:
1513 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1512 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1514 if cache:
1513 if cache:
1515 user_ips = user_ips.options(
1514 user_ips = user_ips.options(
1516 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1515 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1517
1516
1518 for ip in user_ips:
1517 for ip in user_ips:
1519 try:
1518 try:
1520 _set.add(ip.ip_addr)
1519 _set.add(ip.ip_addr)
1521 except ObjectDeletedError:
1520 except ObjectDeletedError:
1522 # since we use heavy caching sometimes it happens that we get
1521 # since we use heavy caching sometimes it happens that we get
1523 # deleted objects here, we just skip them
1522 # deleted objects here, we just skip them
1524 pass
1523 pass
1525 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1524 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1526
1525
1527
1526
1528 def set_available_permissions(settings):
1527 def set_available_permissions(settings):
1529 """
1528 """
1530 This function will propagate pyramid settings with all available defined
1529 This function will propagate pyramid settings with all available defined
1531 permission given in db. We don't want to check each time from db for new
1530 permission given in db. We don't want to check each time from db for new
1532 permissions since adding a new permission also requires application restart
1531 permissions since adding a new permission also requires application restart
1533 ie. to decorate new views with the newly created permission
1532 ie. to decorate new views with the newly created permission
1534
1533
1535 :param settings: current pyramid registry.settings
1534 :param settings: current pyramid registry.settings
1536
1535
1537 """
1536 """
1538 log.debug('auth: getting information about all available permissions')
1537 log.debug('auth: getting information about all available permissions')
1539 try:
1538 try:
1540 sa = meta.Session
1539 sa = meta.Session
1541 all_perms = sa.query(Permission).all()
1540 all_perms = sa.query(Permission).all()
1542 settings.setdefault('available_permissions',
1541 settings.setdefault('available_permissions',
1543 [x.permission_name for x in all_perms])
1542 [x.permission_name for x in all_perms])
1544 log.debug('auth: set available permissions')
1543 log.debug('auth: set available permissions')
1545 except Exception:
1544 except Exception:
1546 log.exception('Failed to fetch permissions from the database.')
1545 log.exception('Failed to fetch permissions from the database.')
1547 raise
1546 raise
1548
1547
1549
1548
1550 def get_csrf_token(session, force_new=False, save_if_missing=True):
1549 def get_csrf_token(session, force_new=False, save_if_missing=True):
1551 """
1550 """
1552 Return the current authentication token, creating one if one doesn't
1551 Return the current authentication token, creating one if one doesn't
1553 already exist and the save_if_missing flag is present.
1552 already exist and the save_if_missing flag is present.
1554
1553
1555 :param session: pass in the pyramid session, else we use the global ones
1554 :param session: pass in the pyramid session, else we use the global ones
1556 :param force_new: force to re-generate the token and store it in session
1555 :param force_new: force to re-generate the token and store it in session
1557 :param save_if_missing: save the newly generated token if it's missing in
1556 :param save_if_missing: save the newly generated token if it's missing in
1558 session
1557 session
1559 """
1558 """
1560 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1559 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1561 # from pyramid.csrf import get_csrf_token
1560 # from pyramid.csrf import get_csrf_token
1562
1561
1563 if (csrf_token_key not in session and save_if_missing) or force_new:
1562 if (csrf_token_key not in session and save_if_missing) or force_new:
1564 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1563 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1565 session[csrf_token_key] = token
1564 session[csrf_token_key] = token
1566 if hasattr(session, 'save'):
1565 if hasattr(session, 'save'):
1567 session.save()
1566 session.save()
1568 return session.get(csrf_token_key)
1567 return session.get(csrf_token_key)
1569
1568
1570
1569
1571 def get_request(perm_class_instance):
1570 def get_request(perm_class_instance):
1572 from pyramid.threadlocal import get_current_request
1571 from pyramid.threadlocal import get_current_request
1573 pyramid_request = get_current_request()
1572 pyramid_request = get_current_request()
1574 return pyramid_request
1573 return pyramid_request
1575
1574
1576
1575
1577 # CHECK DECORATORS
1576 # CHECK DECORATORS
1578 class CSRFRequired(object):
1577 class CSRFRequired(object):
1579 """
1578 """
1580 Decorator for authenticating a form
1579 Decorator for authenticating a form
1581
1580
1582 This decorator uses an authorization token stored in the client's
1581 This decorator uses an authorization token stored in the client's
1583 session for prevention of certain Cross-site request forgery (CSRF)
1582 session for prevention of certain Cross-site request forgery (CSRF)
1584 attacks (See
1583 attacks (See
1585 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1584 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1586 information).
1585 information).
1587
1586
1588 For use with the ``secure_form`` helper functions.
1587 For use with the ``secure_form`` helper functions.
1589
1588
1590 """
1589 """
1591 def __init__(self, token=csrf_token_key, header='X-CSRF-Token', except_methods=None):
1590 def __init__(self, token=csrf_token_key, header='X-CSRF-Token', except_methods=None):
1592 self.token = token
1591 self.token = token
1593 self.header = header
1592 self.header = header
1594 self.except_methods = except_methods or []
1593 self.except_methods = except_methods or []
1595
1594
1596 def __call__(self, func):
1595 def __call__(self, func):
1597 return get_cython_compat_decorator(self.__wrapper, func)
1596 return get_cython_compat_decorator(self.__wrapper, func)
1598
1597
1599 def _get_csrf(self, _request):
1598 def _get_csrf(self, _request):
1600 return _request.POST.get(self.token, _request.headers.get(self.header))
1599 return _request.POST.get(self.token, _request.headers.get(self.header))
1601
1600
1602 def check_csrf(self, _request, cur_token):
1601 def check_csrf(self, _request, cur_token):
1603 supplied_token = self._get_csrf(_request)
1602 supplied_token = self._get_csrf(_request)
1604 return supplied_token and supplied_token == cur_token
1603 return supplied_token and supplied_token == cur_token
1605
1604
1606 def _get_request(self):
1605 def _get_request(self):
1607 return get_request(self)
1606 return get_request(self)
1608
1607
1609 def __wrapper(self, func, *fargs, **fkwargs):
1608 def __wrapper(self, func, *fargs, **fkwargs):
1610 request = self._get_request()
1609 request = self._get_request()
1611
1610
1612 if request.method in self.except_methods:
1611 if request.method in self.except_methods:
1613 return func(*fargs, **fkwargs)
1612 return func(*fargs, **fkwargs)
1614
1613
1615 cur_token = get_csrf_token(request.session, save_if_missing=False)
1614 cur_token = get_csrf_token(request.session, save_if_missing=False)
1616 if self.check_csrf(request, cur_token):
1615 if self.check_csrf(request, cur_token):
1617 if request.POST.get(self.token):
1616 if request.POST.get(self.token):
1618 del request.POST[self.token]
1617 del request.POST[self.token]
1619 return func(*fargs, **fkwargs)
1618 return func(*fargs, **fkwargs)
1620 else:
1619 else:
1621 reason = 'token-missing'
1620 reason = 'token-missing'
1622 supplied_token = self._get_csrf(request)
1621 supplied_token = self._get_csrf(request)
1623 if supplied_token and cur_token != supplied_token:
1622 if supplied_token and cur_token != supplied_token:
1624 reason = 'token-mismatch [%s:%s]' % (
1623 reason = 'token-mismatch [%s:%s]' % (
1625 cur_token or ''[:6], supplied_token or ''[:6])
1624 cur_token or ''[:6], supplied_token or ''[:6])
1626
1625
1627 csrf_message = \
1626 csrf_message = \
1628 ("Cross-site request forgery detected, request denied. See "
1627 ("Cross-site request forgery detected, request denied. See "
1629 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1628 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1630 "more information.")
1629 "more information.")
1631 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1630 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1632 'REMOTE_ADDR:%s, HEADERS:%s' % (
1631 'REMOTE_ADDR:%s, HEADERS:%s' % (
1633 request, reason, request.remote_addr, request.headers))
1632 request, reason, request.remote_addr, request.headers))
1634
1633
1635 raise HTTPForbidden(explanation=csrf_message)
1634 raise HTTPForbidden(explanation=csrf_message)
1636
1635
1637
1636
1638 class LoginRequired(object):
1637 class LoginRequired(object):
1639 """
1638 """
1640 Must be logged in to execute this function else
1639 Must be logged in to execute this function else
1641 redirect to login page
1640 redirect to login page
1642
1641
1643 :param api_access: if enabled this checks only for valid auth token
1642 :param api_access: if enabled this checks only for valid auth token
1644 and grants access based on valid token
1643 and grants access based on valid token
1645 """
1644 """
1646 def __init__(self, auth_token_access=None):
1645 def __init__(self, auth_token_access=None):
1647 self.auth_token_access = auth_token_access
1646 self.auth_token_access = auth_token_access
1648 if self.auth_token_access:
1647 if self.auth_token_access:
1649 valid_type = set(auth_token_access).intersection(set(UserApiKeys.ROLES))
1648 valid_type = set(auth_token_access).intersection(set(UserApiKeys.ROLES))
1650 if not valid_type:
1649 if not valid_type:
1651 raise ValueError('auth_token_access must be on of {}, got {}'.format(
1650 raise ValueError('auth_token_access must be on of {}, got {}'.format(
1652 UserApiKeys.ROLES, auth_token_access))
1651 UserApiKeys.ROLES, auth_token_access))
1653
1652
1654 def __call__(self, func):
1653 def __call__(self, func):
1655 return get_cython_compat_decorator(self.__wrapper, func)
1654 return get_cython_compat_decorator(self.__wrapper, func)
1656
1655
1657 def _get_request(self):
1656 def _get_request(self):
1658 return get_request(self)
1657 return get_request(self)
1659
1658
1660 def __wrapper(self, func, *fargs, **fkwargs):
1659 def __wrapper(self, func, *fargs, **fkwargs):
1661 from rhodecode.lib import helpers as h
1660 from rhodecode.lib import helpers as h
1662 cls = fargs[0]
1661 cls = fargs[0]
1663 user = cls._rhodecode_user
1662 user = cls._rhodecode_user
1664 request = self._get_request()
1663 request = self._get_request()
1665 _ = request.translate
1664 _ = request.translate
1666
1665
1667 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1666 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1668 log.debug('Starting login restriction checks for user: %s', user)
1667 log.debug('Starting login restriction checks for user: %s', user)
1669 # check if our IP is allowed
1668 # check if our IP is allowed
1670 ip_access_valid = True
1669 ip_access_valid = True
1671 if not user.ip_allowed:
1670 if not user.ip_allowed:
1672 h.flash(h.literal(_('IP {} not allowed'.format(user.ip_addr))),
1671 h.flash(h.literal(_('IP {} not allowed'.format(user.ip_addr))),
1673 category='warning')
1672 category='warning')
1674 ip_access_valid = False
1673 ip_access_valid = False
1675
1674
1676 # we used stored token that is extract from GET or URL param (if any)
1675 # we used stored token that is extract from GET or URL param (if any)
1677 _auth_token = request.user_auth_token
1676 _auth_token = request.user_auth_token
1678
1677
1679 # check if we used an AUTH_TOKEN and it's a valid one
1678 # check if we used an AUTH_TOKEN and it's a valid one
1680 # defined white-list of controllers which API access will be enabled
1679 # defined white-list of controllers which API access will be enabled
1681 whitelist = None
1680 whitelist = None
1682 if self.auth_token_access:
1681 if self.auth_token_access:
1683 # since this location is allowed by @LoginRequired decorator it's our
1682 # since this location is allowed by @LoginRequired decorator it's our
1684 # only whitelist
1683 # only whitelist
1685 whitelist = [loc]
1684 whitelist = [loc]
1686 auth_token_access_valid = allowed_auth_token_access(
1685 auth_token_access_valid = allowed_auth_token_access(
1687 loc, whitelist=whitelist, auth_token=_auth_token)
1686 loc, whitelist=whitelist, auth_token=_auth_token)
1688
1687
1689 # explicit controller is enabled or API is in our whitelist
1688 # explicit controller is enabled or API is in our whitelist
1690 if auth_token_access_valid:
1689 if auth_token_access_valid:
1691 log.debug('Checking AUTH TOKEN access for %s', cls)
1690 log.debug('Checking AUTH TOKEN access for %s', cls)
1692 db_user = user.get_instance()
1691 db_user = user.get_instance()
1693
1692
1694 if db_user:
1693 if db_user:
1695 if self.auth_token_access:
1694 if self.auth_token_access:
1696 roles = self.auth_token_access
1695 roles = self.auth_token_access
1697 else:
1696 else:
1698 roles = [UserApiKeys.ROLE_HTTP]
1697 roles = [UserApiKeys.ROLE_HTTP]
1699 log.debug('AUTH TOKEN: checking auth for user %s and roles %s',
1698 log.debug('AUTH TOKEN: checking auth for user %s and roles %s',
1700 db_user, roles)
1699 db_user, roles)
1701 token_match = db_user.authenticate_by_token(
1700 token_match = db_user.authenticate_by_token(
1702 _auth_token, roles=roles)
1701 _auth_token, roles=roles)
1703 else:
1702 else:
1704 log.debug('Unable to fetch db instance for auth user: %s', user)
1703 log.debug('Unable to fetch db instance for auth user: %s', user)
1705 token_match = False
1704 token_match = False
1706
1705
1707 if _auth_token and token_match:
1706 if _auth_token and token_match:
1708 auth_token_access_valid = True
1707 auth_token_access_valid = True
1709 log.debug('AUTH TOKEN ****%s is VALID', _auth_token[-4:])
1708 log.debug('AUTH TOKEN ****%s is VALID', _auth_token[-4:])
1710 else:
1709 else:
1711 auth_token_access_valid = False
1710 auth_token_access_valid = False
1712 if not _auth_token:
1711 if not _auth_token:
1713 log.debug("AUTH TOKEN *NOT* present in request")
1712 log.debug("AUTH TOKEN *NOT* present in request")
1714 else:
1713 else:
1715 log.warning("AUTH TOKEN ****%s *NOT* valid", _auth_token[-4:])
1714 log.warning("AUTH TOKEN ****%s *NOT* valid", _auth_token[-4:])
1716
1715
1717 log.debug('Checking if %s is authenticated @ %s', user.username, loc)
1716 log.debug('Checking if %s is authenticated @ %s', user.username, loc)
1718 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1717 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1719 else 'AUTH_TOKEN_AUTH'
1718 else 'AUTH_TOKEN_AUTH'
1720
1719
1721 if ip_access_valid and (
1720 if ip_access_valid and (
1722 user.is_authenticated or auth_token_access_valid):
1721 user.is_authenticated or auth_token_access_valid):
1723 log.info('user %s authenticating with:%s IS authenticated on func %s',
1722 log.info('user %s authenticating with:%s IS authenticated on func %s',
1724 user, reason, loc)
1723 user, reason, loc)
1725
1724
1726 return func(*fargs, **fkwargs)
1725 return func(*fargs, **fkwargs)
1727 else:
1726 else:
1728 log.warning(
1727 log.warning(
1729 'user %s authenticating with:%s NOT authenticated on '
1728 'user %s authenticating with:%s NOT authenticated on '
1730 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s',
1729 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s',
1731 user, reason, loc, ip_access_valid, auth_token_access_valid)
1730 user, reason, loc, ip_access_valid, auth_token_access_valid)
1732 # we preserve the get PARAM
1731 # we preserve the get PARAM
1733 came_from = get_came_from(request)
1732 came_from = get_came_from(request)
1734
1733
1735 log.debug('redirecting to login page with %s', came_from)
1734 log.debug('redirecting to login page with %s', came_from)
1736 raise HTTPFound(
1735 raise HTTPFound(
1737 h.route_path('login', _query={'came_from': came_from}))
1736 h.route_path('login', _query={'came_from': came_from}))
1738
1737
1739
1738
1740 class NotAnonymous(object):
1739 class NotAnonymous(object):
1741 """
1740 """
1742 Must be logged in to execute this function else
1741 Must be logged in to execute this function else
1743 redirect to login page
1742 redirect to login page
1744 """
1743 """
1745
1744
1746 def __call__(self, func):
1745 def __call__(self, func):
1747 return get_cython_compat_decorator(self.__wrapper, func)
1746 return get_cython_compat_decorator(self.__wrapper, func)
1748
1747
1749 def _get_request(self):
1748 def _get_request(self):
1750 return get_request(self)
1749 return get_request(self)
1751
1750
1752 def __wrapper(self, func, *fargs, **fkwargs):
1751 def __wrapper(self, func, *fargs, **fkwargs):
1753 import rhodecode.lib.helpers as h
1752 import rhodecode.lib.helpers as h
1754 cls = fargs[0]
1753 cls = fargs[0]
1755 self.user = cls._rhodecode_user
1754 self.user = cls._rhodecode_user
1756 request = self._get_request()
1755 request = self._get_request()
1757 _ = request.translate
1756 _ = request.translate
1758 log.debug('Checking if user is not anonymous @%s', cls)
1757 log.debug('Checking if user is not anonymous @%s', cls)
1759
1758
1760 anonymous = self.user.username == User.DEFAULT_USER
1759 anonymous = self.user.username == User.DEFAULT_USER
1761
1760
1762 if anonymous:
1761 if anonymous:
1763 came_from = get_came_from(request)
1762 came_from = get_came_from(request)
1764 h.flash(_('You need to be a registered user to '
1763 h.flash(_('You need to be a registered user to '
1765 'perform this action'),
1764 'perform this action'),
1766 category='warning')
1765 category='warning')
1767 raise HTTPFound(
1766 raise HTTPFound(
1768 h.route_path('login', _query={'came_from': came_from}))
1767 h.route_path('login', _query={'came_from': came_from}))
1769 else:
1768 else:
1770 return func(*fargs, **fkwargs)
1769 return func(*fargs, **fkwargs)
1771
1770
1772
1771
1773 class PermsDecorator(object):
1772 class PermsDecorator(object):
1774 """
1773 """
1775 Base class for controller decorators, we extract the current user from
1774 Base class for controller decorators, we extract the current user from
1776 the class itself, which has it stored in base controllers
1775 the class itself, which has it stored in base controllers
1777 """
1776 """
1778
1777
1779 def __init__(self, *required_perms):
1778 def __init__(self, *required_perms):
1780 self.required_perms = set(required_perms)
1779 self.required_perms = set(required_perms)
1781
1780
1782 def __call__(self, func):
1781 def __call__(self, func):
1783 return get_cython_compat_decorator(self.__wrapper, func)
1782 return get_cython_compat_decorator(self.__wrapper, func)
1784
1783
1785 def _get_request(self):
1784 def _get_request(self):
1786 return get_request(self)
1785 return get_request(self)
1787
1786
1788 def __wrapper(self, func, *fargs, **fkwargs):
1787 def __wrapper(self, func, *fargs, **fkwargs):
1789 import rhodecode.lib.helpers as h
1788 import rhodecode.lib.helpers as h
1790 cls = fargs[0]
1789 cls = fargs[0]
1791 _user = cls._rhodecode_user
1790 _user = cls._rhodecode_user
1792 request = self._get_request()
1791 request = self._get_request()
1793 _ = request.translate
1792 _ = request.translate
1794
1793
1795 log.debug('checking %s permissions %s for %s %s',
1794 log.debug('checking %s permissions %s for %s %s',
1796 self.__class__.__name__, self.required_perms, cls, _user)
1795 self.__class__.__name__, self.required_perms, cls, _user)
1797
1796
1798 if self.check_permissions(_user):
1797 if self.check_permissions(_user):
1799 log.debug('Permission granted for %s %s', cls, _user)
1798 log.debug('Permission granted for %s %s', cls, _user)
1800 return func(*fargs, **fkwargs)
1799 return func(*fargs, **fkwargs)
1801
1800
1802 else:
1801 else:
1803 log.debug('Permission denied for %s %s', cls, _user)
1802 log.debug('Permission denied for %s %s', cls, _user)
1804 anonymous = _user.username == User.DEFAULT_USER
1803 anonymous = _user.username == User.DEFAULT_USER
1805
1804
1806 if anonymous:
1805 if anonymous:
1807 came_from = get_came_from(self._get_request())
1806 came_from = get_came_from(self._get_request())
1808 h.flash(_('You need to be signed in to view this page'),
1807 h.flash(_('You need to be signed in to view this page'),
1809 category='warning')
1808 category='warning')
1810 raise HTTPFound(
1809 raise HTTPFound(
1811 h.route_path('login', _query={'came_from': came_from}))
1810 h.route_path('login', _query={'came_from': came_from}))
1812
1811
1813 else:
1812 else:
1814 # redirect with 404 to prevent resource discovery
1813 # redirect with 404 to prevent resource discovery
1815 raise HTTPNotFound()
1814 raise HTTPNotFound()
1816
1815
1817 def check_permissions(self, user):
1816 def check_permissions(self, user):
1818 """Dummy function for overriding"""
1817 """Dummy function for overriding"""
1819 raise NotImplementedError(
1818 raise NotImplementedError(
1820 'You have to write this function in child class')
1819 'You have to write this function in child class')
1821
1820
1822
1821
1823 class HasPermissionAllDecorator(PermsDecorator):
1822 class HasPermissionAllDecorator(PermsDecorator):
1824 """
1823 """
1825 Checks for access permission for all given predicates. All of them
1824 Checks for access permission for all given predicates. All of them
1826 have to be meet in order to fulfill the request
1825 have to be meet in order to fulfill the request
1827 """
1826 """
1828
1827
1829 def check_permissions(self, user):
1828 def check_permissions(self, user):
1830 perms = user.permissions_with_scope({})
1829 perms = user.permissions_with_scope({})
1831 if self.required_perms.issubset(perms['global']):
1830 if self.required_perms.issubset(perms['global']):
1832 return True
1831 return True
1833 return False
1832 return False
1834
1833
1835
1834
1836 class HasPermissionAnyDecorator(PermsDecorator):
1835 class HasPermissionAnyDecorator(PermsDecorator):
1837 """
1836 """
1838 Checks for access permission for any of given predicates. In order to
1837 Checks for access permission for any of given predicates. In order to
1839 fulfill the request any of predicates must be meet
1838 fulfill the request any of predicates must be meet
1840 """
1839 """
1841
1840
1842 def check_permissions(self, user):
1841 def check_permissions(self, user):
1843 perms = user.permissions_with_scope({})
1842 perms = user.permissions_with_scope({})
1844 if self.required_perms.intersection(perms['global']):
1843 if self.required_perms.intersection(perms['global']):
1845 return True
1844 return True
1846 return False
1845 return False
1847
1846
1848
1847
1849 class HasRepoPermissionAllDecorator(PermsDecorator):
1848 class HasRepoPermissionAllDecorator(PermsDecorator):
1850 """
1849 """
1851 Checks for access permission for all given predicates for specific
1850 Checks for access permission for all given predicates for specific
1852 repository. All of them have to be meet in order to fulfill the request
1851 repository. All of them have to be meet in order to fulfill the request
1853 """
1852 """
1854 def _get_repo_name(self):
1853 def _get_repo_name(self):
1855 _request = self._get_request()
1854 _request = self._get_request()
1856 return get_repo_slug(_request)
1855 return get_repo_slug(_request)
1857
1856
1858 def check_permissions(self, user):
1857 def check_permissions(self, user):
1859 perms = user.permissions
1858 perms = user.permissions
1860 repo_name = self._get_repo_name()
1859 repo_name = self._get_repo_name()
1861
1860
1862 try:
1861 try:
1863 user_perms = {perms['repositories'][repo_name]}
1862 user_perms = {perms['repositories'][repo_name]}
1864 except KeyError:
1863 except KeyError:
1865 log.debug('cannot locate repo with name: `%s` in permissions defs',
1864 log.debug('cannot locate repo with name: `%s` in permissions defs',
1866 repo_name)
1865 repo_name)
1867 return False
1866 return False
1868
1867
1869 log.debug('checking `%s` permissions for repo `%s`',
1868 log.debug('checking `%s` permissions for repo `%s`',
1870 user_perms, repo_name)
1869 user_perms, repo_name)
1871 if self.required_perms.issubset(user_perms):
1870 if self.required_perms.issubset(user_perms):
1872 return True
1871 return True
1873 return False
1872 return False
1874
1873
1875
1874
1876 class HasRepoPermissionAnyDecorator(PermsDecorator):
1875 class HasRepoPermissionAnyDecorator(PermsDecorator):
1877 """
1876 """
1878 Checks for access permission for any of given predicates for specific
1877 Checks for access permission for any of given predicates for specific
1879 repository. In order to fulfill the request any of predicates must be meet
1878 repository. In order to fulfill the request any of predicates must be meet
1880 """
1879 """
1881 def _get_repo_name(self):
1880 def _get_repo_name(self):
1882 _request = self._get_request()
1881 _request = self._get_request()
1883 return get_repo_slug(_request)
1882 return get_repo_slug(_request)
1884
1883
1885 def check_permissions(self, user):
1884 def check_permissions(self, user):
1886 perms = user.permissions
1885 perms = user.permissions
1887 repo_name = self._get_repo_name()
1886 repo_name = self._get_repo_name()
1888
1887
1889 try:
1888 try:
1890 user_perms = {perms['repositories'][repo_name]}
1889 user_perms = {perms['repositories'][repo_name]}
1891 except KeyError:
1890 except KeyError:
1892 log.debug(
1891 log.debug(
1893 'cannot locate repo with name: `%s` in permissions defs',
1892 'cannot locate repo with name: `%s` in permissions defs',
1894 repo_name)
1893 repo_name)
1895 return False
1894 return False
1896
1895
1897 log.debug('checking `%s` permissions for repo `%s`',
1896 log.debug('checking `%s` permissions for repo `%s`',
1898 user_perms, repo_name)
1897 user_perms, repo_name)
1899 if self.required_perms.intersection(user_perms):
1898 if self.required_perms.intersection(user_perms):
1900 return True
1899 return True
1901 return False
1900 return False
1902
1901
1903
1902
1904 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1903 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1905 """
1904 """
1906 Checks for access permission for all given predicates for specific
1905 Checks for access permission for all given predicates for specific
1907 repository group. All of them have to be meet in order to
1906 repository group. All of them have to be meet in order to
1908 fulfill the request
1907 fulfill the request
1909 """
1908 """
1910 def _get_repo_group_name(self):
1909 def _get_repo_group_name(self):
1911 _request = self._get_request()
1910 _request = self._get_request()
1912 return get_repo_group_slug(_request)
1911 return get_repo_group_slug(_request)
1913
1912
1914 def check_permissions(self, user):
1913 def check_permissions(self, user):
1915 perms = user.permissions
1914 perms = user.permissions
1916 group_name = self._get_repo_group_name()
1915 group_name = self._get_repo_group_name()
1917 try:
1916 try:
1918 user_perms = {perms['repositories_groups'][group_name]}
1917 user_perms = {perms['repositories_groups'][group_name]}
1919 except KeyError:
1918 except KeyError:
1920 log.debug(
1919 log.debug(
1921 'cannot locate repo group with name: `%s` in permissions defs',
1920 'cannot locate repo group with name: `%s` in permissions defs',
1922 group_name)
1921 group_name)
1923 return False
1922 return False
1924
1923
1925 log.debug('checking `%s` permissions for repo group `%s`',
1924 log.debug('checking `%s` permissions for repo group `%s`',
1926 user_perms, group_name)
1925 user_perms, group_name)
1927 if self.required_perms.issubset(user_perms):
1926 if self.required_perms.issubset(user_perms):
1928 return True
1927 return True
1929 return False
1928 return False
1930
1929
1931
1930
1932 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1931 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1933 """
1932 """
1934 Checks for access permission for any of given predicates for specific
1933 Checks for access permission for any of given predicates for specific
1935 repository group. In order to fulfill the request any
1934 repository group. In order to fulfill the request any
1936 of predicates must be met
1935 of predicates must be met
1937 """
1936 """
1938 def _get_repo_group_name(self):
1937 def _get_repo_group_name(self):
1939 _request = self._get_request()
1938 _request = self._get_request()
1940 return get_repo_group_slug(_request)
1939 return get_repo_group_slug(_request)
1941
1940
1942 def check_permissions(self, user):
1941 def check_permissions(self, user):
1943 perms = user.permissions
1942 perms = user.permissions
1944 group_name = self._get_repo_group_name()
1943 group_name = self._get_repo_group_name()
1945
1944
1946 try:
1945 try:
1947 user_perms = {perms['repositories_groups'][group_name]}
1946 user_perms = {perms['repositories_groups'][group_name]}
1948 except KeyError:
1947 except KeyError:
1949 log.debug(
1948 log.debug(
1950 'cannot locate repo group with name: `%s` in permissions defs',
1949 'cannot locate repo group with name: `%s` in permissions defs',
1951 group_name)
1950 group_name)
1952 return False
1951 return False
1953
1952
1954 log.debug('checking `%s` permissions for repo group `%s`',
1953 log.debug('checking `%s` permissions for repo group `%s`',
1955 user_perms, group_name)
1954 user_perms, group_name)
1956 if self.required_perms.intersection(user_perms):
1955 if self.required_perms.intersection(user_perms):
1957 return True
1956 return True
1958 return False
1957 return False
1959
1958
1960
1959
1961 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1960 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1962 """
1961 """
1963 Checks for access permission for all given predicates for specific
1962 Checks for access permission for all given predicates for specific
1964 user group. All of them have to be meet in order to fulfill the request
1963 user group. All of them have to be meet in order to fulfill the request
1965 """
1964 """
1966 def _get_user_group_name(self):
1965 def _get_user_group_name(self):
1967 _request = self._get_request()
1966 _request = self._get_request()
1968 return get_user_group_slug(_request)
1967 return get_user_group_slug(_request)
1969
1968
1970 def check_permissions(self, user):
1969 def check_permissions(self, user):
1971 perms = user.permissions
1970 perms = user.permissions
1972 group_name = self._get_user_group_name()
1971 group_name = self._get_user_group_name()
1973 try:
1972 try:
1974 user_perms = {perms['user_groups'][group_name]}
1973 user_perms = {perms['user_groups'][group_name]}
1975 except KeyError:
1974 except KeyError:
1976 return False
1975 return False
1977
1976
1978 if self.required_perms.issubset(user_perms):
1977 if self.required_perms.issubset(user_perms):
1979 return True
1978 return True
1980 return False
1979 return False
1981
1980
1982
1981
1983 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1982 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1984 """
1983 """
1985 Checks for access permission for any of given predicates for specific
1984 Checks for access permission for any of given predicates for specific
1986 user group. In order to fulfill the request any of predicates must be meet
1985 user group. In order to fulfill the request any of predicates must be meet
1987 """
1986 """
1988 def _get_user_group_name(self):
1987 def _get_user_group_name(self):
1989 _request = self._get_request()
1988 _request = self._get_request()
1990 return get_user_group_slug(_request)
1989 return get_user_group_slug(_request)
1991
1990
1992 def check_permissions(self, user):
1991 def check_permissions(self, user):
1993 perms = user.permissions
1992 perms = user.permissions
1994 group_name = self._get_user_group_name()
1993 group_name = self._get_user_group_name()
1995 try:
1994 try:
1996 user_perms = {perms['user_groups'][group_name]}
1995 user_perms = {perms['user_groups'][group_name]}
1997 except KeyError:
1996 except KeyError:
1998 return False
1997 return False
1999
1998
2000 if self.required_perms.intersection(user_perms):
1999 if self.required_perms.intersection(user_perms):
2001 return True
2000 return True
2002 return False
2001 return False
2003
2002
2004
2003
2005 # CHECK FUNCTIONS
2004 # CHECK FUNCTIONS
2006 class PermsFunction(object):
2005 class PermsFunction(object):
2007 """Base function for other check functions"""
2006 """Base function for other check functions"""
2008
2007
2009 def __init__(self, *perms):
2008 def __init__(self, *perms):
2010 self.required_perms = set(perms)
2009 self.required_perms = set(perms)
2011 self.repo_name = None
2010 self.repo_name = None
2012 self.repo_group_name = None
2011 self.repo_group_name = None
2013 self.user_group_name = None
2012 self.user_group_name = None
2014
2013
2015 def __bool__(self):
2014 def __bool__(self):
2015 import inspect
2016 frame = inspect.currentframe()
2016 frame = inspect.currentframe()
2017 stack_trace = traceback.format_stack(frame)
2017 stack_trace = traceback.format_stack(frame)
2018 log.error('Checking bool value on a class instance of perm '
2018 log.error('Checking bool value on a class instance of perm '
2019 'function is not allowed: %s', ''.join(stack_trace))
2019 'function is not allowed: %s', ''.join(stack_trace))
2020 # rather than throwing errors, here we always return False so if by
2020 # rather than throwing errors, here we always return False so if by
2021 # accident someone checks truth for just an instance it will always end
2021 # accident someone checks truth for just an instance it will always end
2022 # up in returning False
2022 # up in returning False
2023 return False
2023 return False
2024 __nonzero__ = __bool__
2024 __nonzero__ = __bool__
2025
2025
2026 def __call__(self, check_location='', user=None):
2026 def __call__(self, check_location='', user=None):
2027 if not user:
2027 if not user:
2028 log.debug('Using user attribute from global request')
2028 log.debug('Using user attribute from global request')
2029 request = self._get_request()
2029 request = self._get_request()
2030 user = request.user
2030 user = request.user
2031
2031
2032 # init auth user if not already given
2032 # init auth user if not already given
2033 if not isinstance(user, AuthUser):
2033 if not isinstance(user, AuthUser):
2034 log.debug('Wrapping user %s into AuthUser', user)
2034 log.debug('Wrapping user %s into AuthUser', user)
2035 user = AuthUser(user.user_id)
2035 user = AuthUser(user.user_id)
2036
2036
2037 cls_name = self.__class__.__name__
2037 cls_name = self.__class__.__name__
2038 check_scope = self._get_check_scope(cls_name)
2038 check_scope = self._get_check_scope(cls_name)
2039 check_location = check_location or 'unspecified location'
2039 check_location = check_location or 'unspecified location'
2040
2040
2041 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
2041 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
2042 self.required_perms, user, check_scope, check_location)
2042 self.required_perms, user, check_scope, check_location)
2043 if not user:
2043 if not user:
2044 log.warning('Empty user given for permission check')
2044 log.warning('Empty user given for permission check')
2045 return False
2045 return False
2046
2046
2047 if self.check_permissions(user):
2047 if self.check_permissions(user):
2048 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2048 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2049 check_scope, user, check_location)
2049 check_scope, user, check_location)
2050 return True
2050 return True
2051
2051
2052 else:
2052 else:
2053 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2053 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2054 check_scope, user, check_location)
2054 check_scope, user, check_location)
2055 return False
2055 return False
2056
2056
2057 def _get_request(self):
2057 def _get_request(self):
2058 return get_request(self)
2058 return get_request(self)
2059
2059
2060 def _get_check_scope(self, cls_name):
2060 def _get_check_scope(self, cls_name):
2061 return {
2061 return {
2062 'HasPermissionAll': 'GLOBAL',
2062 'HasPermissionAll': 'GLOBAL',
2063 'HasPermissionAny': 'GLOBAL',
2063 'HasPermissionAny': 'GLOBAL',
2064 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
2064 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
2065 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
2065 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
2066 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
2066 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
2067 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
2067 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
2068 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
2068 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
2069 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
2069 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
2070 }.get(cls_name, '?:%s' % cls_name)
2070 }.get(cls_name, '?:%s' % cls_name)
2071
2071
2072 def check_permissions(self, user):
2072 def check_permissions(self, user):
2073 """Dummy function for overriding"""
2073 """Dummy function for overriding"""
2074 raise Exception('You have to write this function in child class')
2074 raise Exception('You have to write this function in child class')
2075
2075
2076
2076
2077 class HasPermissionAll(PermsFunction):
2077 class HasPermissionAll(PermsFunction):
2078 def check_permissions(self, user):
2078 def check_permissions(self, user):
2079 perms = user.permissions_with_scope({})
2079 perms = user.permissions_with_scope({})
2080 if self.required_perms.issubset(perms.get('global')):
2080 if self.required_perms.issubset(perms.get('global')):
2081 return True
2081 return True
2082 return False
2082 return False
2083
2083
2084
2084
2085 class HasPermissionAny(PermsFunction):
2085 class HasPermissionAny(PermsFunction):
2086 def check_permissions(self, user):
2086 def check_permissions(self, user):
2087 perms = user.permissions_with_scope({})
2087 perms = user.permissions_with_scope({})
2088 if self.required_perms.intersection(perms.get('global')):
2088 if self.required_perms.intersection(perms.get('global')):
2089 return True
2089 return True
2090 return False
2090 return False
2091
2091
2092
2092
2093 class HasRepoPermissionAll(PermsFunction):
2093 class HasRepoPermissionAll(PermsFunction):
2094 def __call__(self, repo_name=None, check_location='', user=None):
2094 def __call__(self, repo_name=None, check_location='', user=None):
2095 self.repo_name = repo_name
2095 self.repo_name = repo_name
2096 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2096 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2097
2097
2098 def _get_repo_name(self):
2098 def _get_repo_name(self):
2099 if not self.repo_name:
2099 if not self.repo_name:
2100 _request = self._get_request()
2100 _request = self._get_request()
2101 self.repo_name = get_repo_slug(_request)
2101 self.repo_name = get_repo_slug(_request)
2102 return self.repo_name
2102 return self.repo_name
2103
2103
2104 def check_permissions(self, user):
2104 def check_permissions(self, user):
2105 self.repo_name = self._get_repo_name()
2105 self.repo_name = self._get_repo_name()
2106 perms = user.permissions
2106 perms = user.permissions
2107 try:
2107 try:
2108 user_perms = {perms['repositories'][self.repo_name]}
2108 user_perms = {perms['repositories'][self.repo_name]}
2109 except KeyError:
2109 except KeyError:
2110 return False
2110 return False
2111 if self.required_perms.issubset(user_perms):
2111 if self.required_perms.issubset(user_perms):
2112 return True
2112 return True
2113 return False
2113 return False
2114
2114
2115
2115
2116 class HasRepoPermissionAny(PermsFunction):
2116 class HasRepoPermissionAny(PermsFunction):
2117 def __call__(self, repo_name=None, check_location='', user=None):
2117 def __call__(self, repo_name=None, check_location='', user=None):
2118 self.repo_name = repo_name
2118 self.repo_name = repo_name
2119 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2119 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2120
2120
2121 def _get_repo_name(self):
2121 def _get_repo_name(self):
2122 if not self.repo_name:
2122 if not self.repo_name:
2123 _request = self._get_request()
2123 _request = self._get_request()
2124 self.repo_name = get_repo_slug(_request)
2124 self.repo_name = get_repo_slug(_request)
2125 return self.repo_name
2125 return self.repo_name
2126
2126
2127 def check_permissions(self, user):
2127 def check_permissions(self, user):
2128 self.repo_name = self._get_repo_name()
2128 self.repo_name = self._get_repo_name()
2129 perms = user.permissions
2129 perms = user.permissions
2130 try:
2130 try:
2131 user_perms = {perms['repositories'][self.repo_name]}
2131 user_perms = {perms['repositories'][self.repo_name]}
2132 except KeyError:
2132 except KeyError:
2133 return False
2133 return False
2134 if self.required_perms.intersection(user_perms):
2134 if self.required_perms.intersection(user_perms):
2135 return True
2135 return True
2136 return False
2136 return False
2137
2137
2138
2138
2139 class HasRepoGroupPermissionAny(PermsFunction):
2139 class HasRepoGroupPermissionAny(PermsFunction):
2140 def __call__(self, group_name=None, check_location='', user=None):
2140 def __call__(self, group_name=None, check_location='', user=None):
2141 self.repo_group_name = group_name
2141 self.repo_group_name = group_name
2142 return super(HasRepoGroupPermissionAny, self).__call__(check_location, user)
2142 return super(HasRepoGroupPermissionAny, self).__call__(check_location, user)
2143
2143
2144 def check_permissions(self, user):
2144 def check_permissions(self, user):
2145 perms = user.permissions
2145 perms = user.permissions
2146 try:
2146 try:
2147 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2147 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2148 except KeyError:
2148 except KeyError:
2149 return False
2149 return False
2150 if self.required_perms.intersection(user_perms):
2150 if self.required_perms.intersection(user_perms):
2151 return True
2151 return True
2152 return False
2152 return False
2153
2153
2154
2154
2155 class HasRepoGroupPermissionAll(PermsFunction):
2155 class HasRepoGroupPermissionAll(PermsFunction):
2156 def __call__(self, group_name=None, check_location='', user=None):
2156 def __call__(self, group_name=None, check_location='', user=None):
2157 self.repo_group_name = group_name
2157 self.repo_group_name = group_name
2158 return super(HasRepoGroupPermissionAll, self).__call__(check_location, user)
2158 return super(HasRepoGroupPermissionAll, self).__call__(check_location, user)
2159
2159
2160 def check_permissions(self, user):
2160 def check_permissions(self, user):
2161 perms = user.permissions
2161 perms = user.permissions
2162 try:
2162 try:
2163 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2163 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2164 except KeyError:
2164 except KeyError:
2165 return False
2165 return False
2166 if self.required_perms.issubset(user_perms):
2166 if self.required_perms.issubset(user_perms):
2167 return True
2167 return True
2168 return False
2168 return False
2169
2169
2170
2170
2171 class HasUserGroupPermissionAny(PermsFunction):
2171 class HasUserGroupPermissionAny(PermsFunction):
2172 def __call__(self, user_group_name=None, check_location='', user=None):
2172 def __call__(self, user_group_name=None, check_location='', user=None):
2173 self.user_group_name = user_group_name
2173 self.user_group_name = user_group_name
2174 return super(HasUserGroupPermissionAny, self).__call__(check_location, user)
2174 return super(HasUserGroupPermissionAny, self).__call__(check_location, user)
2175
2175
2176 def check_permissions(self, user):
2176 def check_permissions(self, user):
2177 perms = user.permissions
2177 perms = user.permissions
2178 try:
2178 try:
2179 user_perms = {perms['user_groups'][self.user_group_name]}
2179 user_perms = {perms['user_groups'][self.user_group_name]}
2180 except KeyError:
2180 except KeyError:
2181 return False
2181 return False
2182 if self.required_perms.intersection(user_perms):
2182 if self.required_perms.intersection(user_perms):
2183 return True
2183 return True
2184 return False
2184 return False
2185
2185
2186
2186
2187 class HasUserGroupPermissionAll(PermsFunction):
2187 class HasUserGroupPermissionAll(PermsFunction):
2188 def __call__(self, user_group_name=None, check_location='', user=None):
2188 def __call__(self, user_group_name=None, check_location='', user=None):
2189 self.user_group_name = user_group_name
2189 self.user_group_name = user_group_name
2190 return super(HasUserGroupPermissionAll, self).__call__(check_location, user)
2190 return super(HasUserGroupPermissionAll, self).__call__(check_location, user)
2191
2191
2192 def check_permissions(self, user):
2192 def check_permissions(self, user):
2193 perms = user.permissions
2193 perms = user.permissions
2194 try:
2194 try:
2195 user_perms = {perms['user_groups'][self.user_group_name]}
2195 user_perms = {perms['user_groups'][self.user_group_name]}
2196 except KeyError:
2196 except KeyError:
2197 return False
2197 return False
2198 if self.required_perms.issubset(user_perms):
2198 if self.required_perms.issubset(user_perms):
2199 return True
2199 return True
2200 return False
2200 return False
2201
2201
2202
2202
2203 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2203 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2204 class HasPermissionAnyMiddleware(object):
2204 class HasPermissionAnyMiddleware(object):
2205 def __init__(self, *perms):
2205 def __init__(self, *perms):
2206 self.required_perms = set(perms)
2206 self.required_perms = set(perms)
2207
2207
2208 def __call__(self, auth_user, repo_name):
2208 def __call__(self, auth_user, repo_name):
2209 # repo_name MUST be unicode, since we handle keys in permission
2209 # repo_name MUST be unicode, since we handle keys in permission
2210 # dict by unicode
2210 # dict by unicode
2211 repo_name = safe_unicode(repo_name)
2211 repo_name = safe_unicode(repo_name)
2212 log.debug(
2212 log.debug(
2213 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2213 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2214 self.required_perms, auth_user, repo_name)
2214 self.required_perms, auth_user, repo_name)
2215
2215
2216 if self.check_permissions(auth_user, repo_name):
2216 if self.check_permissions(auth_user, repo_name):
2217 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2217 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2218 repo_name, auth_user, 'PermissionMiddleware')
2218 repo_name, auth_user, 'PermissionMiddleware')
2219 return True
2219 return True
2220
2220
2221 else:
2221 else:
2222 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2222 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2223 repo_name, auth_user, 'PermissionMiddleware')
2223 repo_name, auth_user, 'PermissionMiddleware')
2224 return False
2224 return False
2225
2225
2226 def check_permissions(self, user, repo_name):
2226 def check_permissions(self, user, repo_name):
2227 perms = user.permissions_with_scope({'repo_name': repo_name})
2227 perms = user.permissions_with_scope({'repo_name': repo_name})
2228
2228
2229 try:
2229 try:
2230 user_perms = {perms['repositories'][repo_name]}
2230 user_perms = {perms['repositories'][repo_name]}
2231 except Exception:
2231 except Exception:
2232 log.exception('Error while accessing user permissions')
2232 log.exception('Error while accessing user permissions')
2233 return False
2233 return False
2234
2234
2235 if self.required_perms.intersection(user_perms):
2235 if self.required_perms.intersection(user_perms):
2236 return True
2236 return True
2237 return False
2237 return False
2238
2238
2239
2239
2240 # SPECIAL VERSION TO HANDLE API AUTH
2240 # SPECIAL VERSION TO HANDLE API AUTH
2241 class _BaseApiPerm(object):
2241 class _BaseApiPerm(object):
2242 def __init__(self, *perms):
2242 def __init__(self, *perms):
2243 self.required_perms = set(perms)
2243 self.required_perms = set(perms)
2244
2244
2245 def __call__(self, check_location=None, user=None, repo_name=None,
2245 def __call__(self, check_location=None, user=None, repo_name=None,
2246 group_name=None, user_group_name=None):
2246 group_name=None, user_group_name=None):
2247 cls_name = self.__class__.__name__
2247 cls_name = self.__class__.__name__
2248 check_scope = 'global:%s' % (self.required_perms,)
2248 check_scope = 'global:%s' % (self.required_perms,)
2249 if repo_name:
2249 if repo_name:
2250 check_scope += ', repo_name:%s' % (repo_name,)
2250 check_scope += ', repo_name:%s' % (repo_name,)
2251
2251
2252 if group_name:
2252 if group_name:
2253 check_scope += ', repo_group_name:%s' % (group_name,)
2253 check_scope += ', repo_group_name:%s' % (group_name,)
2254
2254
2255 if user_group_name:
2255 if user_group_name:
2256 check_scope += ', user_group_name:%s' % (user_group_name,)
2256 check_scope += ', user_group_name:%s' % (user_group_name,)
2257
2257
2258 log.debug('checking cls:%s %s %s @ %s',
2258 log.debug('checking cls:%s %s %s @ %s',
2259 cls_name, self.required_perms, check_scope, check_location)
2259 cls_name, self.required_perms, check_scope, check_location)
2260 if not user:
2260 if not user:
2261 log.debug('Empty User passed into arguments')
2261 log.debug('Empty User passed into arguments')
2262 return False
2262 return False
2263
2263
2264 # process user
2264 # process user
2265 if not isinstance(user, AuthUser):
2265 if not isinstance(user, AuthUser):
2266 user = AuthUser(user.user_id)
2266 user = AuthUser(user.user_id)
2267 if not check_location:
2267 if not check_location:
2268 check_location = 'unspecified'
2268 check_location = 'unspecified'
2269 if self.check_permissions(user.permissions, repo_name, group_name,
2269 if self.check_permissions(user.permissions, repo_name, group_name,
2270 user_group_name):
2270 user_group_name):
2271 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2271 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2272 check_scope, user, check_location)
2272 check_scope, user, check_location)
2273 return True
2273 return True
2274
2274
2275 else:
2275 else:
2276 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2276 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2277 check_scope, user, check_location)
2277 check_scope, user, check_location)
2278 return False
2278 return False
2279
2279
2280 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2280 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2281 user_group_name=None):
2281 user_group_name=None):
2282 """
2282 """
2283 implement in child class should return True if permissions are ok,
2283 implement in child class should return True if permissions are ok,
2284 False otherwise
2284 False otherwise
2285
2285
2286 :param perm_defs: dict with permission definitions
2286 :param perm_defs: dict with permission definitions
2287 :param repo_name: repo name
2287 :param repo_name: repo name
2288 """
2288 """
2289 raise NotImplementedError()
2289 raise NotImplementedError()
2290
2290
2291
2291
2292 class HasPermissionAllApi(_BaseApiPerm):
2292 class HasPermissionAllApi(_BaseApiPerm):
2293 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2293 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2294 user_group_name=None):
2294 user_group_name=None):
2295 if self.required_perms.issubset(perm_defs.get('global')):
2295 if self.required_perms.issubset(perm_defs.get('global')):
2296 return True
2296 return True
2297 return False
2297 return False
2298
2298
2299
2299
2300 class HasPermissionAnyApi(_BaseApiPerm):
2300 class HasPermissionAnyApi(_BaseApiPerm):
2301 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2301 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2302 user_group_name=None):
2302 user_group_name=None):
2303 if self.required_perms.intersection(perm_defs.get('global')):
2303 if self.required_perms.intersection(perm_defs.get('global')):
2304 return True
2304 return True
2305 return False
2305 return False
2306
2306
2307
2307
2308 class HasRepoPermissionAllApi(_BaseApiPerm):
2308 class HasRepoPermissionAllApi(_BaseApiPerm):
2309 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2309 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2310 user_group_name=None):
2310 user_group_name=None):
2311 try:
2311 try:
2312 _user_perms = {perm_defs['repositories'][repo_name]}
2312 _user_perms = {perm_defs['repositories'][repo_name]}
2313 except KeyError:
2313 except KeyError:
2314 log.warning(traceback.format_exc())
2314 log.warning(traceback.format_exc())
2315 return False
2315 return False
2316 if self.required_perms.issubset(_user_perms):
2316 if self.required_perms.issubset(_user_perms):
2317 return True
2317 return True
2318 return False
2318 return False
2319
2319
2320
2320
2321 class HasRepoPermissionAnyApi(_BaseApiPerm):
2321 class HasRepoPermissionAnyApi(_BaseApiPerm):
2322 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2322 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2323 user_group_name=None):
2323 user_group_name=None):
2324 try:
2324 try:
2325 _user_perms = {perm_defs['repositories'][repo_name]}
2325 _user_perms = {perm_defs['repositories'][repo_name]}
2326 except KeyError:
2326 except KeyError:
2327 log.warning(traceback.format_exc())
2327 log.warning(traceback.format_exc())
2328 return False
2328 return False
2329 if self.required_perms.intersection(_user_perms):
2329 if self.required_perms.intersection(_user_perms):
2330 return True
2330 return True
2331 return False
2331 return False
2332
2332
2333
2333
2334 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2334 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2335 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2335 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2336 user_group_name=None):
2336 user_group_name=None):
2337 try:
2337 try:
2338 _user_perms = {perm_defs['repositories_groups'][group_name]}
2338 _user_perms = {perm_defs['repositories_groups'][group_name]}
2339 except KeyError:
2339 except KeyError:
2340 log.warning(traceback.format_exc())
2340 log.warning(traceback.format_exc())
2341 return False
2341 return False
2342 if self.required_perms.intersection(_user_perms):
2342 if self.required_perms.intersection(_user_perms):
2343 return True
2343 return True
2344 return False
2344 return False
2345
2345
2346
2346
2347 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2347 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2348 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2348 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2349 user_group_name=None):
2349 user_group_name=None):
2350 try:
2350 try:
2351 _user_perms = {perm_defs['repositories_groups'][group_name]}
2351 _user_perms = {perm_defs['repositories_groups'][group_name]}
2352 except KeyError:
2352 except KeyError:
2353 log.warning(traceback.format_exc())
2353 log.warning(traceback.format_exc())
2354 return False
2354 return False
2355 if self.required_perms.issubset(_user_perms):
2355 if self.required_perms.issubset(_user_perms):
2356 return True
2356 return True
2357 return False
2357 return False
2358
2358
2359
2359
2360 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2360 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2361 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2361 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2362 user_group_name=None):
2362 user_group_name=None):
2363 try:
2363 try:
2364 _user_perms = {perm_defs['user_groups'][user_group_name]}
2364 _user_perms = {perm_defs['user_groups'][user_group_name]}
2365 except KeyError:
2365 except KeyError:
2366 log.warning(traceback.format_exc())
2366 log.warning(traceback.format_exc())
2367 return False
2367 return False
2368 if self.required_perms.intersection(_user_perms):
2368 if self.required_perms.intersection(_user_perms):
2369 return True
2369 return True
2370 return False
2370 return False
2371
2371
2372
2372
2373 def check_ip_access(source_ip, allowed_ips=None):
2373 def check_ip_access(source_ip, allowed_ips=None):
2374 """
2374 """
2375 Checks if source_ip is a subnet of any of allowed_ips.
2375 Checks if source_ip is a subnet of any of allowed_ips.
2376
2376
2377 :param source_ip:
2377 :param source_ip:
2378 :param allowed_ips: list of allowed ips together with mask
2378 :param allowed_ips: list of allowed ips together with mask
2379 """
2379 """
2380 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
2380 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
2381 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2381 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2382 if isinstance(allowed_ips, (tuple, list, set)):
2382 if isinstance(allowed_ips, (tuple, list, set)):
2383 for ip in allowed_ips:
2383 for ip in allowed_ips:
2384 ip = safe_unicode(ip)
2384 ip = safe_unicode(ip)
2385 try:
2385 try:
2386 network_address = ipaddress.ip_network(ip, strict=False)
2386 network_address = ipaddress.ip_network(ip, strict=False)
2387 if source_ip_address in network_address:
2387 if source_ip_address in network_address:
2388 log.debug('IP %s is network %s', source_ip_address, network_address)
2388 log.debug('IP %s is network %s', source_ip_address, network_address)
2389 return True
2389 return True
2390 # for any case we cannot determine the IP, don't crash just
2390 # for any case we cannot determine the IP, don't crash just
2391 # skip it and log as error, we want to say forbidden still when
2391 # skip it and log as error, we want to say forbidden still when
2392 # sending bad IP
2392 # sending bad IP
2393 except Exception:
2393 except Exception:
2394 log.error(traceback.format_exc())
2394 log.error(traceback.format_exc())
2395 continue
2395 continue
2396 return False
2396 return False
2397
2397
2398
2398
2399 def get_cython_compat_decorator(wrapper, func):
2399 def get_cython_compat_decorator(wrapper, func):
2400 """
2400 """
2401 Creates a cython compatible decorator. The previously used
2401 Creates a cython compatible decorator. The previously used
2402 decorator.decorator() function seems to be incompatible with cython.
2402 decorator.decorator() function seems to be incompatible with cython.
2403
2403
2404 :param wrapper: __wrapper method of the decorator class
2404 :param wrapper: __wrapper method of the decorator class
2405 :param func: decorated function
2405 :param func: decorated function
2406 """
2406 """
2407 @wraps(func)
2407 @wraps(func)
2408 def local_wrapper(*args, **kwds):
2408 def local_wrapper(*args, **kwds):
2409 return wrapper(func, *args, **kwds)
2409 return wrapper(func, *args, **kwds)
2410 local_wrapper.__wrapped__ = func
2410 local_wrapper.__wrapped__ = func
2411 return local_wrapper
2411 return local_wrapper
2412
2412
2413
2413
General Comments 0
You need to be logged in to leave comments. Login now