##// END OF EJS Templates
auth-tokens: updated logic of authentication to a common shared user method.
marcink -
r1421:5088d9a7 default
parent child Browse files
Show More
@@ -1,536 +1,536 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import inspect
21 import inspect
22 import itertools
22 import itertools
23 import logging
23 import logging
24 import types
24 import types
25 import fnmatch
25 import fnmatch
26
26
27 import decorator
27 import decorator
28 import venusian
28 import venusian
29 from collections import OrderedDict
29 from collections import OrderedDict
30
30
31 from pyramid.exceptions import ConfigurationError
31 from pyramid.exceptions import ConfigurationError
32 from pyramid.renderers import render
32 from pyramid.renderers import render
33 from pyramid.response import Response
33 from pyramid.response import Response
34 from pyramid.httpexceptions import HTTPNotFound
34 from pyramid.httpexceptions import HTTPNotFound
35
35
36 from rhodecode.api.exc import (
36 from rhodecode.api.exc import (
37 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
37 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
38 from rhodecode.lib.auth import AuthUser
38 from rhodecode.lib.auth import AuthUser
39 from rhodecode.lib.base import get_ip_addr
39 from rhodecode.lib.base import get_ip_addr
40 from rhodecode.lib.ext_json import json
40 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.utils2 import safe_str
41 from rhodecode.lib.utils2 import safe_str
42 from rhodecode.lib.plugins.utils import get_plugin_settings
42 from rhodecode.lib.plugins.utils import get_plugin_settings
43 from rhodecode.model.db import User, UserApiKeys
43 from rhodecode.model.db import User, UserApiKeys
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47 DEFAULT_RENDERER = 'jsonrpc_renderer'
47 DEFAULT_RENDERER = 'jsonrpc_renderer'
48 DEFAULT_URL = '/_admin/apiv2'
48 DEFAULT_URL = '/_admin/apiv2'
49
49
50
50
51 def find_methods(jsonrpc_methods, pattern):
51 def find_methods(jsonrpc_methods, pattern):
52 matches = OrderedDict()
52 matches = OrderedDict()
53 if not isinstance(pattern, (list, tuple)):
53 if not isinstance(pattern, (list, tuple)):
54 pattern = [pattern]
54 pattern = [pattern]
55
55
56 for single_pattern in pattern:
56 for single_pattern in pattern:
57 for method_name, method in jsonrpc_methods.items():
57 for method_name, method in jsonrpc_methods.items():
58 if fnmatch.fnmatch(method_name, single_pattern):
58 if fnmatch.fnmatch(method_name, single_pattern):
59 matches[method_name] = method
59 matches[method_name] = method
60 return matches
60 return matches
61
61
62
62
63 class ExtJsonRenderer(object):
63 class ExtJsonRenderer(object):
64 """
64 """
65 Custom renderer that mkaes use of our ext_json lib
65 Custom renderer that mkaes use of our ext_json lib
66
66
67 """
67 """
68
68
69 def __init__(self, serializer=json.dumps, **kw):
69 def __init__(self, serializer=json.dumps, **kw):
70 """ Any keyword arguments will be passed to the ``serializer``
70 """ Any keyword arguments will be passed to the ``serializer``
71 function."""
71 function."""
72 self.serializer = serializer
72 self.serializer = serializer
73 self.kw = kw
73 self.kw = kw
74
74
75 def __call__(self, info):
75 def __call__(self, info):
76 """ Returns a plain JSON-encoded string with content-type
76 """ Returns a plain JSON-encoded string with content-type
77 ``application/json``. The content-type may be overridden by
77 ``application/json``. The content-type may be overridden by
78 setting ``request.response.content_type``."""
78 setting ``request.response.content_type``."""
79
79
80 def _render(value, system):
80 def _render(value, system):
81 request = system.get('request')
81 request = system.get('request')
82 if request is not None:
82 if request is not None:
83 response = request.response
83 response = request.response
84 ct = response.content_type
84 ct = response.content_type
85 if ct == response.default_content_type:
85 if ct == response.default_content_type:
86 response.content_type = 'application/json'
86 response.content_type = 'application/json'
87
87
88 return self.serializer(value, **self.kw)
88 return self.serializer(value, **self.kw)
89
89
90 return _render
90 return _render
91
91
92
92
93 def jsonrpc_response(request, result):
93 def jsonrpc_response(request, result):
94 rpc_id = getattr(request, 'rpc_id', None)
94 rpc_id = getattr(request, 'rpc_id', None)
95 response = request.response
95 response = request.response
96
96
97 # store content_type before render is called
97 # store content_type before render is called
98 ct = response.content_type
98 ct = response.content_type
99
99
100 ret_value = ''
100 ret_value = ''
101 if rpc_id:
101 if rpc_id:
102 ret_value = {
102 ret_value = {
103 'id': rpc_id,
103 'id': rpc_id,
104 'result': result,
104 'result': result,
105 'error': None,
105 'error': None,
106 }
106 }
107
107
108 # fetch deprecation warnings, and store it inside results
108 # fetch deprecation warnings, and store it inside results
109 deprecation = getattr(request, 'rpc_deprecation', None)
109 deprecation = getattr(request, 'rpc_deprecation', None)
110 if deprecation:
110 if deprecation:
111 ret_value['DEPRECATION_WARNING'] = deprecation
111 ret_value['DEPRECATION_WARNING'] = deprecation
112
112
113 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
113 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
114 response.body = safe_str(raw_body, response.charset)
114 response.body = safe_str(raw_body, response.charset)
115
115
116 if ct == response.default_content_type:
116 if ct == response.default_content_type:
117 response.content_type = 'application/json'
117 response.content_type = 'application/json'
118
118
119 return response
119 return response
120
120
121
121
122 def jsonrpc_error(request, message, retid=None, code=None):
122 def jsonrpc_error(request, message, retid=None, code=None):
123 """
123 """
124 Generate a Response object with a JSON-RPC error body
124 Generate a Response object with a JSON-RPC error body
125
125
126 :param code:
126 :param code:
127 :param retid:
127 :param retid:
128 :param message:
128 :param message:
129 """
129 """
130 err_dict = {'id': retid, 'result': None, 'error': message}
130 err_dict = {'id': retid, 'result': None, 'error': message}
131 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
131 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
132 return Response(
132 return Response(
133 body=body,
133 body=body,
134 status=code,
134 status=code,
135 content_type='application/json'
135 content_type='application/json'
136 )
136 )
137
137
138
138
139 def exception_view(exc, request):
139 def exception_view(exc, request):
140 rpc_id = getattr(request, 'rpc_id', None)
140 rpc_id = getattr(request, 'rpc_id', None)
141
141
142 fault_message = 'undefined error'
142 fault_message = 'undefined error'
143 if isinstance(exc, JSONRPCError):
143 if isinstance(exc, JSONRPCError):
144 fault_message = exc.message
144 fault_message = exc.message
145 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
145 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
146 elif isinstance(exc, JSONRPCValidationError):
146 elif isinstance(exc, JSONRPCValidationError):
147 colander_exc = exc.colander_exception
147 colander_exc = exc.colander_exception
148 # TODO(marcink): think maybe of nicer way to serialize errors ?
148 # TODO(marcink): think maybe of nicer way to serialize errors ?
149 fault_message = colander_exc.asdict()
149 fault_message = colander_exc.asdict()
150 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
150 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
151 elif isinstance(exc, JSONRPCForbidden):
151 elif isinstance(exc, JSONRPCForbidden):
152 fault_message = 'Access was denied to this resource.'
152 fault_message = 'Access was denied to this resource.'
153 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
153 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
154 elif isinstance(exc, HTTPNotFound):
154 elif isinstance(exc, HTTPNotFound):
155 method = request.rpc_method
155 method = request.rpc_method
156 log.debug('json-rpc method `%s` not found in list of '
156 log.debug('json-rpc method `%s` not found in list of '
157 'api calls: %s, rpc_id:%s',
157 'api calls: %s, rpc_id:%s',
158 method, request.registry.jsonrpc_methods.keys(), rpc_id)
158 method, request.registry.jsonrpc_methods.keys(), rpc_id)
159
159
160 similar = 'none'
160 similar = 'none'
161 try:
161 try:
162 similar_paterns = ['*{}*'.format(x) for x in method.split('_')]
162 similar_paterns = ['*{}*'.format(x) for x in method.split('_')]
163 similar_found = find_methods(
163 similar_found = find_methods(
164 request.registry.jsonrpc_methods, similar_paterns)
164 request.registry.jsonrpc_methods, similar_paterns)
165 similar = ', '.join(similar_found.keys()) or similar
165 similar = ', '.join(similar_found.keys()) or similar
166 except Exception:
166 except Exception:
167 # make the whole above block safe
167 # make the whole above block safe
168 pass
168 pass
169
169
170 fault_message = "No such method: {}. Similar methods: {}".format(
170 fault_message = "No such method: {}. Similar methods: {}".format(
171 method, similar)
171 method, similar)
172
172
173 return jsonrpc_error(request, fault_message, rpc_id)
173 return jsonrpc_error(request, fault_message, rpc_id)
174
174
175
175
176 def request_view(request):
176 def request_view(request):
177 """
177 """
178 Main request handling method. It handles all logic to call a specific
178 Main request handling method. It handles all logic to call a specific
179 exposed method
179 exposed method
180 """
180 """
181
181
182 # check if we can find this session using api_key, get_by_auth_token
182 # check if we can find this session using api_key, get_by_auth_token
183 # search not expired tokens only
183 # search not expired tokens only
184
184
185 try:
185 try:
186 api_user = User.get_by_auth_token(request.rpc_api_key)
186 api_user = User.get_by_auth_token(request.rpc_api_key)
187
187
188 if api_user is None:
188 if api_user is None:
189 return jsonrpc_error(
189 return jsonrpc_error(
190 request, retid=request.rpc_id, message='Invalid API KEY')
190 request, retid=request.rpc_id, message='Invalid API KEY')
191
191
192 if not api_user.active:
192 if not api_user.active:
193 return jsonrpc_error(
193 return jsonrpc_error(
194 request, retid=request.rpc_id,
194 request, retid=request.rpc_id,
195 message='Request from this user not allowed')
195 message='Request from this user not allowed')
196
196
197 # check if we are allowed to use this IP
197 # check if we are allowed to use this IP
198 auth_u = AuthUser(
198 auth_u = AuthUser(
199 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
199 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
200 if not auth_u.ip_allowed:
200 if not auth_u.ip_allowed:
201 return jsonrpc_error(
201 return jsonrpc_error(
202 request, retid=request.rpc_id,
202 request, retid=request.rpc_id,
203 message='Request from IP:%s not allowed' % (
203 message='Request from IP:%s not allowed' % (
204 request.rpc_ip_addr,))
204 request.rpc_ip_addr,))
205 else:
205 else:
206 log.info('Access for IP:%s allowed' % (request.rpc_ip_addr,))
206 log.info('Access for IP:%s allowed' % (request.rpc_ip_addr,))
207
207
208 # register our auth-user
208 # register our auth-user
209 request.rpc_user = auth_u
209 request.rpc_user = auth_u
210
210
211 # now check if token is valid for API
211 # now check if token is valid for API
212 role = UserApiKeys.ROLE_API
212 auth_token = request.rpc_api_key
213 extra_auth_tokens = [
213 token_match = api_user.authenticate_by_token(
214 x.api_key for x in User.extra_valid_auth_tokens(api_user, role=role)]
214 auth_token, roles=[UserApiKeys.ROLE_API], include_builtin_token=True)
215 active_tokens = [api_user.api_key] + extra_auth_tokens
215 invalid_token = not token_match
216
216
217 log.debug('Checking if API key has proper role')
217 log.debug('Checking if API KEY is valid with proper role')
218 if request.rpc_api_key not in active_tokens:
218 if invalid_token:
219 return jsonrpc_error(
219 return jsonrpc_error(
220 request, retid=request.rpc_id,
220 request, retid=request.rpc_id,
221 message='API KEY has bad role for an API call')
221 message='API KEY invalid or, has bad role for an API call')
222
222
223 except Exception as e:
223 except Exception:
224 log.exception('Error on API AUTH')
224 log.exception('Error on API AUTH')
225 return jsonrpc_error(
225 return jsonrpc_error(
226 request, retid=request.rpc_id, message='Invalid API KEY')
226 request, retid=request.rpc_id, message='Invalid API KEY')
227
227
228 method = request.rpc_method
228 method = request.rpc_method
229 func = request.registry.jsonrpc_methods[method]
229 func = request.registry.jsonrpc_methods[method]
230
230
231 # now that we have a method, add request._req_params to
231 # now that we have a method, add request._req_params to
232 # self.kargs and dispatch control to WGIController
232 # self.kargs and dispatch control to WGIController
233 argspec = inspect.getargspec(func)
233 argspec = inspect.getargspec(func)
234 arglist = argspec[0]
234 arglist = argspec[0]
235 defaults = map(type, argspec[3] or [])
235 defaults = map(type, argspec[3] or [])
236 default_empty = types.NotImplementedType
236 default_empty = types.NotImplementedType
237
237
238 # kw arguments required by this method
238 # kw arguments required by this method
239 func_kwargs = dict(itertools.izip_longest(
239 func_kwargs = dict(itertools.izip_longest(
240 reversed(arglist), reversed(defaults), fillvalue=default_empty))
240 reversed(arglist), reversed(defaults), fillvalue=default_empty))
241
241
242 # This attribute will need to be first param of a method that uses
242 # This attribute will need to be first param of a method that uses
243 # api_key, which is translated to instance of user at that name
243 # api_key, which is translated to instance of user at that name
244 user_var = 'apiuser'
244 user_var = 'apiuser'
245 request_var = 'request'
245 request_var = 'request'
246
246
247 for arg in [user_var, request_var]:
247 for arg in [user_var, request_var]:
248 if arg not in arglist:
248 if arg not in arglist:
249 return jsonrpc_error(
249 return jsonrpc_error(
250 request,
250 request,
251 retid=request.rpc_id,
251 retid=request.rpc_id,
252 message='This method [%s] does not support '
252 message='This method [%s] does not support '
253 'required parameter `%s`' % (func.__name__, arg))
253 'required parameter `%s`' % (func.__name__, arg))
254
254
255 # get our arglist and check if we provided them as args
255 # get our arglist and check if we provided them as args
256 for arg, default in func_kwargs.items():
256 for arg, default in func_kwargs.items():
257 if arg in [user_var, request_var]:
257 if arg in [user_var, request_var]:
258 # user_var and request_var are pre-hardcoded parameters and we
258 # user_var and request_var are pre-hardcoded parameters and we
259 # don't need to do any translation
259 # don't need to do any translation
260 continue
260 continue
261
261
262 # skip the required param check if it's default value is
262 # skip the required param check if it's default value is
263 # NotImplementedType (default_empty)
263 # NotImplementedType (default_empty)
264 if default == default_empty and arg not in request.rpc_params:
264 if default == default_empty and arg not in request.rpc_params:
265 return jsonrpc_error(
265 return jsonrpc_error(
266 request,
266 request,
267 retid=request.rpc_id,
267 retid=request.rpc_id,
268 message=('Missing non optional `%s` arg in JSON DATA' % arg)
268 message=('Missing non optional `%s` arg in JSON DATA' % arg)
269 )
269 )
270
270
271 # sanitize extra passed arguments
271 # sanitize extra passed arguments
272 for k in request.rpc_params.keys()[:]:
272 for k in request.rpc_params.keys()[:]:
273 if k not in func_kwargs:
273 if k not in func_kwargs:
274 del request.rpc_params[k]
274 del request.rpc_params[k]
275
275
276 call_params = request.rpc_params
276 call_params = request.rpc_params
277 call_params.update({
277 call_params.update({
278 'request': request,
278 'request': request,
279 'apiuser': auth_u
279 'apiuser': auth_u
280 })
280 })
281 try:
281 try:
282 ret_value = func(**call_params)
282 ret_value = func(**call_params)
283 return jsonrpc_response(request, ret_value)
283 return jsonrpc_response(request, ret_value)
284 except JSONRPCBaseError:
284 except JSONRPCBaseError:
285 raise
285 raise
286 except Exception:
286 except Exception:
287 log.exception('Unhandled exception occurred on api call: %s', func)
287 log.exception('Unhandled exception occurred on api call: %s', func)
288 return jsonrpc_error(request, retid=request.rpc_id,
288 return jsonrpc_error(request, retid=request.rpc_id,
289 message='Internal server error')
289 message='Internal server error')
290
290
291
291
292 def setup_request(request):
292 def setup_request(request):
293 """
293 """
294 Parse a JSON-RPC request body. It's used inside the predicates method
294 Parse a JSON-RPC request body. It's used inside the predicates method
295 to validate and bootstrap requests for usage in rpc calls.
295 to validate and bootstrap requests for usage in rpc calls.
296
296
297 We need to raise JSONRPCError here if we want to return some errors back to
297 We need to raise JSONRPCError here if we want to return some errors back to
298 user.
298 user.
299 """
299 """
300
300
301 log.debug('Executing setup request: %r', request)
301 log.debug('Executing setup request: %r', request)
302 request.rpc_ip_addr = get_ip_addr(request.environ)
302 request.rpc_ip_addr = get_ip_addr(request.environ)
303 # TODO(marcink): deprecate GET at some point
303 # TODO(marcink): deprecate GET at some point
304 if request.method not in ['POST', 'GET']:
304 if request.method not in ['POST', 'GET']:
305 log.debug('unsupported request method "%s"', request.method)
305 log.debug('unsupported request method "%s"', request.method)
306 raise JSONRPCError(
306 raise JSONRPCError(
307 'unsupported request method "%s". Please use POST' % request.method)
307 'unsupported request method "%s". Please use POST' % request.method)
308
308
309 if 'CONTENT_LENGTH' not in request.environ:
309 if 'CONTENT_LENGTH' not in request.environ:
310 log.debug("No Content-Length")
310 log.debug("No Content-Length")
311 raise JSONRPCError("Empty body, No Content-Length in request")
311 raise JSONRPCError("Empty body, No Content-Length in request")
312
312
313 else:
313 else:
314 length = request.environ['CONTENT_LENGTH']
314 length = request.environ['CONTENT_LENGTH']
315 log.debug('Content-Length: %s', length)
315 log.debug('Content-Length: %s', length)
316
316
317 if length == 0:
317 if length == 0:
318 log.debug("Content-Length is 0")
318 log.debug("Content-Length is 0")
319 raise JSONRPCError("Content-Length is 0")
319 raise JSONRPCError("Content-Length is 0")
320
320
321 raw_body = request.body
321 raw_body = request.body
322 try:
322 try:
323 json_body = json.loads(raw_body)
323 json_body = json.loads(raw_body)
324 except ValueError as e:
324 except ValueError as e:
325 # catch JSON errors Here
325 # catch JSON errors Here
326 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
326 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
327
327
328 request.rpc_id = json_body.get('id')
328 request.rpc_id = json_body.get('id')
329 request.rpc_method = json_body.get('method')
329 request.rpc_method = json_body.get('method')
330
330
331 # check required base parameters
331 # check required base parameters
332 try:
332 try:
333 api_key = json_body.get('api_key')
333 api_key = json_body.get('api_key')
334 if not api_key:
334 if not api_key:
335 api_key = json_body.get('auth_token')
335 api_key = json_body.get('auth_token')
336
336
337 if not api_key:
337 if not api_key:
338 raise KeyError('api_key or auth_token')
338 raise KeyError('api_key or auth_token')
339
339
340 # TODO(marcink): support passing in token in request header
340 # TODO(marcink): support passing in token in request header
341
341
342 request.rpc_api_key = api_key
342 request.rpc_api_key = api_key
343 request.rpc_id = json_body['id']
343 request.rpc_id = json_body['id']
344 request.rpc_method = json_body['method']
344 request.rpc_method = json_body['method']
345 request.rpc_params = json_body['args'] \
345 request.rpc_params = json_body['args'] \
346 if isinstance(json_body['args'], dict) else {}
346 if isinstance(json_body['args'], dict) else {}
347
347
348 log.debug(
348 log.debug(
349 'method: %s, params: %s' % (request.rpc_method, request.rpc_params))
349 'method: %s, params: %s' % (request.rpc_method, request.rpc_params))
350 except KeyError as e:
350 except KeyError as e:
351 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
351 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
352
352
353 log.debug('setup complete, now handling method:%s rpcid:%s',
353 log.debug('setup complete, now handling method:%s rpcid:%s',
354 request.rpc_method, request.rpc_id, )
354 request.rpc_method, request.rpc_id, )
355
355
356
356
357 class RoutePredicate(object):
357 class RoutePredicate(object):
358 def __init__(self, val, config):
358 def __init__(self, val, config):
359 self.val = val
359 self.val = val
360
360
361 def text(self):
361 def text(self):
362 return 'jsonrpc route = %s' % self.val
362 return 'jsonrpc route = %s' % self.val
363
363
364 phash = text
364 phash = text
365
365
366 def __call__(self, info, request):
366 def __call__(self, info, request):
367 if self.val:
367 if self.val:
368 # potentially setup and bootstrap our call
368 # potentially setup and bootstrap our call
369 setup_request(request)
369 setup_request(request)
370
370
371 # Always return True so that even if it isn't a valid RPC it
371 # Always return True so that even if it isn't a valid RPC it
372 # will fall through to the underlaying handlers like notfound_view
372 # will fall through to the underlaying handlers like notfound_view
373 return True
373 return True
374
374
375
375
376 class NotFoundPredicate(object):
376 class NotFoundPredicate(object):
377 def __init__(self, val, config):
377 def __init__(self, val, config):
378 self.val = val
378 self.val = val
379 self.methods = config.registry.jsonrpc_methods
379 self.methods = config.registry.jsonrpc_methods
380
380
381 def text(self):
381 def text(self):
382 return 'jsonrpc method not found = {}.'.format(self.val)
382 return 'jsonrpc method not found = {}.'.format(self.val)
383
383
384 phash = text
384 phash = text
385
385
386 def __call__(self, info, request):
386 def __call__(self, info, request):
387 return hasattr(request, 'rpc_method')
387 return hasattr(request, 'rpc_method')
388
388
389
389
390 class MethodPredicate(object):
390 class MethodPredicate(object):
391 def __init__(self, val, config):
391 def __init__(self, val, config):
392 self.method = val
392 self.method = val
393
393
394 def text(self):
394 def text(self):
395 return 'jsonrpc method = %s' % self.method
395 return 'jsonrpc method = %s' % self.method
396
396
397 phash = text
397 phash = text
398
398
399 def __call__(self, context, request):
399 def __call__(self, context, request):
400 # we need to explicitly return False here, so pyramid doesn't try to
400 # we need to explicitly return False here, so pyramid doesn't try to
401 # execute our view directly. We need our main handler to execute things
401 # execute our view directly. We need our main handler to execute things
402 return getattr(request, 'rpc_method') == self.method
402 return getattr(request, 'rpc_method') == self.method
403
403
404
404
405 def add_jsonrpc_method(config, view, **kwargs):
405 def add_jsonrpc_method(config, view, **kwargs):
406 # pop the method name
406 # pop the method name
407 method = kwargs.pop('method', None)
407 method = kwargs.pop('method', None)
408
408
409 if method is None:
409 if method is None:
410 raise ConfigurationError(
410 raise ConfigurationError(
411 'Cannot register a JSON-RPC method without specifying the '
411 'Cannot register a JSON-RPC method without specifying the '
412 '"method"')
412 '"method"')
413
413
414 # we define custom predicate, to enable to detect conflicting methods,
414 # we define custom predicate, to enable to detect conflicting methods,
415 # those predicates are kind of "translation" from the decorator variables
415 # those predicates are kind of "translation" from the decorator variables
416 # to internal predicates names
416 # to internal predicates names
417
417
418 kwargs['jsonrpc_method'] = method
418 kwargs['jsonrpc_method'] = method
419
419
420 # register our view into global view store for validation
420 # register our view into global view store for validation
421 config.registry.jsonrpc_methods[method] = view
421 config.registry.jsonrpc_methods[method] = view
422
422
423 # we're using our main request_view handler, here, so each method
423 # we're using our main request_view handler, here, so each method
424 # has a unified handler for itself
424 # has a unified handler for itself
425 config.add_view(request_view, route_name='apiv2', **kwargs)
425 config.add_view(request_view, route_name='apiv2', **kwargs)
426
426
427
427
428 class jsonrpc_method(object):
428 class jsonrpc_method(object):
429 """
429 """
430 decorator that works similar to @add_view_config decorator,
430 decorator that works similar to @add_view_config decorator,
431 but tailored for our JSON RPC
431 but tailored for our JSON RPC
432 """
432 """
433
433
434 venusian = venusian # for testing injection
434 venusian = venusian # for testing injection
435
435
436 def __init__(self, method=None, **kwargs):
436 def __init__(self, method=None, **kwargs):
437 self.method = method
437 self.method = method
438 self.kwargs = kwargs
438 self.kwargs = kwargs
439
439
440 def __call__(self, wrapped):
440 def __call__(self, wrapped):
441 kwargs = self.kwargs.copy()
441 kwargs = self.kwargs.copy()
442 kwargs['method'] = self.method or wrapped.__name__
442 kwargs['method'] = self.method or wrapped.__name__
443 depth = kwargs.pop('_depth', 0)
443 depth = kwargs.pop('_depth', 0)
444
444
445 def callback(context, name, ob):
445 def callback(context, name, ob):
446 config = context.config.with_package(info.module)
446 config = context.config.with_package(info.module)
447 config.add_jsonrpc_method(view=ob, **kwargs)
447 config.add_jsonrpc_method(view=ob, **kwargs)
448
448
449 info = venusian.attach(wrapped, callback, category='pyramid',
449 info = venusian.attach(wrapped, callback, category='pyramid',
450 depth=depth + 1)
450 depth=depth + 1)
451 if info.scope == 'class':
451 if info.scope == 'class':
452 # ensure that attr is set if decorating a class method
452 # ensure that attr is set if decorating a class method
453 kwargs.setdefault('attr', wrapped.__name__)
453 kwargs.setdefault('attr', wrapped.__name__)
454
454
455 kwargs['_info'] = info.codeinfo # fbo action_method
455 kwargs['_info'] = info.codeinfo # fbo action_method
456 return wrapped
456 return wrapped
457
457
458
458
459 class jsonrpc_deprecated_method(object):
459 class jsonrpc_deprecated_method(object):
460 """
460 """
461 Marks method as deprecated, adds log.warning, and inject special key to
461 Marks method as deprecated, adds log.warning, and inject special key to
462 the request variable to mark method as deprecated.
462 the request variable to mark method as deprecated.
463 Also injects special docstring that extract_docs will catch to mark
463 Also injects special docstring that extract_docs will catch to mark
464 method as deprecated.
464 method as deprecated.
465
465
466 :param use_method: specify which method should be used instead of
466 :param use_method: specify which method should be used instead of
467 the decorated one
467 the decorated one
468
468
469 Use like::
469 Use like::
470
470
471 @jsonrpc_method()
471 @jsonrpc_method()
472 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
472 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
473 def old_func(request, apiuser, arg1, arg2):
473 def old_func(request, apiuser, arg1, arg2):
474 ...
474 ...
475 """
475 """
476
476
477 def __init__(self, use_method, deprecated_at_version):
477 def __init__(self, use_method, deprecated_at_version):
478 self.use_method = use_method
478 self.use_method = use_method
479 self.deprecated_at_version = deprecated_at_version
479 self.deprecated_at_version = deprecated_at_version
480 self.deprecated_msg = ''
480 self.deprecated_msg = ''
481
481
482 def __call__(self, func):
482 def __call__(self, func):
483 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
483 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
484 method=self.use_method)
484 method=self.use_method)
485
485
486 docstring = """\n
486 docstring = """\n
487 .. deprecated:: {version}
487 .. deprecated:: {version}
488
488
489 {deprecation_message}
489 {deprecation_message}
490
490
491 {original_docstring}
491 {original_docstring}
492 """
492 """
493 func.__doc__ = docstring.format(
493 func.__doc__ = docstring.format(
494 version=self.deprecated_at_version,
494 version=self.deprecated_at_version,
495 deprecation_message=self.deprecated_msg,
495 deprecation_message=self.deprecated_msg,
496 original_docstring=func.__doc__)
496 original_docstring=func.__doc__)
497 return decorator.decorator(self.__wrapper, func)
497 return decorator.decorator(self.__wrapper, func)
498
498
499 def __wrapper(self, func, *fargs, **fkwargs):
499 def __wrapper(self, func, *fargs, **fkwargs):
500 log.warning('DEPRECATED API CALL on function %s, please '
500 log.warning('DEPRECATED API CALL on function %s, please '
501 'use `%s` instead', func, self.use_method)
501 'use `%s` instead', func, self.use_method)
502 # alter function docstring to mark as deprecated, this is picked up
502 # alter function docstring to mark as deprecated, this is picked up
503 # via fabric file that generates API DOC.
503 # via fabric file that generates API DOC.
504 result = func(*fargs, **fkwargs)
504 result = func(*fargs, **fkwargs)
505
505
506 request = fargs[0]
506 request = fargs[0]
507 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
507 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
508 return result
508 return result
509
509
510
510
511 def includeme(config):
511 def includeme(config):
512 plugin_module = 'rhodecode.api'
512 plugin_module = 'rhodecode.api'
513 plugin_settings = get_plugin_settings(
513 plugin_settings = get_plugin_settings(
514 plugin_module, config.registry.settings)
514 plugin_module, config.registry.settings)
515
515
516 if not hasattr(config.registry, 'jsonrpc_methods'):
516 if not hasattr(config.registry, 'jsonrpc_methods'):
517 config.registry.jsonrpc_methods = OrderedDict()
517 config.registry.jsonrpc_methods = OrderedDict()
518
518
519 # match filter by given method only
519 # match filter by given method only
520 config.add_view_predicate('jsonrpc_method', MethodPredicate)
520 config.add_view_predicate('jsonrpc_method', MethodPredicate)
521
521
522 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
522 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
523 serializer=json.dumps, indent=4))
523 serializer=json.dumps, indent=4))
524 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
524 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
525
525
526 config.add_route_predicate(
526 config.add_route_predicate(
527 'jsonrpc_call', RoutePredicate)
527 'jsonrpc_call', RoutePredicate)
528
528
529 config.add_route(
529 config.add_route(
530 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
530 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
531
531
532 config.scan(plugin_module, ignore='rhodecode.api.tests')
532 config.scan(plugin_module, ignore='rhodecode.api.tests')
533 # register some exception handling view
533 # register some exception handling view
534 config.add_view(exception_view, context=JSONRPCBaseError)
534 config.add_view(exception_view, context=JSONRPCBaseError)
535 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
535 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
536 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
536 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
@@ -1,140 +1,140 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 RhodeCode authentication token plugin for built in internal auth
22 RhodeCode authentication token plugin for built in internal auth
23 """
23 """
24
24
25 import logging
25 import logging
26
26
27 from sqlalchemy.ext.hybrid import hybrid_property
27 from sqlalchemy.ext.hybrid import hybrid_property
28
28
29 from rhodecode.translation import _
29 from rhodecode.translation import _
30 from rhodecode.authentication.base import RhodeCodeAuthPluginBase, VCS_TYPE
30 from rhodecode.authentication.base import RhodeCodeAuthPluginBase, VCS_TYPE
31 from rhodecode.authentication.routes import AuthnPluginResourceBase
31 from rhodecode.authentication.routes import AuthnPluginResourceBase
32 from rhodecode.model.db import User, UserApiKeys
32 from rhodecode.model.db import User, UserApiKeys
33
33
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 def plugin_factory(plugin_id, *args, **kwds):
38 def plugin_factory(plugin_id, *args, **kwds):
39 plugin = RhodeCodeAuthPlugin(plugin_id)
39 plugin = RhodeCodeAuthPlugin(plugin_id)
40 return plugin
40 return plugin
41
41
42
42
43 class RhodecodeAuthnResource(AuthnPluginResourceBase):
43 class RhodecodeAuthnResource(AuthnPluginResourceBase):
44 pass
44 pass
45
45
46
46
47 class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
47 class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
48 """
48 """
49 Enables usage of authentication tokens for vcs operations.
49 Enables usage of authentication tokens for vcs operations.
50 """
50 """
51
51
52 def includeme(self, config):
52 def includeme(self, config):
53 config.add_authn_plugin(self)
53 config.add_authn_plugin(self)
54 config.add_authn_resource(self.get_id(), RhodecodeAuthnResource(self))
54 config.add_authn_resource(self.get_id(), RhodecodeAuthnResource(self))
55 config.add_view(
55 config.add_view(
56 'rhodecode.authentication.views.AuthnPluginViewBase',
56 'rhodecode.authentication.views.AuthnPluginViewBase',
57 attr='settings_get',
57 attr='settings_get',
58 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
58 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
59 request_method='GET',
59 request_method='GET',
60 route_name='auth_home',
60 route_name='auth_home',
61 context=RhodecodeAuthnResource)
61 context=RhodecodeAuthnResource)
62 config.add_view(
62 config.add_view(
63 'rhodecode.authentication.views.AuthnPluginViewBase',
63 'rhodecode.authentication.views.AuthnPluginViewBase',
64 attr='settings_post',
64 attr='settings_post',
65 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
65 renderer='rhodecode:templates/admin/auth/plugin_settings.mako',
66 request_method='POST',
66 request_method='POST',
67 route_name='auth_home',
67 route_name='auth_home',
68 context=RhodecodeAuthnResource)
68 context=RhodecodeAuthnResource)
69
69
70 def get_display_name(self):
70 def get_display_name(self):
71 return _('Rhodecode Token Auth')
71 return _('Rhodecode Token Auth')
72
72
73 @hybrid_property
73 @hybrid_property
74 def name(self):
74 def name(self):
75 return "authtoken"
75 return "authtoken"
76
76
77 def user_activation_state(self):
77 def user_activation_state(self):
78 def_user_perms = User.get_default_user().AuthUser.permissions['global']
78 def_user_perms = User.get_default_user().AuthUser.permissions['global']
79 return 'hg.register.auto_activate' in def_user_perms
79 return 'hg.register.auto_activate' in def_user_perms
80
80
81 def allows_authentication_from(
81 def allows_authentication_from(
82 self, user, allows_non_existing_user=True,
82 self, user, allows_non_existing_user=True,
83 allowed_auth_plugins=None, allowed_auth_sources=None):
83 allowed_auth_plugins=None, allowed_auth_sources=None):
84 """
84 """
85 Custom method for this auth that doesn't accept empty users. And also
85 Custom method for this auth that doesn't accept empty users. And also
86 allows users from all other active plugins to use it and also
86 allows users from all other active plugins to use it and also
87 authenticate against it. But only via vcs mode
87 authenticate against it. But only via vcs mode
88 """
88 """
89 from rhodecode.authentication.base import get_authn_registry
89 from rhodecode.authentication.base import get_authn_registry
90 authn_registry = get_authn_registry()
90 authn_registry = get_authn_registry()
91
91
92 active_plugins = set(
92 active_plugins = set(
93 [x.name for x in authn_registry.get_plugins_for_authentication()])
93 [x.name for x in authn_registry.get_plugins_for_authentication()])
94 active_plugins.discard(self.name)
94 active_plugins.discard(self.name)
95
95
96 allowed_auth_plugins = [self.name] + list(active_plugins)
96 allowed_auth_plugins = [self.name] + list(active_plugins)
97 # only for vcs operations
97 # only for vcs operations
98 allowed_auth_sources = [VCS_TYPE]
98 allowed_auth_sources = [VCS_TYPE]
99
99
100 return super(RhodeCodeAuthPlugin, self).allows_authentication_from(
100 return super(RhodeCodeAuthPlugin, self).allows_authentication_from(
101 user, allows_non_existing_user=False,
101 user, allows_non_existing_user=False,
102 allowed_auth_plugins=allowed_auth_plugins,
102 allowed_auth_plugins=allowed_auth_plugins,
103 allowed_auth_sources=allowed_auth_sources)
103 allowed_auth_sources=allowed_auth_sources)
104
104
105 def auth(self, userobj, username, password, settings, **kwargs):
105 def auth(self, userobj, username, password, settings, **kwargs):
106 if not userobj:
106 if not userobj:
107 log.debug('userobj was:%s skipping' % (userobj, ))
107 log.debug('userobj was:%s skipping' % (userobj, ))
108 return None
108 return None
109
109
110 user_attrs = {
110 user_attrs = {
111 "username": userobj.username,
111 "username": userobj.username,
112 "firstname": userobj.firstname,
112 "firstname": userobj.firstname,
113 "lastname": userobj.lastname,
113 "lastname": userobj.lastname,
114 "groups": [],
114 "groups": [],
115 "email": userobj.email,
115 "email": userobj.email,
116 "admin": userobj.admin,
116 "admin": userobj.admin,
117 "active": userobj.active,
117 "active": userobj.active,
118 "active_from_extern": userobj.active,
118 "active_from_extern": userobj.active,
119 "extern_name": userobj.user_id,
119 "extern_name": userobj.user_id,
120 "extern_type": userobj.extern_type,
120 "extern_type": userobj.extern_type,
121 }
121 }
122
122
123 log.debug('Authenticating user with args %s', user_attrs)
123 log.debug('Authenticating user with args %s', user_attrs)
124 if userobj.active:
124 if userobj.active:
125 role = UserApiKeys.ROLE_VCS
125 token_match = userobj.authenticate_by_token(
126 active_tokens = [x.api_key for x in
126 password, roles=[UserApiKeys.ROLE_VCS])
127 User.extra_valid_auth_tokens(userobj, role=role)]
127
128 if userobj.username == username and password in active_tokens:
128 if userobj.username == username and token_match:
129 log.info(
129 log.info(
130 'user `%s` successfully authenticated via %s',
130 'user `%s` successfully authenticated via %s',
131 user_attrs['username'], self.name)
131 user_attrs['username'], self.name)
132 return user_attrs
132 return user_attrs
133 log.error(
133 log.error(
134 'user `%s` failed to authenticate via %s, reason: bad or '
134 'user `%s` failed to authenticate via %s, reason: bad or '
135 'inactive token.', username, self.name)
135 'inactive token.', username, self.name)
136 else:
136 else:
137 log.warning(
137 log.warning(
138 'user `%s` failed to authenticate via %s, reason: account not '
138 'user `%s` failed to authenticate via %s, reason: account not '
139 'active.', username, self.name)
139 'active.', username, self.name)
140 return None
140 return None
@@ -1,181 +1,180 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Feed controller for RhodeCode
22 Feed controller for RhodeCode
23 """
23 """
24
24
25 import logging
25 import logging
26
26
27 import pytz
27 import pytz
28 from pylons import url, response, tmpl_context as c
28 from pylons import url, response, tmpl_context as c
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30
30
31 from beaker.cache import cache_region, region_invalidate
31 from beaker.cache import cache_region
32 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
32 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
33
33
34 from rhodecode.model.db import CacheKey
34 from rhodecode.model.db import CacheKey, UserApiKeys
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib import caches
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
36 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
38 from rhodecode.lib.base import BaseRepoController
37 from rhodecode.lib.base import BaseRepoController
39 from rhodecode.lib.diffs import DiffProcessor, LimitedDiffContainer
38 from rhodecode.lib.diffs import DiffProcessor, LimitedDiffContainer
40 from rhodecode.lib.utils2 import safe_int, str2bool
39 from rhodecode.lib.utils2 import safe_int, str2bool
41 from rhodecode.lib.utils import PartialRenderer
40 from rhodecode.lib.utils import PartialRenderer
42
41
43 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
44
43
45
44
46 class FeedController(BaseRepoController):
45 class FeedController(BaseRepoController):
47
46
48 def _get_config(self):
47 def _get_config(self):
49 import rhodecode
48 import rhodecode
50 config = rhodecode.CONFIG
49 config = rhodecode.CONFIG
51
50
52 return {
51 return {
53 'language': 'en-us',
52 'language': 'en-us',
54 'feed_ttl': '5', # TTL of feed,
53 'feed_ttl': '5', # TTL of feed,
55 'feed_include_diff':
54 'feed_include_diff':
56 str2bool(config.get('rss_include_diff', False)),
55 str2bool(config.get('rss_include_diff', False)),
57 'feed_items_per_page':
56 'feed_items_per_page':
58 safe_int(config.get('rss_items_per_page', 20)),
57 safe_int(config.get('rss_items_per_page', 20)),
59 'feed_diff_limit':
58 'feed_diff_limit':
60 # we need to protect from parsing huge diffs here other way
59 # we need to protect from parsing huge diffs here other way
61 # we can kill the server
60 # we can kill the server
62 safe_int(config.get('rss_cut_off_limit', 32 * 1024)),
61 safe_int(config.get('rss_cut_off_limit', 32 * 1024)),
63 }
62 }
64
63
65 @LoginRequired(auth_token_access=True)
64 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
66 def __before__(self):
65 def __before__(self):
67 super(FeedController, self).__before__()
66 super(FeedController, self).__before__()
68 config = self._get_config()
67 config = self._get_config()
69 # common values for feeds
68 # common values for feeds
70 self.description = _('Changes on %s repository')
69 self.description = _('Changes on %s repository')
71 self.title = self.title = _('%s %s feed') % (c.rhodecode_name, '%s')
70 self.title = self.title = _('%s %s feed') % (c.rhodecode_name, '%s')
72 self.language = config["language"]
71 self.language = config["language"]
73 self.ttl = config["feed_ttl"]
72 self.ttl = config["feed_ttl"]
74 self.feed_include_diff = config['feed_include_diff']
73 self.feed_include_diff = config['feed_include_diff']
75 self.feed_diff_limit = config['feed_diff_limit']
74 self.feed_diff_limit = config['feed_diff_limit']
76 self.feed_items_per_page = config['feed_items_per_page']
75 self.feed_items_per_page = config['feed_items_per_page']
77
76
78 def __changes(self, commit):
77 def __changes(self, commit):
79 diff_processor = DiffProcessor(
78 diff_processor = DiffProcessor(
80 commit.diff(), diff_limit=self.feed_diff_limit)
79 commit.diff(), diff_limit=self.feed_diff_limit)
81 _parsed = diff_processor.prepare(inline_diff=False)
80 _parsed = diff_processor.prepare(inline_diff=False)
82 limited_diff = isinstance(_parsed, LimitedDiffContainer)
81 limited_diff = isinstance(_parsed, LimitedDiffContainer)
83
82
84 return _parsed, limited_diff
83 return _parsed, limited_diff
85
84
86 def _get_title(self, commit):
85 def _get_title(self, commit):
87 return h.shorter(commit.message, 160)
86 return h.shorter(commit.message, 160)
88
87
89 def _get_description(self, commit):
88 def _get_description(self, commit):
90 _renderer = PartialRenderer('feed/atom_feed_entry.mako')
89 _renderer = PartialRenderer('feed/atom_feed_entry.mako')
91 parsed_diff, limited_diff = self.__changes(commit)
90 parsed_diff, limited_diff = self.__changes(commit)
92 return _renderer(
91 return _renderer(
93 'body',
92 'body',
94 commit=commit,
93 commit=commit,
95 parsed_diff=parsed_diff,
94 parsed_diff=parsed_diff,
96 limited_diff=limited_diff,
95 limited_diff=limited_diff,
97 feed_include_diff=self.feed_include_diff,
96 feed_include_diff=self.feed_include_diff,
98 )
97 )
99
98
100 def _set_timezone(self, date, tzinfo=pytz.utc):
99 def _set_timezone(self, date, tzinfo=pytz.utc):
101 if not getattr(date, "tzinfo", None):
100 if not getattr(date, "tzinfo", None):
102 date.replace(tzinfo=tzinfo)
101 date.replace(tzinfo=tzinfo)
103 return date
102 return date
104
103
105 def _get_commits(self):
104 def _get_commits(self):
106 return list(c.rhodecode_repo[-self.feed_items_per_page:])
105 return list(c.rhodecode_repo[-self.feed_items_per_page:])
107
106
108 @HasRepoPermissionAnyDecorator(
107 @HasRepoPermissionAnyDecorator(
109 'repository.read', 'repository.write', 'repository.admin')
108 'repository.read', 'repository.write', 'repository.admin')
110 def atom(self, repo_name):
109 def atom(self, repo_name):
111 """Produce an atom-1.0 feed via feedgenerator module"""
110 """Produce an atom-1.0 feed via feedgenerator module"""
112
111
113 @cache_region('long_term')
112 @cache_region('long_term')
114 def _generate_feed(cache_key):
113 def _generate_feed(cache_key):
115 feed = Atom1Feed(
114 feed = Atom1Feed(
116 title=self.title % repo_name,
115 title=self.title % repo_name,
117 link=url('summary_home', repo_name=repo_name, qualified=True),
116 link=url('summary_home', repo_name=repo_name, qualified=True),
118 description=self.description % repo_name,
117 description=self.description % repo_name,
119 language=self.language,
118 language=self.language,
120 ttl=self.ttl
119 ttl=self.ttl
121 )
120 )
122
121
123 for commit in reversed(self._get_commits()):
122 for commit in reversed(self._get_commits()):
124 date = self._set_timezone(commit.date)
123 date = self._set_timezone(commit.date)
125 feed.add_item(
124 feed.add_item(
126 title=self._get_title(commit),
125 title=self._get_title(commit),
127 author_name=commit.author,
126 author_name=commit.author,
128 description=self._get_description(commit),
127 description=self._get_description(commit),
129 link=url('changeset_home', repo_name=repo_name,
128 link=url('changeset_home', repo_name=repo_name,
130 revision=commit.raw_id, qualified=True),
129 revision=commit.raw_id, qualified=True),
131 pubdate=date,)
130 pubdate=date,)
132
131
133 return feed.mime_type, feed.writeString('utf-8')
132 return feed.mime_type, feed.writeString('utf-8')
134
133
135 invalidator_context = CacheKey.repo_context_cache(
134 invalidator_context = CacheKey.repo_context_cache(
136 _generate_feed, repo_name, CacheKey.CACHE_TYPE_ATOM)
135 _generate_feed, repo_name, CacheKey.CACHE_TYPE_ATOM)
137
136
138 with invalidator_context as context:
137 with invalidator_context as context:
139 context.invalidate()
138 context.invalidate()
140 mime_type, feed = context.compute()
139 mime_type, feed = context.compute()
141
140
142 response.content_type = mime_type
141 response.content_type = mime_type
143 return feed
142 return feed
144
143
145 @HasRepoPermissionAnyDecorator(
144 @HasRepoPermissionAnyDecorator(
146 'repository.read', 'repository.write', 'repository.admin')
145 'repository.read', 'repository.write', 'repository.admin')
147 def rss(self, repo_name):
146 def rss(self, repo_name):
148 """Produce an rss2 feed via feedgenerator module"""
147 """Produce an rss2 feed via feedgenerator module"""
149
148
150 @cache_region('long_term')
149 @cache_region('long_term')
151 def _generate_feed(cache_key):
150 def _generate_feed(cache_key):
152 feed = Rss201rev2Feed(
151 feed = Rss201rev2Feed(
153 title=self.title % repo_name,
152 title=self.title % repo_name,
154 link=url('summary_home', repo_name=repo_name,
153 link=url('summary_home', repo_name=repo_name,
155 qualified=True),
154 qualified=True),
156 description=self.description % repo_name,
155 description=self.description % repo_name,
157 language=self.language,
156 language=self.language,
158 ttl=self.ttl
157 ttl=self.ttl
159 )
158 )
160
159
161 for commit in reversed(self._get_commits()):
160 for commit in reversed(self._get_commits()):
162 date = self._set_timezone(commit.date)
161 date = self._set_timezone(commit.date)
163 feed.add_item(
162 feed.add_item(
164 title=self._get_title(commit),
163 title=self._get_title(commit),
165 author_name=commit.author,
164 author_name=commit.author,
166 description=self._get_description(commit),
165 description=self._get_description(commit),
167 link=url('changeset_home', repo_name=repo_name,
166 link=url('changeset_home', repo_name=repo_name,
168 revision=commit.raw_id, qualified=True),
167 revision=commit.raw_id, qualified=True),
169 pubdate=date,)
168 pubdate=date,)
170
169
171 return feed.mime_type, feed.writeString('utf-8')
170 return feed.mime_type, feed.writeString('utf-8')
172
171
173 invalidator_context = CacheKey.repo_context_cache(
172 invalidator_context = CacheKey.repo_context_cache(
174 _generate_feed, repo_name, CacheKey.CACHE_TYPE_RSS)
173 _generate_feed, repo_name, CacheKey.CACHE_TYPE_RSS)
175
174
176 with invalidator_context as context:
175 with invalidator_context as context:
177 context.invalidate()
176 context.invalidate()
178 mime_type, feed = context.compute()
177 mime_type, feed = context.compute()
179
178
180 response.content_type = mime_type
179 response.content_type = mime_type
181 return feed
180 return feed
@@ -1,306 +1,306 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Journal / user event log controller for rhodecode
22 Journal / user event log controller for rhodecode
23 """
23 """
24
24
25 import logging
25 import logging
26 from itertools import groupby
26 from itertools import groupby
27
27
28 from sqlalchemy import or_
28 from sqlalchemy import or_
29 from sqlalchemy.orm import joinedload
29 from sqlalchemy.orm import joinedload
30
30
31 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
31 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
32
32
33 from webob.exc import HTTPBadRequest
33 from webob.exc import HTTPBadRequest
34 from pylons import request, tmpl_context as c, response, url
34 from pylons import request, tmpl_context as c, response, url
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 from rhodecode.controllers.admin.admin import _journal_filter
37 from rhodecode.controllers.admin.admin import _journal_filter
38 from rhodecode.model.db import UserLog, UserFollowing, User
38 from rhodecode.model.db import UserLog, UserFollowing, User, UserApiKeys
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40 import rhodecode.lib.helpers as h
40 import rhodecode.lib.helpers as h
41 from rhodecode.lib.helpers import Page
41 from rhodecode.lib.helpers import Page
42 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
42 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
43 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.utils2 import safe_int, AttributeDict
44 from rhodecode.lib.utils2 import safe_int, AttributeDict
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class JournalController(BaseController):
49 class JournalController(BaseController):
50
50
51 def __before__(self):
51 def __before__(self):
52 super(JournalController, self).__before__()
52 super(JournalController, self).__before__()
53 self.language = 'en-us'
53 self.language = 'en-us'
54 self.ttl = "5"
54 self.ttl = "5"
55 self.feed_nr = 20
55 self.feed_nr = 20
56 c.search_term = request.GET.get('filter')
56 c.search_term = request.GET.get('filter')
57
57
58 def _get_daily_aggregate(self, journal):
58 def _get_daily_aggregate(self, journal):
59 groups = []
59 groups = []
60 for k, g in groupby(journal, lambda x: x.action_as_day):
60 for k, g in groupby(journal, lambda x: x.action_as_day):
61 user_group = []
61 user_group = []
62 #groupby username if it's a present value, else fallback to journal username
62 #groupby username if it's a present value, else fallback to journal username
63 for _, g2 in groupby(list(g), lambda x: x.user.username if x.user else x.username):
63 for _, g2 in groupby(list(g), lambda x: x.user.username if x.user else x.username):
64 l = list(g2)
64 l = list(g2)
65 user_group.append((l[0].user, l))
65 user_group.append((l[0].user, l))
66
66
67 groups.append((k, user_group,))
67 groups.append((k, user_group,))
68
68
69 return groups
69 return groups
70
70
71 def _get_journal_data(self, following_repos):
71 def _get_journal_data(self, following_repos):
72 repo_ids = [x.follows_repository.repo_id for x in following_repos
72 repo_ids = [x.follows_repository.repo_id for x in following_repos
73 if x.follows_repository is not None]
73 if x.follows_repository is not None]
74 user_ids = [x.follows_user.user_id for x in following_repos
74 user_ids = [x.follows_user.user_id for x in following_repos
75 if x.follows_user is not None]
75 if x.follows_user is not None]
76
76
77 filtering_criterion = None
77 filtering_criterion = None
78
78
79 if repo_ids and user_ids:
79 if repo_ids and user_ids:
80 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
80 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
81 UserLog.user_id.in_(user_ids))
81 UserLog.user_id.in_(user_ids))
82 if repo_ids and not user_ids:
82 if repo_ids and not user_ids:
83 filtering_criterion = UserLog.repository_id.in_(repo_ids)
83 filtering_criterion = UserLog.repository_id.in_(repo_ids)
84 if not repo_ids and user_ids:
84 if not repo_ids and user_ids:
85 filtering_criterion = UserLog.user_id.in_(user_ids)
85 filtering_criterion = UserLog.user_id.in_(user_ids)
86 if filtering_criterion is not None:
86 if filtering_criterion is not None:
87 journal = self.sa.query(UserLog)\
87 journal = self.sa.query(UserLog)\
88 .options(joinedload(UserLog.user))\
88 .options(joinedload(UserLog.user))\
89 .options(joinedload(UserLog.repository))
89 .options(joinedload(UserLog.repository))
90 #filter
90 #filter
91 try:
91 try:
92 journal = _journal_filter(journal, c.search_term)
92 journal = _journal_filter(journal, c.search_term)
93 except Exception:
93 except Exception:
94 # we want this to crash for now
94 # we want this to crash for now
95 raise
95 raise
96 journal = journal.filter(filtering_criterion)\
96 journal = journal.filter(filtering_criterion)\
97 .order_by(UserLog.action_date.desc())
97 .order_by(UserLog.action_date.desc())
98 else:
98 else:
99 journal = []
99 journal = []
100
100
101 return journal
101 return journal
102
102
103 def _atom_feed(self, repos, public=True):
103 def _atom_feed(self, repos, public=True):
104 journal = self._get_journal_data(repos)
104 journal = self._get_journal_data(repos)
105 if public:
105 if public:
106 _link = url('public_journal_atom', qualified=True)
106 _link = url('public_journal_atom', qualified=True)
107 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
107 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
108 'atom feed')
108 'atom feed')
109 else:
109 else:
110 _link = url('journal_atom', qualified=True)
110 _link = url('journal_atom', qualified=True)
111 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
111 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
112
112
113 feed = Atom1Feed(title=_desc,
113 feed = Atom1Feed(title=_desc,
114 link=_link,
114 link=_link,
115 description=_desc,
115 description=_desc,
116 language=self.language,
116 language=self.language,
117 ttl=self.ttl)
117 ttl=self.ttl)
118
118
119 for entry in journal[:self.feed_nr]:
119 for entry in journal[:self.feed_nr]:
120 user = entry.user
120 user = entry.user
121 if user is None:
121 if user is None:
122 #fix deleted users
122 #fix deleted users
123 user = AttributeDict({'short_contact': entry.username,
123 user = AttributeDict({'short_contact': entry.username,
124 'email': '',
124 'email': '',
125 'full_contact': ''})
125 'full_contact': ''})
126 action, action_extra, ico = h.action_parser(entry, feed=True)
126 action, action_extra, ico = h.action_parser(entry, feed=True)
127 title = "%s - %s %s" % (user.short_contact, action(),
127 title = "%s - %s %s" % (user.short_contact, action(),
128 entry.repository.repo_name)
128 entry.repository.repo_name)
129 desc = action_extra()
129 desc = action_extra()
130 _url = None
130 _url = None
131 if entry.repository is not None:
131 if entry.repository is not None:
132 _url = url('changelog_home',
132 _url = url('changelog_home',
133 repo_name=entry.repository.repo_name,
133 repo_name=entry.repository.repo_name,
134 qualified=True)
134 qualified=True)
135
135
136 feed.add_item(title=title,
136 feed.add_item(title=title,
137 pubdate=entry.action_date,
137 pubdate=entry.action_date,
138 link=_url or url('', qualified=True),
138 link=_url or url('', qualified=True),
139 author_email=user.email,
139 author_email=user.email,
140 author_name=user.full_contact,
140 author_name=user.full_contact,
141 description=desc)
141 description=desc)
142
142
143 response.content_type = feed.mime_type
143 response.content_type = feed.mime_type
144 return feed.writeString('utf-8')
144 return feed.writeString('utf-8')
145
145
146 def _rss_feed(self, repos, public=True):
146 def _rss_feed(self, repos, public=True):
147 journal = self._get_journal_data(repos)
147 journal = self._get_journal_data(repos)
148 if public:
148 if public:
149 _link = url('public_journal_atom', qualified=True)
149 _link = url('public_journal_atom', qualified=True)
150 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
150 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
151 'rss feed')
151 'rss feed')
152 else:
152 else:
153 _link = url('journal_atom', qualified=True)
153 _link = url('journal_atom', qualified=True)
154 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
154 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
155
155
156 feed = Rss201rev2Feed(title=_desc,
156 feed = Rss201rev2Feed(title=_desc,
157 link=_link,
157 link=_link,
158 description=_desc,
158 description=_desc,
159 language=self.language,
159 language=self.language,
160 ttl=self.ttl)
160 ttl=self.ttl)
161
161
162 for entry in journal[:self.feed_nr]:
162 for entry in journal[:self.feed_nr]:
163 user = entry.user
163 user = entry.user
164 if user is None:
164 if user is None:
165 #fix deleted users
165 #fix deleted users
166 user = AttributeDict({'short_contact': entry.username,
166 user = AttributeDict({'short_contact': entry.username,
167 'email': '',
167 'email': '',
168 'full_contact': ''})
168 'full_contact': ''})
169 action, action_extra, ico = h.action_parser(entry, feed=True)
169 action, action_extra, ico = h.action_parser(entry, feed=True)
170 title = "%s - %s %s" % (user.short_contact, action(),
170 title = "%s - %s %s" % (user.short_contact, action(),
171 entry.repository.repo_name)
171 entry.repository.repo_name)
172 desc = action_extra()
172 desc = action_extra()
173 _url = None
173 _url = None
174 if entry.repository is not None:
174 if entry.repository is not None:
175 _url = url('changelog_home',
175 _url = url('changelog_home',
176 repo_name=entry.repository.repo_name,
176 repo_name=entry.repository.repo_name,
177 qualified=True)
177 qualified=True)
178
178
179 feed.add_item(title=title,
179 feed.add_item(title=title,
180 pubdate=entry.action_date,
180 pubdate=entry.action_date,
181 link=_url or url('', qualified=True),
181 link=_url or url('', qualified=True),
182 author_email=user.email,
182 author_email=user.email,
183 author_name=user.full_contact,
183 author_name=user.full_contact,
184 description=desc)
184 description=desc)
185
185
186 response.content_type = feed.mime_type
186 response.content_type = feed.mime_type
187 return feed.writeString('utf-8')
187 return feed.writeString('utf-8')
188
188
189 @LoginRequired()
189 @LoginRequired()
190 @NotAnonymous()
190 @NotAnonymous()
191 def index(self):
191 def index(self):
192 # Return a rendered template
192 # Return a rendered template
193 p = safe_int(request.GET.get('page', 1), 1)
193 p = safe_int(request.GET.get('page', 1), 1)
194 c.user = User.get(c.rhodecode_user.user_id)
194 c.user = User.get(c.rhodecode_user.user_id)
195 following = self.sa.query(UserFollowing)\
195 following = self.sa.query(UserFollowing)\
196 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
196 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
197 .options(joinedload(UserFollowing.follows_repository))\
197 .options(joinedload(UserFollowing.follows_repository))\
198 .all()
198 .all()
199
199
200 journal = self._get_journal_data(following)
200 journal = self._get_journal_data(following)
201
201
202 def url_generator(**kw):
202 def url_generator(**kw):
203 return url.current(filter=c.search_term, **kw)
203 return url.current(filter=c.search_term, **kw)
204
204
205 c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
205 c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
206 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
206 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
207
207
208 c.journal_data = render('journal/journal_data.mako')
208 c.journal_data = render('journal/journal_data.mako')
209 if request.is_xhr:
209 if request.is_xhr:
210 return c.journal_data
210 return c.journal_data
211
211
212 return render('journal/journal.mako')
212 return render('journal/journal.mako')
213
213
214 @LoginRequired(auth_token_access=True)
214 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
215 @NotAnonymous()
215 @NotAnonymous()
216 def journal_atom(self):
216 def journal_atom(self):
217 """
217 """
218 Produce an atom-1.0 feed via feedgenerator module
218 Produce an atom-1.0 feed via feedgenerator module
219 """
219 """
220 following = self.sa.query(UserFollowing)\
220 following = self.sa.query(UserFollowing)\
221 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
221 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
222 .options(joinedload(UserFollowing.follows_repository))\
222 .options(joinedload(UserFollowing.follows_repository))\
223 .all()
223 .all()
224 return self._atom_feed(following, public=False)
224 return self._atom_feed(following, public=False)
225
225
226 @LoginRequired(auth_token_access=True)
226 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
227 @NotAnonymous()
227 @NotAnonymous()
228 def journal_rss(self):
228 def journal_rss(self):
229 """
229 """
230 Produce an rss feed via feedgenerator module
230 Produce an rss feed via feedgenerator module
231 """
231 """
232 following = self.sa.query(UserFollowing)\
232 following = self.sa.query(UserFollowing)\
233 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
233 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
234 .options(joinedload(UserFollowing.follows_repository))\
234 .options(joinedload(UserFollowing.follows_repository))\
235 .all()
235 .all()
236 return self._rss_feed(following, public=False)
236 return self._rss_feed(following, public=False)
237
237
238 @CSRFRequired()
238 @CSRFRequired()
239 @LoginRequired()
239 @LoginRequired()
240 @NotAnonymous()
240 @NotAnonymous()
241 def toggle_following(self):
241 def toggle_following(self):
242 user_id = request.POST.get('follows_user_id')
242 user_id = request.POST.get('follows_user_id')
243 if user_id:
243 if user_id:
244 try:
244 try:
245 self.scm_model.toggle_following_user(
245 self.scm_model.toggle_following_user(
246 user_id, c.rhodecode_user.user_id)
246 user_id, c.rhodecode_user.user_id)
247 Session().commit()
247 Session().commit()
248 return 'ok'
248 return 'ok'
249 except Exception:
249 except Exception:
250 raise HTTPBadRequest()
250 raise HTTPBadRequest()
251
251
252 repo_id = request.POST.get('follows_repo_id')
252 repo_id = request.POST.get('follows_repo_id')
253 if repo_id:
253 if repo_id:
254 try:
254 try:
255 self.scm_model.toggle_following_repo(
255 self.scm_model.toggle_following_repo(
256 repo_id, c.rhodecode_user.user_id)
256 repo_id, c.rhodecode_user.user_id)
257 Session().commit()
257 Session().commit()
258 return 'ok'
258 return 'ok'
259 except Exception:
259 except Exception:
260 raise HTTPBadRequest()
260 raise HTTPBadRequest()
261
261
262
262
263 @LoginRequired()
263 @LoginRequired()
264 def public_journal(self):
264 def public_journal(self):
265 # Return a rendered template
265 # Return a rendered template
266 p = safe_int(request.GET.get('page', 1), 1)
266 p = safe_int(request.GET.get('page', 1), 1)
267
267
268 c.following = self.sa.query(UserFollowing)\
268 c.following = self.sa.query(UserFollowing)\
269 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
269 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
270 .options(joinedload(UserFollowing.follows_repository))\
270 .options(joinedload(UserFollowing.follows_repository))\
271 .all()
271 .all()
272
272
273 journal = self._get_journal_data(c.following)
273 journal = self._get_journal_data(c.following)
274
274
275 c.journal_pager = Page(journal, page=p, items_per_page=20)
275 c.journal_pager = Page(journal, page=p, items_per_page=20)
276
276
277 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
277 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
278
278
279 c.journal_data = render('journal/journal_data.mako')
279 c.journal_data = render('journal/journal_data.mako')
280 if request.is_xhr:
280 if request.is_xhr:
281 return c.journal_data
281 return c.journal_data
282 return render('journal/public_journal.mako')
282 return render('journal/public_journal.mako')
283
283
284 @LoginRequired(auth_token_access=True)
284 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
285 def public_journal_atom(self):
285 def public_journal_atom(self):
286 """
286 """
287 Produce an atom-1.0 feed via feedgenerator module
287 Produce an atom-1.0 feed via feedgenerator module
288 """
288 """
289 c.following = self.sa.query(UserFollowing)\
289 c.following = self.sa.query(UserFollowing)\
290 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
290 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
291 .options(joinedload(UserFollowing.follows_repository))\
291 .options(joinedload(UserFollowing.follows_repository))\
292 .all()
292 .all()
293
293
294 return self._atom_feed(c.following)
294 return self._atom_feed(c.following)
295
295
296 @LoginRequired(auth_token_access=True)
296 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
297 def public_journal_rss(self):
297 def public_journal_rss(self):
298 """
298 """
299 Produce an rss2 feed via feedgenerator module
299 Produce an rss2 feed via feedgenerator module
300 """
300 """
301 c.following = self.sa.query(UserFollowing)\
301 c.following = self.sa.query(UserFollowing)\
302 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
302 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
303 .options(joinedload(UserFollowing.follows_repository))\
303 .options(joinedload(UserFollowing.follows_repository))\
304 .all()
304 .all()
305
305
306 return self._rss_feed(c.following)
306 return self._rss_feed(c.following)
@@ -1,1907 +1,1909 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 authentication and permission libraries
22 authentication and permission libraries
23 """
23 """
24
24
25 import inspect
25 import inspect
26 import collections
26 import collections
27 import fnmatch
27 import fnmatch
28 import hashlib
28 import hashlib
29 import itertools
29 import itertools
30 import logging
30 import logging
31 import os
31 import os
32 import random
32 import random
33 import time
33 import time
34 import traceback
34 import traceback
35 from functools import wraps
35 from functools import wraps
36
36
37 import ipaddress
37 import ipaddress
38 from pyramid.httpexceptions import HTTPForbidden
38 from pyramid.httpexceptions import HTTPForbidden
39 from pylons import url, request
39 from pylons import url, request
40 from pylons.controllers.util import abort, redirect
40 from pylons.controllers.util import abort, redirect
41 from pylons.i18n.translation import _
41 from pylons.i18n.translation import _
42 from sqlalchemy import or_
42 from sqlalchemy import or_
43 from sqlalchemy.orm.exc import ObjectDeletedError
43 from sqlalchemy.orm.exc import ObjectDeletedError
44 from sqlalchemy.orm import joinedload
44 from sqlalchemy.orm import joinedload
45 from zope.cachedescriptors.property import Lazy as LazyProperty
45 from zope.cachedescriptors.property import Lazy as LazyProperty
46
46
47 import rhodecode
47 import rhodecode
48 from rhodecode.model import meta
48 from rhodecode.model import meta
49 from rhodecode.model.meta import Session
49 from rhodecode.model.meta import Session
50 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
51 from rhodecode.model.db import (
51 from rhodecode.model.db import (
52 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
52 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
53 UserIpMap, UserApiKeys, RepoGroup)
53 UserIpMap, UserApiKeys, RepoGroup)
54 from rhodecode.lib import caches
54 from rhodecode.lib import caches
55 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5
55 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5
56 from rhodecode.lib.utils import (
56 from rhodecode.lib.utils import (
57 get_repo_slug, get_repo_group_slug, get_user_group_slug)
57 get_repo_slug, get_repo_group_slug, get_user_group_slug)
58 from rhodecode.lib.caching_query import FromCache
58 from rhodecode.lib.caching_query import FromCache
59
59
60
60
61 if rhodecode.is_unix:
61 if rhodecode.is_unix:
62 import bcrypt
62 import bcrypt
63
63
64 log = logging.getLogger(__name__)
64 log = logging.getLogger(__name__)
65
65
66 csrf_token_key = "csrf_token"
66 csrf_token_key = "csrf_token"
67
67
68
68
69 class PasswordGenerator(object):
69 class PasswordGenerator(object):
70 """
70 """
71 This is a simple class for generating password from different sets of
71 This is a simple class for generating password from different sets of
72 characters
72 characters
73 usage::
73 usage::
74
74
75 passwd_gen = PasswordGenerator()
75 passwd_gen = PasswordGenerator()
76 #print 8-letter password containing only big and small letters
76 #print 8-letter password containing only big and small letters
77 of alphabet
77 of alphabet
78 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
78 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
79 """
79 """
80 ALPHABETS_NUM = r'''1234567890'''
80 ALPHABETS_NUM = r'''1234567890'''
81 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
81 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
82 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
82 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
83 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
83 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
84 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
84 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
85 + ALPHABETS_NUM + ALPHABETS_SPECIAL
85 + ALPHABETS_NUM + ALPHABETS_SPECIAL
86 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
86 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
87 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
87 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
88 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
88 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
89 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
89 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
90
90
91 def __init__(self, passwd=''):
91 def __init__(self, passwd=''):
92 self.passwd = passwd
92 self.passwd = passwd
93
93
94 def gen_password(self, length, type_=None):
94 def gen_password(self, length, type_=None):
95 if type_ is None:
95 if type_ is None:
96 type_ = self.ALPHABETS_FULL
96 type_ = self.ALPHABETS_FULL
97 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
97 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
98 return self.passwd
98 return self.passwd
99
99
100
100
101 class _RhodeCodeCryptoBase(object):
101 class _RhodeCodeCryptoBase(object):
102 ENC_PREF = None
102
103
103 def hash_create(self, str_):
104 def hash_create(self, str_):
104 """
105 """
105 hash the string using
106 hash the string using
106
107
107 :param str_: password to hash
108 :param str_: password to hash
108 """
109 """
109 raise NotImplementedError
110 raise NotImplementedError
110
111
111 def hash_check_with_upgrade(self, password, hashed):
112 def hash_check_with_upgrade(self, password, hashed):
112 """
113 """
113 Returns tuple in which first element is boolean that states that
114 Returns tuple in which first element is boolean that states that
114 given password matches it's hashed version, and the second is new hash
115 given password matches it's hashed version, and the second is new hash
115 of the password, in case this password should be migrated to new
116 of the password, in case this password should be migrated to new
116 cipher.
117 cipher.
117 """
118 """
118 checked_hash = self.hash_check(password, hashed)
119 checked_hash = self.hash_check(password, hashed)
119 return checked_hash, None
120 return checked_hash, None
120
121
121 def hash_check(self, password, hashed):
122 def hash_check(self, password, hashed):
122 """
123 """
123 Checks matching password with it's hashed value.
124 Checks matching password with it's hashed value.
124
125
125 :param password: password
126 :param password: password
126 :param hashed: password in hashed form
127 :param hashed: password in hashed form
127 """
128 """
128 raise NotImplementedError
129 raise NotImplementedError
129
130
130 def _assert_bytes(self, value):
131 def _assert_bytes(self, value):
131 """
132 """
132 Passing in an `unicode` object can lead to hard to detect issues
133 Passing in an `unicode` object can lead to hard to detect issues
133 if passwords contain non-ascii characters. Doing a type check
134 if passwords contain non-ascii characters. Doing a type check
134 during runtime, so that such mistakes are detected early on.
135 during runtime, so that such mistakes are detected early on.
135 """
136 """
136 if not isinstance(value, str):
137 if not isinstance(value, str):
137 raise TypeError(
138 raise TypeError(
138 "Bytestring required as input, got %r." % (value, ))
139 "Bytestring required as input, got %r." % (value, ))
139
140
140
141
141 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
142 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
143 ENC_PREF = '$2a$10'
142
144
143 def hash_create(self, str_):
145 def hash_create(self, str_):
144 self._assert_bytes(str_)
146 self._assert_bytes(str_)
145 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
147 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
146
148
147 def hash_check_with_upgrade(self, password, hashed):
149 def hash_check_with_upgrade(self, password, hashed):
148 """
150 """
149 Returns tuple in which first element is boolean that states that
151 Returns tuple in which first element is boolean that states that
150 given password matches it's hashed version, and the second is new hash
152 given password matches it's hashed version, and the second is new hash
151 of the password, in case this password should be migrated to new
153 of the password, in case this password should be migrated to new
152 cipher.
154 cipher.
153
155
154 This implements special upgrade logic which works like that:
156 This implements special upgrade logic which works like that:
155 - check if the given password == bcrypted hash, if yes then we
157 - check if the given password == bcrypted hash, if yes then we
156 properly used password and it was already in bcrypt. Proceed
158 properly used password and it was already in bcrypt. Proceed
157 without any changes
159 without any changes
158 - if bcrypt hash check is not working try with sha256. If hash compare
160 - if bcrypt hash check is not working try with sha256. If hash compare
159 is ok, it means we using correct but old hashed password. indicate
161 is ok, it means we using correct but old hashed password. indicate
160 hash change and proceed
162 hash change and proceed
161 """
163 """
162
164
163 new_hash = None
165 new_hash = None
164
166
165 # regular pw check
167 # regular pw check
166 password_match_bcrypt = self.hash_check(password, hashed)
168 password_match_bcrypt = self.hash_check(password, hashed)
167
169
168 # now we want to know if the password was maybe from sha256
170 # now we want to know if the password was maybe from sha256
169 # basically calling _RhodeCodeCryptoSha256().hash_check()
171 # basically calling _RhodeCodeCryptoSha256().hash_check()
170 if not password_match_bcrypt:
172 if not password_match_bcrypt:
171 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
173 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
172 new_hash = self.hash_create(password) # make new bcrypt hash
174 new_hash = self.hash_create(password) # make new bcrypt hash
173 password_match_bcrypt = True
175 password_match_bcrypt = True
174
176
175 return password_match_bcrypt, new_hash
177 return password_match_bcrypt, new_hash
176
178
177 def hash_check(self, password, hashed):
179 def hash_check(self, password, hashed):
178 """
180 """
179 Checks matching password with it's hashed value.
181 Checks matching password with it's hashed value.
180
182
181 :param password: password
183 :param password: password
182 :param hashed: password in hashed form
184 :param hashed: password in hashed form
183 """
185 """
184 self._assert_bytes(password)
186 self._assert_bytes(password)
185 try:
187 try:
186 return bcrypt.hashpw(password, hashed) == hashed
188 return bcrypt.hashpw(password, hashed) == hashed
187 except ValueError as e:
189 except ValueError as e:
188 # we're having a invalid salt here probably, we should not crash
190 # we're having a invalid salt here probably, we should not crash
189 # just return with False as it would be a wrong password.
191 # just return with False as it would be a wrong password.
190 log.debug('Failed to check password hash using bcrypt %s',
192 log.debug('Failed to check password hash using bcrypt %s',
191 safe_str(e))
193 safe_str(e))
192
194
193 return False
195 return False
194
196
195
197
196 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
198 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
199 ENC_PREF = '_'
197
200
198 def hash_create(self, str_):
201 def hash_create(self, str_):
199 self._assert_bytes(str_)
202 self._assert_bytes(str_)
200 return hashlib.sha256(str_).hexdigest()
203 return hashlib.sha256(str_).hexdigest()
201
204
202 def hash_check(self, password, hashed):
205 def hash_check(self, password, hashed):
203 """
206 """
204 Checks matching password with it's hashed value.
207 Checks matching password with it's hashed value.
205
208
206 :param password: password
209 :param password: password
207 :param hashed: password in hashed form
210 :param hashed: password in hashed form
208 """
211 """
209 self._assert_bytes(password)
212 self._assert_bytes(password)
210 return hashlib.sha256(password).hexdigest() == hashed
213 return hashlib.sha256(password).hexdigest() == hashed
211
214
212
215
213 class _RhodeCodeCryptoMd5(_RhodeCodeCryptoBase):
216 class _RhodeCodeCryptoMd5(_RhodeCodeCryptoBase):
217 ENC_PREF = '_'
214
218
215 def hash_create(self, str_):
219 def hash_create(self, str_):
216 self._assert_bytes(str_)
220 self._assert_bytes(str_)
217 return hashlib.md5(str_).hexdigest()
221 return hashlib.md5(str_).hexdigest()
218
222
219 def hash_check(self, password, hashed):
223 def hash_check(self, password, hashed):
220 """
224 """
221 Checks matching password with it's hashed value.
225 Checks matching password with it's hashed value.
222
226
223 :param password: password
227 :param password: password
224 :param hashed: password in hashed form
228 :param hashed: password in hashed form
225 """
229 """
226 self._assert_bytes(password)
230 self._assert_bytes(password)
227 return hashlib.md5(password).hexdigest() == hashed
231 return hashlib.md5(password).hexdigest() == hashed
228
232
229
233
230 def crypto_backend():
234 def crypto_backend():
231 """
235 """
232 Return the matching crypto backend.
236 Return the matching crypto backend.
233
237
234 Selection is based on if we run tests or not, we pick md5 backend to run
238 Selection is based on if we run tests or not, we pick md5 backend to run
235 tests faster since BCRYPT is expensive to calculate
239 tests faster since BCRYPT is expensive to calculate
236 """
240 """
237 if rhodecode.is_test:
241 if rhodecode.is_test:
238 RhodeCodeCrypto = _RhodeCodeCryptoMd5()
242 RhodeCodeCrypto = _RhodeCodeCryptoMd5()
239 else:
243 else:
240 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
244 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
241
245
242 return RhodeCodeCrypto
246 return RhodeCodeCrypto
243
247
244
248
245 def get_crypt_password(password):
249 def get_crypt_password(password):
246 """
250 """
247 Create the hash of `password` with the active crypto backend.
251 Create the hash of `password` with the active crypto backend.
248
252
249 :param password: The cleartext password.
253 :param password: The cleartext password.
250 :type password: unicode
254 :type password: unicode
251 """
255 """
252 password = safe_str(password)
256 password = safe_str(password)
253 return crypto_backend().hash_create(password)
257 return crypto_backend().hash_create(password)
254
258
255
259
256 def check_password(password, hashed):
260 def check_password(password, hashed):
257 """
261 """
258 Check if the value in `password` matches the hash in `hashed`.
262 Check if the value in `password` matches the hash in `hashed`.
259
263
260 :param password: The cleartext password.
264 :param password: The cleartext password.
261 :type password: unicode
265 :type password: unicode
262
266
263 :param hashed: The expected hashed version of the password.
267 :param hashed: The expected hashed version of the password.
264 :type hashed: The hash has to be passed in in text representation.
268 :type hashed: The hash has to be passed in in text representation.
265 """
269 """
266 password = safe_str(password)
270 password = safe_str(password)
267 return crypto_backend().hash_check(password, hashed)
271 return crypto_backend().hash_check(password, hashed)
268
272
269
273
270 def generate_auth_token(data, salt=None):
274 def generate_auth_token(data, salt=None):
271 """
275 """
272 Generates API KEY from given string
276 Generates API KEY from given string
273 """
277 """
274
278
275 if salt is None:
279 if salt is None:
276 salt = os.urandom(16)
280 salt = os.urandom(16)
277 return hashlib.sha1(safe_str(data) + salt).hexdigest()
281 return hashlib.sha1(safe_str(data) + salt).hexdigest()
278
282
279
283
280 class CookieStoreWrapper(object):
284 class CookieStoreWrapper(object):
281
285
282 def __init__(self, cookie_store):
286 def __init__(self, cookie_store):
283 self.cookie_store = cookie_store
287 self.cookie_store = cookie_store
284
288
285 def __repr__(self):
289 def __repr__(self):
286 return 'CookieStore<%s>' % (self.cookie_store)
290 return 'CookieStore<%s>' % (self.cookie_store)
287
291
288 def get(self, key, other=None):
292 def get(self, key, other=None):
289 if isinstance(self.cookie_store, dict):
293 if isinstance(self.cookie_store, dict):
290 return self.cookie_store.get(key, other)
294 return self.cookie_store.get(key, other)
291 elif isinstance(self.cookie_store, AuthUser):
295 elif isinstance(self.cookie_store, AuthUser):
292 return self.cookie_store.__dict__.get(key, other)
296 return self.cookie_store.__dict__.get(key, other)
293
297
294
298
295 def _cached_perms_data(user_id, scope, user_is_admin,
299 def _cached_perms_data(user_id, scope, user_is_admin,
296 user_inherit_default_permissions, explicit, algo):
300 user_inherit_default_permissions, explicit, algo):
297
301
298 permissions = PermissionCalculator(
302 permissions = PermissionCalculator(
299 user_id, scope, user_is_admin, user_inherit_default_permissions,
303 user_id, scope, user_is_admin, user_inherit_default_permissions,
300 explicit, algo)
304 explicit, algo)
301 return permissions.calculate()
305 return permissions.calculate()
302
306
303 class PermOrigin:
307 class PermOrigin:
304 ADMIN = 'superadmin'
308 ADMIN = 'superadmin'
305
309
306 REPO_USER = 'user:%s'
310 REPO_USER = 'user:%s'
307 REPO_USERGROUP = 'usergroup:%s'
311 REPO_USERGROUP = 'usergroup:%s'
308 REPO_OWNER = 'repo.owner'
312 REPO_OWNER = 'repo.owner'
309 REPO_DEFAULT = 'repo.default'
313 REPO_DEFAULT = 'repo.default'
310 REPO_PRIVATE = 'repo.private'
314 REPO_PRIVATE = 'repo.private'
311
315
312 REPOGROUP_USER = 'user:%s'
316 REPOGROUP_USER = 'user:%s'
313 REPOGROUP_USERGROUP = 'usergroup:%s'
317 REPOGROUP_USERGROUP = 'usergroup:%s'
314 REPOGROUP_OWNER = 'group.owner'
318 REPOGROUP_OWNER = 'group.owner'
315 REPOGROUP_DEFAULT = 'group.default'
319 REPOGROUP_DEFAULT = 'group.default'
316
320
317 USERGROUP_USER = 'user:%s'
321 USERGROUP_USER = 'user:%s'
318 USERGROUP_USERGROUP = 'usergroup:%s'
322 USERGROUP_USERGROUP = 'usergroup:%s'
319 USERGROUP_OWNER = 'usergroup.owner'
323 USERGROUP_OWNER = 'usergroup.owner'
320 USERGROUP_DEFAULT = 'usergroup.default'
324 USERGROUP_DEFAULT = 'usergroup.default'
321
325
322
326
323 class PermOriginDict(dict):
327 class PermOriginDict(dict):
324 """
328 """
325 A special dict used for tracking permissions along with their origins.
329 A special dict used for tracking permissions along with their origins.
326
330
327 `__setitem__` has been overridden to expect a tuple(perm, origin)
331 `__setitem__` has been overridden to expect a tuple(perm, origin)
328 `__getitem__` will return only the perm
332 `__getitem__` will return only the perm
329 `.perm_origin_stack` will return the stack of (perm, origin) set per key
333 `.perm_origin_stack` will return the stack of (perm, origin) set per key
330
334
331 >>> perms = PermOriginDict()
335 >>> perms = PermOriginDict()
332 >>> perms['resource'] = 'read', 'default'
336 >>> perms['resource'] = 'read', 'default'
333 >>> perms['resource']
337 >>> perms['resource']
334 'read'
338 'read'
335 >>> perms['resource'] = 'write', 'admin'
339 >>> perms['resource'] = 'write', 'admin'
336 >>> perms['resource']
340 >>> perms['resource']
337 'write'
341 'write'
338 >>> perms.perm_origin_stack
342 >>> perms.perm_origin_stack
339 {'resource': [('read', 'default'), ('write', 'admin')]}
343 {'resource': [('read', 'default'), ('write', 'admin')]}
340 """
344 """
341
345
342
346
343 def __init__(self, *args, **kw):
347 def __init__(self, *args, **kw):
344 dict.__init__(self, *args, **kw)
348 dict.__init__(self, *args, **kw)
345 self.perm_origin_stack = {}
349 self.perm_origin_stack = {}
346
350
347 def __setitem__(self, key, (perm, origin)):
351 def __setitem__(self, key, (perm, origin)):
348 self.perm_origin_stack.setdefault(key, []).append((perm, origin))
352 self.perm_origin_stack.setdefault(key, []).append((perm, origin))
349 dict.__setitem__(self, key, perm)
353 dict.__setitem__(self, key, perm)
350
354
351
355
352 class PermissionCalculator(object):
356 class PermissionCalculator(object):
353
357
354 def __init__(
358 def __init__(
355 self, user_id, scope, user_is_admin,
359 self, user_id, scope, user_is_admin,
356 user_inherit_default_permissions, explicit, algo):
360 user_inherit_default_permissions, explicit, algo):
357 self.user_id = user_id
361 self.user_id = user_id
358 self.user_is_admin = user_is_admin
362 self.user_is_admin = user_is_admin
359 self.inherit_default_permissions = user_inherit_default_permissions
363 self.inherit_default_permissions = user_inherit_default_permissions
360 self.explicit = explicit
364 self.explicit = explicit
361 self.algo = algo
365 self.algo = algo
362
366
363 scope = scope or {}
367 scope = scope or {}
364 self.scope_repo_id = scope.get('repo_id')
368 self.scope_repo_id = scope.get('repo_id')
365 self.scope_repo_group_id = scope.get('repo_group_id')
369 self.scope_repo_group_id = scope.get('repo_group_id')
366 self.scope_user_group_id = scope.get('user_group_id')
370 self.scope_user_group_id = scope.get('user_group_id')
367
371
368 self.default_user_id = User.get_default_user(cache=True).user_id
372 self.default_user_id = User.get_default_user(cache=True).user_id
369
373
370 self.permissions_repositories = PermOriginDict()
374 self.permissions_repositories = PermOriginDict()
371 self.permissions_repository_groups = PermOriginDict()
375 self.permissions_repository_groups = PermOriginDict()
372 self.permissions_user_groups = PermOriginDict()
376 self.permissions_user_groups = PermOriginDict()
373 self.permissions_global = set()
377 self.permissions_global = set()
374
378
375 self.default_repo_perms = Permission.get_default_repo_perms(
379 self.default_repo_perms = Permission.get_default_repo_perms(
376 self.default_user_id, self.scope_repo_id)
380 self.default_user_id, self.scope_repo_id)
377 self.default_repo_groups_perms = Permission.get_default_group_perms(
381 self.default_repo_groups_perms = Permission.get_default_group_perms(
378 self.default_user_id, self.scope_repo_group_id)
382 self.default_user_id, self.scope_repo_group_id)
379 self.default_user_group_perms = \
383 self.default_user_group_perms = \
380 Permission.get_default_user_group_perms(
384 Permission.get_default_user_group_perms(
381 self.default_user_id, self.scope_user_group_id)
385 self.default_user_id, self.scope_user_group_id)
382
386
383 def calculate(self):
387 def calculate(self):
384 if self.user_is_admin:
388 if self.user_is_admin:
385 return self._admin_permissions()
389 return self._admin_permissions()
386
390
387 self._calculate_global_default_permissions()
391 self._calculate_global_default_permissions()
388 self._calculate_global_permissions()
392 self._calculate_global_permissions()
389 self._calculate_default_permissions()
393 self._calculate_default_permissions()
390 self._calculate_repository_permissions()
394 self._calculate_repository_permissions()
391 self._calculate_repository_group_permissions()
395 self._calculate_repository_group_permissions()
392 self._calculate_user_group_permissions()
396 self._calculate_user_group_permissions()
393 return self._permission_structure()
397 return self._permission_structure()
394
398
395 def _admin_permissions(self):
399 def _admin_permissions(self):
396 """
400 """
397 admin user have all default rights for repositories
401 admin user have all default rights for repositories
398 and groups set to admin
402 and groups set to admin
399 """
403 """
400 self.permissions_global.add('hg.admin')
404 self.permissions_global.add('hg.admin')
401 self.permissions_global.add('hg.create.write_on_repogroup.true')
405 self.permissions_global.add('hg.create.write_on_repogroup.true')
402
406
403 # repositories
407 # repositories
404 for perm in self.default_repo_perms:
408 for perm in self.default_repo_perms:
405 r_k = perm.UserRepoToPerm.repository.repo_name
409 r_k = perm.UserRepoToPerm.repository.repo_name
406 p = 'repository.admin'
410 p = 'repository.admin'
407 self.permissions_repositories[r_k] = p, PermOrigin.ADMIN
411 self.permissions_repositories[r_k] = p, PermOrigin.ADMIN
408
412
409 # repository groups
413 # repository groups
410 for perm in self.default_repo_groups_perms:
414 for perm in self.default_repo_groups_perms:
411 rg_k = perm.UserRepoGroupToPerm.group.group_name
415 rg_k = perm.UserRepoGroupToPerm.group.group_name
412 p = 'group.admin'
416 p = 'group.admin'
413 self.permissions_repository_groups[rg_k] = p, PermOrigin.ADMIN
417 self.permissions_repository_groups[rg_k] = p, PermOrigin.ADMIN
414
418
415 # user groups
419 # user groups
416 for perm in self.default_user_group_perms:
420 for perm in self.default_user_group_perms:
417 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
421 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
418 p = 'usergroup.admin'
422 p = 'usergroup.admin'
419 self.permissions_user_groups[u_k] = p, PermOrigin.ADMIN
423 self.permissions_user_groups[u_k] = p, PermOrigin.ADMIN
420
424
421 return self._permission_structure()
425 return self._permission_structure()
422
426
423 def _calculate_global_default_permissions(self):
427 def _calculate_global_default_permissions(self):
424 """
428 """
425 global permissions taken from the default user
429 global permissions taken from the default user
426 """
430 """
427 default_global_perms = UserToPerm.query()\
431 default_global_perms = UserToPerm.query()\
428 .filter(UserToPerm.user_id == self.default_user_id)\
432 .filter(UserToPerm.user_id == self.default_user_id)\
429 .options(joinedload(UserToPerm.permission))
433 .options(joinedload(UserToPerm.permission))
430
434
431 for perm in default_global_perms:
435 for perm in default_global_perms:
432 self.permissions_global.add(perm.permission.permission_name)
436 self.permissions_global.add(perm.permission.permission_name)
433
437
434 def _calculate_global_permissions(self):
438 def _calculate_global_permissions(self):
435 """
439 """
436 Set global system permissions with user permissions or permissions
440 Set global system permissions with user permissions or permissions
437 taken from the user groups of the current user.
441 taken from the user groups of the current user.
438
442
439 The permissions include repo creating, repo group creating, forking
443 The permissions include repo creating, repo group creating, forking
440 etc.
444 etc.
441 """
445 """
442
446
443 # now we read the defined permissions and overwrite what we have set
447 # now we read the defined permissions and overwrite what we have set
444 # before those can be configured from groups or users explicitly.
448 # before those can be configured from groups or users explicitly.
445
449
446 # TODO: johbo: This seems to be out of sync, find out the reason
450 # TODO: johbo: This seems to be out of sync, find out the reason
447 # for the comment below and update it.
451 # for the comment below and update it.
448
452
449 # In case we want to extend this list we should be always in sync with
453 # In case we want to extend this list we should be always in sync with
450 # User.DEFAULT_USER_PERMISSIONS definitions
454 # User.DEFAULT_USER_PERMISSIONS definitions
451 _configurable = frozenset([
455 _configurable = frozenset([
452 'hg.fork.none', 'hg.fork.repository',
456 'hg.fork.none', 'hg.fork.repository',
453 'hg.create.none', 'hg.create.repository',
457 'hg.create.none', 'hg.create.repository',
454 'hg.usergroup.create.false', 'hg.usergroup.create.true',
458 'hg.usergroup.create.false', 'hg.usergroup.create.true',
455 'hg.repogroup.create.false', 'hg.repogroup.create.true',
459 'hg.repogroup.create.false', 'hg.repogroup.create.true',
456 'hg.create.write_on_repogroup.false',
460 'hg.create.write_on_repogroup.false',
457 'hg.create.write_on_repogroup.true',
461 'hg.create.write_on_repogroup.true',
458 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
462 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
459 ])
463 ])
460
464
461 # USER GROUPS comes first user group global permissions
465 # USER GROUPS comes first user group global permissions
462 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
466 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
463 .options(joinedload(UserGroupToPerm.permission))\
467 .options(joinedload(UserGroupToPerm.permission))\
464 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
468 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
465 UserGroupMember.users_group_id))\
469 UserGroupMember.users_group_id))\
466 .filter(UserGroupMember.user_id == self.user_id)\
470 .filter(UserGroupMember.user_id == self.user_id)\
467 .order_by(UserGroupToPerm.users_group_id)\
471 .order_by(UserGroupToPerm.users_group_id)\
468 .all()
472 .all()
469
473
470 # need to group here by groups since user can be in more than
474 # need to group here by groups since user can be in more than
471 # one group, so we get all groups
475 # one group, so we get all groups
472 _explicit_grouped_perms = [
476 _explicit_grouped_perms = [
473 [x, list(y)] for x, y in
477 [x, list(y)] for x, y in
474 itertools.groupby(user_perms_from_users_groups,
478 itertools.groupby(user_perms_from_users_groups,
475 lambda _x: _x.users_group)]
479 lambda _x: _x.users_group)]
476
480
477 for gr, perms in _explicit_grouped_perms:
481 for gr, perms in _explicit_grouped_perms:
478 # since user can be in multiple groups iterate over them and
482 # since user can be in multiple groups iterate over them and
479 # select the lowest permissions first (more explicit)
483 # select the lowest permissions first (more explicit)
480 # TODO: marcink: do this^^
484 # TODO: marcink: do this^^
481
485
482 # group doesn't inherit default permissions so we actually set them
486 # group doesn't inherit default permissions so we actually set them
483 if not gr.inherit_default_permissions:
487 if not gr.inherit_default_permissions:
484 # NEED TO IGNORE all previously set configurable permissions
488 # NEED TO IGNORE all previously set configurable permissions
485 # and replace them with explicitly set from this user
489 # and replace them with explicitly set from this user
486 # group permissions
490 # group permissions
487 self.permissions_global = self.permissions_global.difference(
491 self.permissions_global = self.permissions_global.difference(
488 _configurable)
492 _configurable)
489 for perm in perms:
493 for perm in perms:
490 self.permissions_global.add(perm.permission.permission_name)
494 self.permissions_global.add(perm.permission.permission_name)
491
495
492 # user explicit global permissions
496 # user explicit global permissions
493 user_perms = Session().query(UserToPerm)\
497 user_perms = Session().query(UserToPerm)\
494 .options(joinedload(UserToPerm.permission))\
498 .options(joinedload(UserToPerm.permission))\
495 .filter(UserToPerm.user_id == self.user_id).all()
499 .filter(UserToPerm.user_id == self.user_id).all()
496
500
497 if not self.inherit_default_permissions:
501 if not self.inherit_default_permissions:
498 # NEED TO IGNORE all configurable permissions and
502 # NEED TO IGNORE all configurable permissions and
499 # replace them with explicitly set from this user permissions
503 # replace them with explicitly set from this user permissions
500 self.permissions_global = self.permissions_global.difference(
504 self.permissions_global = self.permissions_global.difference(
501 _configurable)
505 _configurable)
502 for perm in user_perms:
506 for perm in user_perms:
503 self.permissions_global.add(perm.permission.permission_name)
507 self.permissions_global.add(perm.permission.permission_name)
504
508
505 def _calculate_default_permissions(self):
509 def _calculate_default_permissions(self):
506 """
510 """
507 Set default user permissions for repositories, repository groups
511 Set default user permissions for repositories, repository groups
508 taken from the default user.
512 taken from the default user.
509
513
510 Calculate inheritance of object permissions based on what we have now
514 Calculate inheritance of object permissions based on what we have now
511 in GLOBAL permissions. We check if .false is in GLOBAL since this is
515 in GLOBAL permissions. We check if .false is in GLOBAL since this is
512 explicitly set. Inherit is the opposite of .false being there.
516 explicitly set. Inherit is the opposite of .false being there.
513
517
514 .. note::
518 .. note::
515
519
516 the syntax is little bit odd but what we need to check here is
520 the syntax is little bit odd but what we need to check here is
517 the opposite of .false permission being in the list so even for
521 the opposite of .false permission being in the list so even for
518 inconsistent state when both .true/.false is there
522 inconsistent state when both .true/.false is there
519 .false is more important
523 .false is more important
520
524
521 """
525 """
522 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
526 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
523 in self.permissions_global)
527 in self.permissions_global)
524
528
525 # defaults for repositories, taken from `default` user permissions
529 # defaults for repositories, taken from `default` user permissions
526 # on given repo
530 # on given repo
527 for perm in self.default_repo_perms:
531 for perm in self.default_repo_perms:
528 r_k = perm.UserRepoToPerm.repository.repo_name
532 r_k = perm.UserRepoToPerm.repository.repo_name
529 o = PermOrigin.REPO_DEFAULT
533 o = PermOrigin.REPO_DEFAULT
530 if perm.Repository.private and not (
534 if perm.Repository.private and not (
531 perm.Repository.user_id == self.user_id):
535 perm.Repository.user_id == self.user_id):
532 # disable defaults for private repos,
536 # disable defaults for private repos,
533 p = 'repository.none'
537 p = 'repository.none'
534 o = PermOrigin.REPO_PRIVATE
538 o = PermOrigin.REPO_PRIVATE
535 elif perm.Repository.user_id == self.user_id:
539 elif perm.Repository.user_id == self.user_id:
536 # set admin if owner
540 # set admin if owner
537 p = 'repository.admin'
541 p = 'repository.admin'
538 o = PermOrigin.REPO_OWNER
542 o = PermOrigin.REPO_OWNER
539 else:
543 else:
540 p = perm.Permission.permission_name
544 p = perm.Permission.permission_name
541 # if we decide this user isn't inheriting permissions from
545 # if we decide this user isn't inheriting permissions from
542 # default user we set him to .none so only explicit
546 # default user we set him to .none so only explicit
543 # permissions work
547 # permissions work
544 if not user_inherit_object_permissions:
548 if not user_inherit_object_permissions:
545 p = 'repository.none'
549 p = 'repository.none'
546 self.permissions_repositories[r_k] = p, o
550 self.permissions_repositories[r_k] = p, o
547
551
548 # defaults for repository groups taken from `default` user permission
552 # defaults for repository groups taken from `default` user permission
549 # on given group
553 # on given group
550 for perm in self.default_repo_groups_perms:
554 for perm in self.default_repo_groups_perms:
551 rg_k = perm.UserRepoGroupToPerm.group.group_name
555 rg_k = perm.UserRepoGroupToPerm.group.group_name
552 o = PermOrigin.REPOGROUP_DEFAULT
556 o = PermOrigin.REPOGROUP_DEFAULT
553 if perm.RepoGroup.user_id == self.user_id:
557 if perm.RepoGroup.user_id == self.user_id:
554 # set admin if owner
558 # set admin if owner
555 p = 'group.admin'
559 p = 'group.admin'
556 o = PermOrigin.REPOGROUP_OWNER
560 o = PermOrigin.REPOGROUP_OWNER
557 else:
561 else:
558 p = perm.Permission.permission_name
562 p = perm.Permission.permission_name
559
563
560 # if we decide this user isn't inheriting permissions from default
564 # if we decide this user isn't inheriting permissions from default
561 # user we set him to .none so only explicit permissions work
565 # user we set him to .none so only explicit permissions work
562 if not user_inherit_object_permissions:
566 if not user_inherit_object_permissions:
563 p = 'group.none'
567 p = 'group.none'
564 self.permissions_repository_groups[rg_k] = p, o
568 self.permissions_repository_groups[rg_k] = p, o
565
569
566 # defaults for user groups taken from `default` user permission
570 # defaults for user groups taken from `default` user permission
567 # on given user group
571 # on given user group
568 for perm in self.default_user_group_perms:
572 for perm in self.default_user_group_perms:
569 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
573 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
570 p = perm.Permission.permission_name
574 p = perm.Permission.permission_name
571 o = PermOrigin.USERGROUP_DEFAULT
575 o = PermOrigin.USERGROUP_DEFAULT
572 # if we decide this user isn't inheriting permissions from default
576 # if we decide this user isn't inheriting permissions from default
573 # user we set him to .none so only explicit permissions work
577 # user we set him to .none so only explicit permissions work
574 if not user_inherit_object_permissions:
578 if not user_inherit_object_permissions:
575 p = 'usergroup.none'
579 p = 'usergroup.none'
576 self.permissions_user_groups[u_k] = p, o
580 self.permissions_user_groups[u_k] = p, o
577
581
578 def _calculate_repository_permissions(self):
582 def _calculate_repository_permissions(self):
579 """
583 """
580 Repository permissions for the current user.
584 Repository permissions for the current user.
581
585
582 Check if the user is part of user groups for this repository and
586 Check if the user is part of user groups for this repository and
583 fill in the permission from it. `_choose_permission` decides of which
587 fill in the permission from it. `_choose_permission` decides of which
584 permission should be selected based on selected method.
588 permission should be selected based on selected method.
585 """
589 """
586
590
587 # user group for repositories permissions
591 # user group for repositories permissions
588 user_repo_perms_from_user_group = Permission\
592 user_repo_perms_from_user_group = Permission\
589 .get_default_repo_perms_from_user_group(
593 .get_default_repo_perms_from_user_group(
590 self.user_id, self.scope_repo_id)
594 self.user_id, self.scope_repo_id)
591
595
592 multiple_counter = collections.defaultdict(int)
596 multiple_counter = collections.defaultdict(int)
593 for perm in user_repo_perms_from_user_group:
597 for perm in user_repo_perms_from_user_group:
594 r_k = perm.UserGroupRepoToPerm.repository.repo_name
598 r_k = perm.UserGroupRepoToPerm.repository.repo_name
595 ug_k = perm.UserGroupRepoToPerm.users_group.users_group_name
599 ug_k = perm.UserGroupRepoToPerm.users_group.users_group_name
596 multiple_counter[r_k] += 1
600 multiple_counter[r_k] += 1
597 p = perm.Permission.permission_name
601 p = perm.Permission.permission_name
598 o = PermOrigin.REPO_USERGROUP % ug_k
602 o = PermOrigin.REPO_USERGROUP % ug_k
599
603
600 if perm.Repository.user_id == self.user_id:
604 if perm.Repository.user_id == self.user_id:
601 # set admin if owner
605 # set admin if owner
602 p = 'repository.admin'
606 p = 'repository.admin'
603 o = PermOrigin.REPO_OWNER
607 o = PermOrigin.REPO_OWNER
604 else:
608 else:
605 if multiple_counter[r_k] > 1:
609 if multiple_counter[r_k] > 1:
606 cur_perm = self.permissions_repositories[r_k]
610 cur_perm = self.permissions_repositories[r_k]
607 p = self._choose_permission(p, cur_perm)
611 p = self._choose_permission(p, cur_perm)
608 self.permissions_repositories[r_k] = p, o
612 self.permissions_repositories[r_k] = p, o
609
613
610 # user explicit permissions for repositories, overrides any specified
614 # user explicit permissions for repositories, overrides any specified
611 # by the group permission
615 # by the group permission
612 user_repo_perms = Permission.get_default_repo_perms(
616 user_repo_perms = Permission.get_default_repo_perms(
613 self.user_id, self.scope_repo_id)
617 self.user_id, self.scope_repo_id)
614 for perm in user_repo_perms:
618 for perm in user_repo_perms:
615 r_k = perm.UserRepoToPerm.repository.repo_name
619 r_k = perm.UserRepoToPerm.repository.repo_name
616 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
620 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
617 # set admin if owner
621 # set admin if owner
618 if perm.Repository.user_id == self.user_id:
622 if perm.Repository.user_id == self.user_id:
619 p = 'repository.admin'
623 p = 'repository.admin'
620 o = PermOrigin.REPO_OWNER
624 o = PermOrigin.REPO_OWNER
621 else:
625 else:
622 p = perm.Permission.permission_name
626 p = perm.Permission.permission_name
623 if not self.explicit:
627 if not self.explicit:
624 cur_perm = self.permissions_repositories.get(
628 cur_perm = self.permissions_repositories.get(
625 r_k, 'repository.none')
629 r_k, 'repository.none')
626 p = self._choose_permission(p, cur_perm)
630 p = self._choose_permission(p, cur_perm)
627 self.permissions_repositories[r_k] = p, o
631 self.permissions_repositories[r_k] = p, o
628
632
629 def _calculate_repository_group_permissions(self):
633 def _calculate_repository_group_permissions(self):
630 """
634 """
631 Repository group permissions for the current user.
635 Repository group permissions for the current user.
632
636
633 Check if the user is part of user groups for repository groups and
637 Check if the user is part of user groups for repository groups and
634 fill in the permissions from it. `_choose_permmission` decides of which
638 fill in the permissions from it. `_choose_permmission` decides of which
635 permission should be selected based on selected method.
639 permission should be selected based on selected method.
636 """
640 """
637 # user group for repo groups permissions
641 # user group for repo groups permissions
638 user_repo_group_perms_from_user_group = Permission\
642 user_repo_group_perms_from_user_group = Permission\
639 .get_default_group_perms_from_user_group(
643 .get_default_group_perms_from_user_group(
640 self.user_id, self.scope_repo_group_id)
644 self.user_id, self.scope_repo_group_id)
641
645
642 multiple_counter = collections.defaultdict(int)
646 multiple_counter = collections.defaultdict(int)
643 for perm in user_repo_group_perms_from_user_group:
647 for perm in user_repo_group_perms_from_user_group:
644 g_k = perm.UserGroupRepoGroupToPerm.group.group_name
648 g_k = perm.UserGroupRepoGroupToPerm.group.group_name
645 ug_k = perm.UserGroupRepoGroupToPerm.users_group.users_group_name
649 ug_k = perm.UserGroupRepoGroupToPerm.users_group.users_group_name
646 o = PermOrigin.REPOGROUP_USERGROUP % ug_k
650 o = PermOrigin.REPOGROUP_USERGROUP % ug_k
647 multiple_counter[g_k] += 1
651 multiple_counter[g_k] += 1
648 p = perm.Permission.permission_name
652 p = perm.Permission.permission_name
649 if perm.RepoGroup.user_id == self.user_id:
653 if perm.RepoGroup.user_id == self.user_id:
650 # set admin if owner
654 # set admin if owner
651 p = 'group.admin'
655 p = 'group.admin'
652 o = PermOrigin.REPOGROUP_OWNER
656 o = PermOrigin.REPOGROUP_OWNER
653 else:
657 else:
654 if multiple_counter[g_k] > 1:
658 if multiple_counter[g_k] > 1:
655 cur_perm = self.permissions_repository_groups[g_k]
659 cur_perm = self.permissions_repository_groups[g_k]
656 p = self._choose_permission(p, cur_perm)
660 p = self._choose_permission(p, cur_perm)
657 self.permissions_repository_groups[g_k] = p, o
661 self.permissions_repository_groups[g_k] = p, o
658
662
659 # user explicit permissions for repository groups
663 # user explicit permissions for repository groups
660 user_repo_groups_perms = Permission.get_default_group_perms(
664 user_repo_groups_perms = Permission.get_default_group_perms(
661 self.user_id, self.scope_repo_group_id)
665 self.user_id, self.scope_repo_group_id)
662 for perm in user_repo_groups_perms:
666 for perm in user_repo_groups_perms:
663 rg_k = perm.UserRepoGroupToPerm.group.group_name
667 rg_k = perm.UserRepoGroupToPerm.group.group_name
664 u_k = perm.UserRepoGroupToPerm.user.username
668 u_k = perm.UserRepoGroupToPerm.user.username
665 o = PermOrigin.REPOGROUP_USER % u_k
669 o = PermOrigin.REPOGROUP_USER % u_k
666
670
667 if perm.RepoGroup.user_id == self.user_id:
671 if perm.RepoGroup.user_id == self.user_id:
668 # set admin if owner
672 # set admin if owner
669 p = 'group.admin'
673 p = 'group.admin'
670 o = PermOrigin.REPOGROUP_OWNER
674 o = PermOrigin.REPOGROUP_OWNER
671 else:
675 else:
672 p = perm.Permission.permission_name
676 p = perm.Permission.permission_name
673 if not self.explicit:
677 if not self.explicit:
674 cur_perm = self.permissions_repository_groups.get(
678 cur_perm = self.permissions_repository_groups.get(
675 rg_k, 'group.none')
679 rg_k, 'group.none')
676 p = self._choose_permission(p, cur_perm)
680 p = self._choose_permission(p, cur_perm)
677 self.permissions_repository_groups[rg_k] = p, o
681 self.permissions_repository_groups[rg_k] = p, o
678
682
679 def _calculate_user_group_permissions(self):
683 def _calculate_user_group_permissions(self):
680 """
684 """
681 User group permissions for the current user.
685 User group permissions for the current user.
682 """
686 """
683 # user group for user group permissions
687 # user group for user group permissions
684 user_group_from_user_group = Permission\
688 user_group_from_user_group = Permission\
685 .get_default_user_group_perms_from_user_group(
689 .get_default_user_group_perms_from_user_group(
686 self.user_id, self.scope_repo_group_id)
690 self.user_id, self.scope_repo_group_id)
687
691
688 multiple_counter = collections.defaultdict(int)
692 multiple_counter = collections.defaultdict(int)
689 for perm in user_group_from_user_group:
693 for perm in user_group_from_user_group:
690 g_k = perm.UserGroupUserGroupToPerm\
694 g_k = perm.UserGroupUserGroupToPerm\
691 .target_user_group.users_group_name
695 .target_user_group.users_group_name
692 u_k = perm.UserGroupUserGroupToPerm\
696 u_k = perm.UserGroupUserGroupToPerm\
693 .user_group.users_group_name
697 .user_group.users_group_name
694 o = PermOrigin.USERGROUP_USERGROUP % u_k
698 o = PermOrigin.USERGROUP_USERGROUP % u_k
695 multiple_counter[g_k] += 1
699 multiple_counter[g_k] += 1
696 p = perm.Permission.permission_name
700 p = perm.Permission.permission_name
697 if multiple_counter[g_k] > 1:
701 if multiple_counter[g_k] > 1:
698 cur_perm = self.permissions_user_groups[g_k]
702 cur_perm = self.permissions_user_groups[g_k]
699 p = self._choose_permission(p, cur_perm)
703 p = self._choose_permission(p, cur_perm)
700 self.permissions_user_groups[g_k] = p, o
704 self.permissions_user_groups[g_k] = p, o
701
705
702 # user explicit permission for user groups
706 # user explicit permission for user groups
703 user_user_groups_perms = Permission.get_default_user_group_perms(
707 user_user_groups_perms = Permission.get_default_user_group_perms(
704 self.user_id, self.scope_user_group_id)
708 self.user_id, self.scope_user_group_id)
705 for perm in user_user_groups_perms:
709 for perm in user_user_groups_perms:
706 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
710 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
707 u_k = perm.UserUserGroupToPerm.user.username
711 u_k = perm.UserUserGroupToPerm.user.username
708 p = perm.Permission.permission_name
712 p = perm.Permission.permission_name
709 o = PermOrigin.USERGROUP_USER % u_k
713 o = PermOrigin.USERGROUP_USER % u_k
710 if not self.explicit:
714 if not self.explicit:
711 cur_perm = self.permissions_user_groups.get(
715 cur_perm = self.permissions_user_groups.get(
712 ug_k, 'usergroup.none')
716 ug_k, 'usergroup.none')
713 p = self._choose_permission(p, cur_perm)
717 p = self._choose_permission(p, cur_perm)
714 self.permissions_user_groups[ug_k] = p, o
718 self.permissions_user_groups[ug_k] = p, o
715
719
716 def _choose_permission(self, new_perm, cur_perm):
720 def _choose_permission(self, new_perm, cur_perm):
717 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
721 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
718 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
722 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
719 if self.algo == 'higherwin':
723 if self.algo == 'higherwin':
720 if new_perm_val > cur_perm_val:
724 if new_perm_val > cur_perm_val:
721 return new_perm
725 return new_perm
722 return cur_perm
726 return cur_perm
723 elif self.algo == 'lowerwin':
727 elif self.algo == 'lowerwin':
724 if new_perm_val < cur_perm_val:
728 if new_perm_val < cur_perm_val:
725 return new_perm
729 return new_perm
726 return cur_perm
730 return cur_perm
727
731
728 def _permission_structure(self):
732 def _permission_structure(self):
729 return {
733 return {
730 'global': self.permissions_global,
734 'global': self.permissions_global,
731 'repositories': self.permissions_repositories,
735 'repositories': self.permissions_repositories,
732 'repositories_groups': self.permissions_repository_groups,
736 'repositories_groups': self.permissions_repository_groups,
733 'user_groups': self.permissions_user_groups,
737 'user_groups': self.permissions_user_groups,
734 }
738 }
735
739
736
740
737 def allowed_auth_token_access(controller_name, whitelist=None, auth_token=None):
741 def allowed_auth_token_access(controller_name, whitelist=None, auth_token=None):
738 """
742 """
739 Check if given controller_name is in whitelist of auth token access
743 Check if given controller_name is in whitelist of auth token access
740 """
744 """
741 if not whitelist:
745 if not whitelist:
742 from rhodecode import CONFIG
746 from rhodecode import CONFIG
743 whitelist = aslist(
747 whitelist = aslist(
744 CONFIG.get('api_access_controllers_whitelist'), sep=',')
748 CONFIG.get('api_access_controllers_whitelist'), sep=',')
745 log.debug(
749 log.debug(
746 'Allowed controllers for AUTH TOKEN access: %s' % (whitelist,))
750 'Allowed controllers for AUTH TOKEN access: %s' % (whitelist,))
747
751
748 auth_token_access_valid = False
752 auth_token_access_valid = False
749 for entry in whitelist:
753 for entry in whitelist:
750 if fnmatch.fnmatch(controller_name, entry):
754 if fnmatch.fnmatch(controller_name, entry):
751 auth_token_access_valid = True
755 auth_token_access_valid = True
752 break
756 break
753
757
754 if auth_token_access_valid:
758 if auth_token_access_valid:
755 log.debug('controller:%s matches entry in whitelist'
759 log.debug('controller:%s matches entry in whitelist'
756 % (controller_name,))
760 % (controller_name,))
757 else:
761 else:
758 msg = ('controller: %s does *NOT* match any entry in whitelist'
762 msg = ('controller: %s does *NOT* match any entry in whitelist'
759 % (controller_name,))
763 % (controller_name,))
760 if auth_token:
764 if auth_token:
761 # if we use auth token key and don't have access it's a warning
765 # if we use auth token key and don't have access it's a warning
762 log.warning(msg)
766 log.warning(msg)
763 else:
767 else:
764 log.debug(msg)
768 log.debug(msg)
765
769
766 return auth_token_access_valid
770 return auth_token_access_valid
767
771
768
772
769 class AuthUser(object):
773 class AuthUser(object):
770 """
774 """
771 A simple object that handles all attributes of user in RhodeCode
775 A simple object that handles all attributes of user in RhodeCode
772
776
773 It does lookup based on API key,given user, or user present in session
777 It does lookup based on API key,given user, or user present in session
774 Then it fills all required information for such user. It also checks if
778 Then it fills all required information for such user. It also checks if
775 anonymous access is enabled and if so, it returns default user as logged in
779 anonymous access is enabled and if so, it returns default user as logged in
776 """
780 """
777 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
781 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
778
782
779 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
783 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
780
784
781 self.user_id = user_id
785 self.user_id = user_id
782 self._api_key = api_key
786 self._api_key = api_key
783
787
784 self.api_key = None
788 self.api_key = None
785 self.feed_token = ''
789 self.feed_token = ''
786 self.username = username
790 self.username = username
787 self.ip_addr = ip_addr
791 self.ip_addr = ip_addr
788 self.name = ''
792 self.name = ''
789 self.lastname = ''
793 self.lastname = ''
790 self.email = ''
794 self.email = ''
791 self.is_authenticated = False
795 self.is_authenticated = False
792 self.admin = False
796 self.admin = False
793 self.inherit_default_permissions = False
797 self.inherit_default_permissions = False
794 self.password = ''
798 self.password = ''
795
799
796 self.anonymous_user = None # propagated on propagate_data
800 self.anonymous_user = None # propagated on propagate_data
797 self.propagate_data()
801 self.propagate_data()
798 self._instance = None
802 self._instance = None
799 self._permissions_scoped_cache = {} # used to bind scoped calculation
803 self._permissions_scoped_cache = {} # used to bind scoped calculation
800
804
801 @LazyProperty
805 @LazyProperty
802 def permissions(self):
806 def permissions(self):
803 return self.get_perms(user=self, cache=False)
807 return self.get_perms(user=self, cache=False)
804
808
805 def permissions_with_scope(self, scope):
809 def permissions_with_scope(self, scope):
806 """
810 """
807 Call the get_perms function with scoped data. The scope in that function
811 Call the get_perms function with scoped data. The scope in that function
808 narrows the SQL calls to the given ID of objects resulting in fetching
812 narrows the SQL calls to the given ID of objects resulting in fetching
809 Just particular permission we want to obtain. If scope is an empty dict
813 Just particular permission we want to obtain. If scope is an empty dict
810 then it basically narrows the scope to GLOBAL permissions only.
814 then it basically narrows the scope to GLOBAL permissions only.
811
815
812 :param scope: dict
816 :param scope: dict
813 """
817 """
814 if 'repo_name' in scope:
818 if 'repo_name' in scope:
815 obj = Repository.get_by_repo_name(scope['repo_name'])
819 obj = Repository.get_by_repo_name(scope['repo_name'])
816 if obj:
820 if obj:
817 scope['repo_id'] = obj.repo_id
821 scope['repo_id'] = obj.repo_id
818 _scope = {
822 _scope = {
819 'repo_id': -1,
823 'repo_id': -1,
820 'user_group_id': -1,
824 'user_group_id': -1,
821 'repo_group_id': -1,
825 'repo_group_id': -1,
822 }
826 }
823 _scope.update(scope)
827 _scope.update(scope)
824 cache_key = "_".join(map(safe_str, reduce(lambda a, b: a+b,
828 cache_key = "_".join(map(safe_str, reduce(lambda a, b: a+b,
825 _scope.items())))
829 _scope.items())))
826 if cache_key not in self._permissions_scoped_cache:
830 if cache_key not in self._permissions_scoped_cache:
827 # store in cache to mimic how the @LazyProperty works,
831 # store in cache to mimic how the @LazyProperty works,
828 # the difference here is that we use the unique key calculated
832 # the difference here is that we use the unique key calculated
829 # from params and values
833 # from params and values
830 res = self.get_perms(user=self, cache=False, scope=_scope)
834 res = self.get_perms(user=self, cache=False, scope=_scope)
831 self._permissions_scoped_cache[cache_key] = res
835 self._permissions_scoped_cache[cache_key] = res
832 return self._permissions_scoped_cache[cache_key]
836 return self._permissions_scoped_cache[cache_key]
833
837
834 @property
835 def auth_tokens(self):
836 return self.get_auth_tokens()
837
838 def get_instance(self):
838 def get_instance(self):
839 return User.get(self.user_id)
839 return User.get(self.user_id)
840
840
841 def update_lastactivity(self):
841 def update_lastactivity(self):
842 if self.user_id:
842 if self.user_id:
843 User.get(self.user_id).update_lastactivity()
843 User.get(self.user_id).update_lastactivity()
844
844
845 def propagate_data(self):
845 def propagate_data(self):
846 """
846 """
847 Fills in user data and propagates values to this instance. Maps fetched
847 Fills in user data and propagates values to this instance. Maps fetched
848 user attributes to this class instance attributes
848 user attributes to this class instance attributes
849 """
849 """
850 log.debug('starting data propagation for new potential AuthUser')
850 log.debug('starting data propagation for new potential AuthUser')
851 user_model = UserModel()
851 user_model = UserModel()
852 anon_user = self.anonymous_user = User.get_default_user(cache=True)
852 anon_user = self.anonymous_user = User.get_default_user(cache=True)
853 is_user_loaded = False
853 is_user_loaded = False
854
854
855 # lookup by userid
855 # lookup by userid
856 if self.user_id is not None and self.user_id != anon_user.user_id:
856 if self.user_id is not None and self.user_id != anon_user.user_id:
857 log.debug('Trying Auth User lookup by USER ID: `%s`' % self.user_id)
857 log.debug('Trying Auth User lookup by USER ID: `%s`' % self.user_id)
858 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
858 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
859
859
860 # try go get user by api key
860 # try go get user by api key
861 elif self._api_key and self._api_key != anon_user.api_key:
861 elif self._api_key and self._api_key != anon_user.api_key:
862 log.debug('Trying Auth User lookup by API KEY: `%s`' % self._api_key)
862 log.debug('Trying Auth User lookup by API KEY: `%s`' % self._api_key)
863 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
863 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
864
864
865 # lookup by username
865 # lookup by username
866 elif self.username:
866 elif self.username:
867 log.debug('Trying Auth User lookup by USER NAME: `%s`' % self.username)
867 log.debug('Trying Auth User lookup by USER NAME: `%s`' % self.username)
868 is_user_loaded = user_model.fill_data(self, username=self.username)
868 is_user_loaded = user_model.fill_data(self, username=self.username)
869 else:
869 else:
870 log.debug('No data in %s that could been used to log in' % self)
870 log.debug('No data in %s that could been used to log in' % self)
871
871
872 if not is_user_loaded:
872 if not is_user_loaded:
873 log.debug('Failed to load user. Fallback to default user')
873 log.debug('Failed to load user. Fallback to default user')
874 # if we cannot authenticate user try anonymous
874 # if we cannot authenticate user try anonymous
875 if anon_user.active:
875 if anon_user.active:
876 user_model.fill_data(self, user_id=anon_user.user_id)
876 user_model.fill_data(self, user_id=anon_user.user_id)
877 # then we set this user is logged in
877 # then we set this user is logged in
878 self.is_authenticated = True
878 self.is_authenticated = True
879 else:
879 else:
880 # in case of disabled anonymous user we reset some of the
880 # in case of disabled anonymous user we reset some of the
881 # parameters so such user is "corrupted", skipping the fill_data
881 # parameters so such user is "corrupted", skipping the fill_data
882 for attr in ['user_id', 'username', 'admin', 'active']:
882 for attr in ['user_id', 'username', 'admin', 'active']:
883 setattr(self, attr, None)
883 setattr(self, attr, None)
884 self.is_authenticated = False
884 self.is_authenticated = False
885
885
886 if not self.username:
886 if not self.username:
887 self.username = 'None'
887 self.username = 'None'
888
888
889 log.debug('Auth User is now %s' % self)
889 log.debug('Auth User is now %s' % self)
890
890
891 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
891 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
892 cache=False):
892 cache=False):
893 """
893 """
894 Fills user permission attribute with permissions taken from database
894 Fills user permission attribute with permissions taken from database
895 works for permissions given for repositories, and for permissions that
895 works for permissions given for repositories, and for permissions that
896 are granted to groups
896 are granted to groups
897
897
898 :param user: instance of User object from database
898 :param user: instance of User object from database
899 :param explicit: In case there are permissions both for user and a group
899 :param explicit: In case there are permissions both for user and a group
900 that user is part of, explicit flag will defiine if user will
900 that user is part of, explicit flag will defiine if user will
901 explicitly override permissions from group, if it's False it will
901 explicitly override permissions from group, if it's False it will
902 make decision based on the algo
902 make decision based on the algo
903 :param algo: algorithm to decide what permission should be choose if
903 :param algo: algorithm to decide what permission should be choose if
904 it's multiple defined, eg user in two different groups. It also
904 it's multiple defined, eg user in two different groups. It also
905 decides if explicit flag is turned off how to specify the permission
905 decides if explicit flag is turned off how to specify the permission
906 for case when user is in a group + have defined separate permission
906 for case when user is in a group + have defined separate permission
907 """
907 """
908 user_id = user.user_id
908 user_id = user.user_id
909 user_is_admin = user.is_admin
909 user_is_admin = user.is_admin
910
910
911 # inheritance of global permissions like create repo/fork repo etc
911 # inheritance of global permissions like create repo/fork repo etc
912 user_inherit_default_permissions = user.inherit_default_permissions
912 user_inherit_default_permissions = user.inherit_default_permissions
913
913
914 log.debug('Computing PERMISSION tree for scope %s' % (scope, ))
914 log.debug('Computing PERMISSION tree for scope %s' % (scope, ))
915 compute = caches.conditional_cache(
915 compute = caches.conditional_cache(
916 'short_term', 'cache_desc',
916 'short_term', 'cache_desc',
917 condition=cache, func=_cached_perms_data)
917 condition=cache, func=_cached_perms_data)
918 result = compute(user_id, scope, user_is_admin,
918 result = compute(user_id, scope, user_is_admin,
919 user_inherit_default_permissions, explicit, algo)
919 user_inherit_default_permissions, explicit, algo)
920
920
921 result_repr = []
921 result_repr = []
922 for k in result:
922 for k in result:
923 result_repr.append((k, len(result[k])))
923 result_repr.append((k, len(result[k])))
924
924
925 log.debug('PERMISSION tree computed %s' % (result_repr,))
925 log.debug('PERMISSION tree computed %s' % (result_repr,))
926 return result
926 return result
927
927
928 def get_auth_tokens(self):
929 auth_tokens = [self.api_key]
930 for api_key in UserApiKeys.query()\
931 .filter(UserApiKeys.user_id == self.user_id)\
932 .filter(or_(UserApiKeys.expires == -1,
933 UserApiKeys.expires >= time.time())).all():
934 auth_tokens.append(api_key.api_key)
935
936 return auth_tokens
937
938 @property
928 @property
939 def is_default(self):
929 def is_default(self):
940 return self.username == User.DEFAULT_USER
930 return self.username == User.DEFAULT_USER
941
931
942 @property
932 @property
943 def is_admin(self):
933 def is_admin(self):
944 return self.admin
934 return self.admin
945
935
946 @property
936 @property
947 def is_user_object(self):
937 def is_user_object(self):
948 return self.user_id is not None
938 return self.user_id is not None
949
939
950 @property
940 @property
951 def repositories_admin(self):
941 def repositories_admin(self):
952 """
942 """
953 Returns list of repositories you're an admin of
943 Returns list of repositories you're an admin of
954 """
944 """
955 return [x[0] for x in self.permissions['repositories'].iteritems()
945 return [x[0] for x in self.permissions['repositories'].iteritems()
956 if x[1] == 'repository.admin']
946 if x[1] == 'repository.admin']
957
947
958 @property
948 @property
959 def repository_groups_admin(self):
949 def repository_groups_admin(self):
960 """
950 """
961 Returns list of repository groups you're an admin of
951 Returns list of repository groups you're an admin of
962 """
952 """
963 return [x[0]
953 return [x[0]
964 for x in self.permissions['repositories_groups'].iteritems()
954 for x in self.permissions['repositories_groups'].iteritems()
965 if x[1] == 'group.admin']
955 if x[1] == 'group.admin']
966
956
967 @property
957 @property
968 def user_groups_admin(self):
958 def user_groups_admin(self):
969 """
959 """
970 Returns list of user groups you're an admin of
960 Returns list of user groups you're an admin of
971 """
961 """
972 return [x[0] for x in self.permissions['user_groups'].iteritems()
962 return [x[0] for x in self.permissions['user_groups'].iteritems()
973 if x[1] == 'usergroup.admin']
963 if x[1] == 'usergroup.admin']
974
964
975 @property
965 @property
976 def ip_allowed(self):
966 def ip_allowed(self):
977 """
967 """
978 Checks if ip_addr used in constructor is allowed from defined list of
968 Checks if ip_addr used in constructor is allowed from defined list of
979 allowed ip_addresses for user
969 allowed ip_addresses for user
980
970
981 :returns: boolean, True if ip is in allowed ip range
971 :returns: boolean, True if ip is in allowed ip range
982 """
972 """
983 # check IP
973 # check IP
984 inherit = self.inherit_default_permissions
974 inherit = self.inherit_default_permissions
985 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
975 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
986 inherit_from_default=inherit)
976 inherit_from_default=inherit)
987 @property
977 @property
988 def personal_repo_group(self):
978 def personal_repo_group(self):
989 return RepoGroup.get_user_personal_repo_group(self.user_id)
979 return RepoGroup.get_user_personal_repo_group(self.user_id)
990
980
991 @classmethod
981 @classmethod
992 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
982 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
993 allowed_ips = AuthUser.get_allowed_ips(
983 allowed_ips = AuthUser.get_allowed_ips(
994 user_id, cache=True, inherit_from_default=inherit_from_default)
984 user_id, cache=True, inherit_from_default=inherit_from_default)
995 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
985 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
996 log.debug('IP:%s is in range of %s' % (ip_addr, allowed_ips))
986 log.debug('IP:%s is in range of %s' % (ip_addr, allowed_ips))
997 return True
987 return True
998 else:
988 else:
999 log.info('Access for IP:%s forbidden, '
989 log.info('Access for IP:%s forbidden, '
1000 'not in %s' % (ip_addr, allowed_ips))
990 'not in %s' % (ip_addr, allowed_ips))
1001 return False
991 return False
1002
992
1003 def __repr__(self):
993 def __repr__(self):
1004 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
994 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1005 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
995 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1006
996
1007 def set_authenticated(self, authenticated=True):
997 def set_authenticated(self, authenticated=True):
1008 if self.user_id != self.anonymous_user.user_id:
998 if self.user_id != self.anonymous_user.user_id:
1009 self.is_authenticated = authenticated
999 self.is_authenticated = authenticated
1010
1000
1011 def get_cookie_store(self):
1001 def get_cookie_store(self):
1012 return {
1002 return {
1013 'username': self.username,
1003 'username': self.username,
1014 'password': md5(self.password),
1004 'password': md5(self.password),
1015 'user_id': self.user_id,
1005 'user_id': self.user_id,
1016 'is_authenticated': self.is_authenticated
1006 'is_authenticated': self.is_authenticated
1017 }
1007 }
1018
1008
1019 @classmethod
1009 @classmethod
1020 def from_cookie_store(cls, cookie_store):
1010 def from_cookie_store(cls, cookie_store):
1021 """
1011 """
1022 Creates AuthUser from a cookie store
1012 Creates AuthUser from a cookie store
1023
1013
1024 :param cls:
1014 :param cls:
1025 :param cookie_store:
1015 :param cookie_store:
1026 """
1016 """
1027 user_id = cookie_store.get('user_id')
1017 user_id = cookie_store.get('user_id')
1028 username = cookie_store.get('username')
1018 username = cookie_store.get('username')
1029 api_key = cookie_store.get('api_key')
1019 api_key = cookie_store.get('api_key')
1030 return AuthUser(user_id, api_key, username)
1020 return AuthUser(user_id, api_key, username)
1031
1021
1032 @classmethod
1022 @classmethod
1033 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1023 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1034 _set = set()
1024 _set = set()
1035
1025
1036 if inherit_from_default:
1026 if inherit_from_default:
1037 default_ips = UserIpMap.query().filter(
1027 default_ips = UserIpMap.query().filter(
1038 UserIpMap.user == User.get_default_user(cache=True))
1028 UserIpMap.user == User.get_default_user(cache=True))
1039 if cache:
1029 if cache:
1040 default_ips = default_ips.options(FromCache("sql_cache_short",
1030 default_ips = default_ips.options(FromCache("sql_cache_short",
1041 "get_user_ips_default"))
1031 "get_user_ips_default"))
1042
1032
1043 # populate from default user
1033 # populate from default user
1044 for ip in default_ips:
1034 for ip in default_ips:
1045 try:
1035 try:
1046 _set.add(ip.ip_addr)
1036 _set.add(ip.ip_addr)
1047 except ObjectDeletedError:
1037 except ObjectDeletedError:
1048 # since we use heavy caching sometimes it happens that
1038 # since we use heavy caching sometimes it happens that
1049 # we get deleted objects here, we just skip them
1039 # we get deleted objects here, we just skip them
1050 pass
1040 pass
1051
1041
1052 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1042 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1053 if cache:
1043 if cache:
1054 user_ips = user_ips.options(FromCache("sql_cache_short",
1044 user_ips = user_ips.options(FromCache("sql_cache_short",
1055 "get_user_ips_%s" % user_id))
1045 "get_user_ips_%s" % user_id))
1056
1046
1057 for ip in user_ips:
1047 for ip in user_ips:
1058 try:
1048 try:
1059 _set.add(ip.ip_addr)
1049 _set.add(ip.ip_addr)
1060 except ObjectDeletedError:
1050 except ObjectDeletedError:
1061 # since we use heavy caching sometimes it happens that we get
1051 # since we use heavy caching sometimes it happens that we get
1062 # deleted objects here, we just skip them
1052 # deleted objects here, we just skip them
1063 pass
1053 pass
1064 return _set or set(['0.0.0.0/0', '::/0'])
1054 return _set or set(['0.0.0.0/0', '::/0'])
1065
1055
1066
1056
1067 def set_available_permissions(config):
1057 def set_available_permissions(config):
1068 """
1058 """
1069 This function will propagate pylons globals with all available defined
1059 This function will propagate pylons globals with all available defined
1070 permission given in db. We don't want to check each time from db for new
1060 permission given in db. We don't want to check each time from db for new
1071 permissions since adding a new permission also requires application restart
1061 permissions since adding a new permission also requires application restart
1072 ie. to decorate new views with the newly created permission
1062 ie. to decorate new views with the newly created permission
1073
1063
1074 :param config: current pylons config instance
1064 :param config: current pylons config instance
1075
1065
1076 """
1066 """
1077 log.info('getting information about all available permissions')
1067 log.info('getting information about all available permissions')
1078 try:
1068 try:
1079 sa = meta.Session
1069 sa = meta.Session
1080 all_perms = sa.query(Permission).all()
1070 all_perms = sa.query(Permission).all()
1081 config['available_permissions'] = [x.permission_name for x in all_perms]
1071 config['available_permissions'] = [x.permission_name for x in all_perms]
1082 except Exception:
1072 except Exception:
1083 log.error(traceback.format_exc())
1073 log.error(traceback.format_exc())
1084 finally:
1074 finally:
1085 meta.Session.remove()
1075 meta.Session.remove()
1086
1076
1087
1077
1088 def get_csrf_token(session=None, force_new=False, save_if_missing=True):
1078 def get_csrf_token(session=None, force_new=False, save_if_missing=True):
1089 """
1079 """
1090 Return the current authentication token, creating one if one doesn't
1080 Return the current authentication token, creating one if one doesn't
1091 already exist and the save_if_missing flag is present.
1081 already exist and the save_if_missing flag is present.
1092
1082
1093 :param session: pass in the pylons session, else we use the global ones
1083 :param session: pass in the pylons session, else we use the global ones
1094 :param force_new: force to re-generate the token and store it in session
1084 :param force_new: force to re-generate the token and store it in session
1095 :param save_if_missing: save the newly generated token if it's missing in
1085 :param save_if_missing: save the newly generated token if it's missing in
1096 session
1086 session
1097 """
1087 """
1098 if not session:
1088 if not session:
1099 from pylons import session
1089 from pylons import session
1100
1090
1101 if (csrf_token_key not in session and save_if_missing) or force_new:
1091 if (csrf_token_key not in session and save_if_missing) or force_new:
1102 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1092 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1103 session[csrf_token_key] = token
1093 session[csrf_token_key] = token
1104 if hasattr(session, 'save'):
1094 if hasattr(session, 'save'):
1105 session.save()
1095 session.save()
1106 return session.get(csrf_token_key)
1096 return session.get(csrf_token_key)
1107
1097
1108
1098
1109 # CHECK DECORATORS
1099 # CHECK DECORATORS
1110 class CSRFRequired(object):
1100 class CSRFRequired(object):
1111 """
1101 """
1112 Decorator for authenticating a form
1102 Decorator for authenticating a form
1113
1103
1114 This decorator uses an authorization token stored in the client's
1104 This decorator uses an authorization token stored in the client's
1115 session for prevention of certain Cross-site request forgery (CSRF)
1105 session for prevention of certain Cross-site request forgery (CSRF)
1116 attacks (See
1106 attacks (See
1117 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1107 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1118 information).
1108 information).
1119
1109
1120 For use with the ``webhelpers.secure_form`` helper functions.
1110 For use with the ``webhelpers.secure_form`` helper functions.
1121
1111
1122 """
1112 """
1123 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1113 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1124 except_methods=None):
1114 except_methods=None):
1125 self.token = token
1115 self.token = token
1126 self.header = header
1116 self.header = header
1127 self.except_methods = except_methods or []
1117 self.except_methods = except_methods or []
1128
1118
1129 def __call__(self, func):
1119 def __call__(self, func):
1130 return get_cython_compat_decorator(self.__wrapper, func)
1120 return get_cython_compat_decorator(self.__wrapper, func)
1131
1121
1132 def _get_csrf(self, _request):
1122 def _get_csrf(self, _request):
1133 return _request.POST.get(self.token, _request.headers.get(self.header))
1123 return _request.POST.get(self.token, _request.headers.get(self.header))
1134
1124
1135 def check_csrf(self, _request, cur_token):
1125 def check_csrf(self, _request, cur_token):
1136 supplied_token = self._get_csrf(_request)
1126 supplied_token = self._get_csrf(_request)
1137 return supplied_token and supplied_token == cur_token
1127 return supplied_token and supplied_token == cur_token
1138
1128
1139 def __wrapper(self, func, *fargs, **fkwargs):
1129 def __wrapper(self, func, *fargs, **fkwargs):
1140 if request.method in self.except_methods:
1130 if request.method in self.except_methods:
1141 return func(*fargs, **fkwargs)
1131 return func(*fargs, **fkwargs)
1142
1132
1143 cur_token = get_csrf_token(save_if_missing=False)
1133 cur_token = get_csrf_token(save_if_missing=False)
1144 if self.check_csrf(request, cur_token):
1134 if self.check_csrf(request, cur_token):
1145 if request.POST.get(self.token):
1135 if request.POST.get(self.token):
1146 del request.POST[self.token]
1136 del request.POST[self.token]
1147 return func(*fargs, **fkwargs)
1137 return func(*fargs, **fkwargs)
1148 else:
1138 else:
1149 reason = 'token-missing'
1139 reason = 'token-missing'
1150 supplied_token = self._get_csrf(request)
1140 supplied_token = self._get_csrf(request)
1151 if supplied_token and cur_token != supplied_token:
1141 if supplied_token and cur_token != supplied_token:
1152 reason = 'token-mismatch [%s:%s]' % (cur_token or ''[:6],
1142 reason = 'token-mismatch [%s:%s]' % (cur_token or ''[:6],
1153 supplied_token or ''[:6])
1143 supplied_token or ''[:6])
1154
1144
1155 csrf_message = \
1145 csrf_message = \
1156 ("Cross-site request forgery detected, request denied. See "
1146 ("Cross-site request forgery detected, request denied. See "
1157 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1147 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1158 "more information.")
1148 "more information.")
1159 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1149 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1160 'REMOTE_ADDR:%s, HEADERS:%s' % (
1150 'REMOTE_ADDR:%s, HEADERS:%s' % (
1161 request, reason, request.remote_addr, request.headers))
1151 request, reason, request.remote_addr, request.headers))
1162
1152
1163 raise HTTPForbidden(explanation=csrf_message)
1153 raise HTTPForbidden(explanation=csrf_message)
1164
1154
1165
1155
1166 class LoginRequired(object):
1156 class LoginRequired(object):
1167 """
1157 """
1168 Must be logged in to execute this function else
1158 Must be logged in to execute this function else
1169 redirect to login page
1159 redirect to login page
1170
1160
1171 :param api_access: if enabled this checks only for valid auth token
1161 :param api_access: if enabled this checks only for valid auth token
1172 and grants access based on valid token
1162 and grants access based on valid token
1173 """
1163 """
1174 def __init__(self, auth_token_access=False):
1164 def __init__(self, auth_token_access=None):
1175 self.auth_token_access = auth_token_access
1165 self.auth_token_access = auth_token_access
1176
1166
1177 def __call__(self, func):
1167 def __call__(self, func):
1178 return get_cython_compat_decorator(self.__wrapper, func)
1168 return get_cython_compat_decorator(self.__wrapper, func)
1179
1169
1180 def __wrapper(self, func, *fargs, **fkwargs):
1170 def __wrapper(self, func, *fargs, **fkwargs):
1181 from rhodecode.lib import helpers as h
1171 from rhodecode.lib import helpers as h
1182 cls = fargs[0]
1172 cls = fargs[0]
1183 user = cls._rhodecode_user
1173 user = cls._rhodecode_user
1184 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1174 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1185 log.debug('Starting login restriction checks for user: %s' % (user,))
1175 log.debug('Starting login restriction checks for user: %s' % (user,))
1186 # check if our IP is allowed
1176 # check if our IP is allowed
1187 ip_access_valid = True
1177 ip_access_valid = True
1188 if not user.ip_allowed:
1178 if not user.ip_allowed:
1189 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1179 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1190 category='warning')
1180 category='warning')
1191 ip_access_valid = False
1181 ip_access_valid = False
1192
1182
1193 # check if we used an APIKEY and it's a valid one
1183 # check if we used an APIKEY and it's a valid one
1194 # defined whitelist of controllers which API access will be enabled
1184 # defined white-list of controllers which API access will be enabled
1195 _auth_token = request.GET.get(
1185 _auth_token = request.GET.get(
1196 'auth_token', '') or request.GET.get('api_key', '')
1186 'auth_token', '') or request.GET.get('api_key', '')
1197 auth_token_access_valid = allowed_auth_token_access(
1187 auth_token_access_valid = allowed_auth_token_access(
1198 loc, auth_token=_auth_token)
1188 loc, auth_token=_auth_token)
1199
1189
1200 # explicit controller is enabled or API is in our whitelist
1190 # explicit controller is enabled or API is in our whitelist
1201 if self.auth_token_access or auth_token_access_valid:
1191 if self.auth_token_access or auth_token_access_valid:
1202 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1192 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1193 db_user = user.get_instance()
1203
1194
1204 if _auth_token and _auth_token in user.auth_tokens:
1195 if db_user:
1196 if self.auth_token_access:
1197 roles = self.auth_token_access
1198 else:
1199 roles = [UserApiKeys.ROLE_HTTP]
1200 token_match = db_user.authenticate_by_token(
1201 _auth_token, roles=roles, include_builtin_token=True)
1202 else:
1203 log.debug('Unable to fetch db instance for auth user: %s', user)
1204 token_match = False
1205
1206 if _auth_token and token_match:
1205 auth_token_access_valid = True
1207 auth_token_access_valid = True
1206 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1208 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1207 else:
1209 else:
1208 auth_token_access_valid = False
1210 auth_token_access_valid = False
1209 if not _auth_token:
1211 if not _auth_token:
1210 log.debug("AUTH TOKEN *NOT* present in request")
1212 log.debug("AUTH TOKEN *NOT* present in request")
1211 else:
1213 else:
1212 log.warning(
1214 log.warning(
1213 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1215 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1214
1216
1215 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1217 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1216 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1218 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1217 else 'AUTH_TOKEN_AUTH'
1219 else 'AUTH_TOKEN_AUTH'
1218
1220
1219 if ip_access_valid and (
1221 if ip_access_valid and (
1220 user.is_authenticated or auth_token_access_valid):
1222 user.is_authenticated or auth_token_access_valid):
1221 log.info(
1223 log.info(
1222 'user %s authenticating with:%s IS authenticated on func %s'
1224 'user %s authenticating with:%s IS authenticated on func %s'
1223 % (user, reason, loc))
1225 % (user, reason, loc))
1224
1226
1225 # update user data to check last activity
1227 # update user data to check last activity
1226 user.update_lastactivity()
1228 user.update_lastactivity()
1227 Session().commit()
1229 Session().commit()
1228 return func(*fargs, **fkwargs)
1230 return func(*fargs, **fkwargs)
1229 else:
1231 else:
1230 log.warning(
1232 log.warning(
1231 'user %s authenticating with:%s NOT authenticated on '
1233 'user %s authenticating with:%s NOT authenticated on '
1232 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1234 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1233 % (user, reason, loc, ip_access_valid,
1235 % (user, reason, loc, ip_access_valid,
1234 auth_token_access_valid))
1236 auth_token_access_valid))
1235 # we preserve the get PARAM
1237 # we preserve the get PARAM
1236 came_from = request.path_qs
1238 came_from = request.path_qs
1237
1239
1238 log.debug('redirecting to login page with %s' % (came_from,))
1240 log.debug('redirecting to login page with %s' % (came_from,))
1239 return redirect(
1241 return redirect(
1240 h.route_path('login', _query={'came_from': came_from}))
1242 h.route_path('login', _query={'came_from': came_from}))
1241
1243
1242
1244
1243 class NotAnonymous(object):
1245 class NotAnonymous(object):
1244 """
1246 """
1245 Must be logged in to execute this function else
1247 Must be logged in to execute this function else
1246 redirect to login page"""
1248 redirect to login page"""
1247
1249
1248 def __call__(self, func):
1250 def __call__(self, func):
1249 return get_cython_compat_decorator(self.__wrapper, func)
1251 return get_cython_compat_decorator(self.__wrapper, func)
1250
1252
1251 def __wrapper(self, func, *fargs, **fkwargs):
1253 def __wrapper(self, func, *fargs, **fkwargs):
1252 cls = fargs[0]
1254 cls = fargs[0]
1253 self.user = cls._rhodecode_user
1255 self.user = cls._rhodecode_user
1254
1256
1255 log.debug('Checking if user is not anonymous @%s' % cls)
1257 log.debug('Checking if user is not anonymous @%s' % cls)
1256
1258
1257 anonymous = self.user.username == User.DEFAULT_USER
1259 anonymous = self.user.username == User.DEFAULT_USER
1258
1260
1259 if anonymous:
1261 if anonymous:
1260 came_from = request.path_qs
1262 came_from = request.path_qs
1261
1263
1262 import rhodecode.lib.helpers as h
1264 import rhodecode.lib.helpers as h
1263 h.flash(_('You need to be a registered user to '
1265 h.flash(_('You need to be a registered user to '
1264 'perform this action'),
1266 'perform this action'),
1265 category='warning')
1267 category='warning')
1266 return redirect(
1268 return redirect(
1267 h.route_path('login', _query={'came_from': came_from}))
1269 h.route_path('login', _query={'came_from': came_from}))
1268 else:
1270 else:
1269 return func(*fargs, **fkwargs)
1271 return func(*fargs, **fkwargs)
1270
1272
1271
1273
1272 class XHRRequired(object):
1274 class XHRRequired(object):
1273 def __call__(self, func):
1275 def __call__(self, func):
1274 return get_cython_compat_decorator(self.__wrapper, func)
1276 return get_cython_compat_decorator(self.__wrapper, func)
1275
1277
1276 def __wrapper(self, func, *fargs, **fkwargs):
1278 def __wrapper(self, func, *fargs, **fkwargs):
1277 log.debug('Checking if request is XMLHttpRequest (XHR)')
1279 log.debug('Checking if request is XMLHttpRequest (XHR)')
1278 xhr_message = 'This is not a valid XMLHttpRequest (XHR) request'
1280 xhr_message = 'This is not a valid XMLHttpRequest (XHR) request'
1279 if not request.is_xhr:
1281 if not request.is_xhr:
1280 abort(400, detail=xhr_message)
1282 abort(400, detail=xhr_message)
1281
1283
1282 return func(*fargs, **fkwargs)
1284 return func(*fargs, **fkwargs)
1283
1285
1284
1286
1285 class HasAcceptedRepoType(object):
1287 class HasAcceptedRepoType(object):
1286 """
1288 """
1287 Check if requested repo is within given repo type aliases
1289 Check if requested repo is within given repo type aliases
1288
1290
1289 TODO: anderson: not sure where to put this decorator
1291 TODO: anderson: not sure where to put this decorator
1290 """
1292 """
1291
1293
1292 def __init__(self, *repo_type_list):
1294 def __init__(self, *repo_type_list):
1293 self.repo_type_list = set(repo_type_list)
1295 self.repo_type_list = set(repo_type_list)
1294
1296
1295 def __call__(self, func):
1297 def __call__(self, func):
1296 return get_cython_compat_decorator(self.__wrapper, func)
1298 return get_cython_compat_decorator(self.__wrapper, func)
1297
1299
1298 def __wrapper(self, func, *fargs, **fkwargs):
1300 def __wrapper(self, func, *fargs, **fkwargs):
1299 cls = fargs[0]
1301 cls = fargs[0]
1300 rhodecode_repo = cls.rhodecode_repo
1302 rhodecode_repo = cls.rhodecode_repo
1301
1303
1302 log.debug('%s checking repo type for %s in %s',
1304 log.debug('%s checking repo type for %s in %s',
1303 self.__class__.__name__,
1305 self.__class__.__name__,
1304 rhodecode_repo.alias, self.repo_type_list)
1306 rhodecode_repo.alias, self.repo_type_list)
1305
1307
1306 if rhodecode_repo.alias in self.repo_type_list:
1308 if rhodecode_repo.alias in self.repo_type_list:
1307 return func(*fargs, **fkwargs)
1309 return func(*fargs, **fkwargs)
1308 else:
1310 else:
1309 import rhodecode.lib.helpers as h
1311 import rhodecode.lib.helpers as h
1310 h.flash(h.literal(
1312 h.flash(h.literal(
1311 _('Action not supported for %s.' % rhodecode_repo.alias)),
1313 _('Action not supported for %s.' % rhodecode_repo.alias)),
1312 category='warning')
1314 category='warning')
1313 return redirect(
1315 return redirect(
1314 url('summary_home', repo_name=cls.rhodecode_db_repo.repo_name))
1316 url('summary_home', repo_name=cls.rhodecode_db_repo.repo_name))
1315
1317
1316
1318
1317 class PermsDecorator(object):
1319 class PermsDecorator(object):
1318 """
1320 """
1319 Base class for controller decorators, we extract the current user from
1321 Base class for controller decorators, we extract the current user from
1320 the class itself, which has it stored in base controllers
1322 the class itself, which has it stored in base controllers
1321 """
1323 """
1322
1324
1323 def __init__(self, *required_perms):
1325 def __init__(self, *required_perms):
1324 self.required_perms = set(required_perms)
1326 self.required_perms = set(required_perms)
1325
1327
1326 def __call__(self, func):
1328 def __call__(self, func):
1327 return get_cython_compat_decorator(self.__wrapper, func)
1329 return get_cython_compat_decorator(self.__wrapper, func)
1328
1330
1329 def __wrapper(self, func, *fargs, **fkwargs):
1331 def __wrapper(self, func, *fargs, **fkwargs):
1330 cls = fargs[0]
1332 cls = fargs[0]
1331 _user = cls._rhodecode_user
1333 _user = cls._rhodecode_user
1332
1334
1333 log.debug('checking %s permissions %s for %s %s',
1335 log.debug('checking %s permissions %s for %s %s',
1334 self.__class__.__name__, self.required_perms, cls, _user)
1336 self.__class__.__name__, self.required_perms, cls, _user)
1335
1337
1336 if self.check_permissions(_user):
1338 if self.check_permissions(_user):
1337 log.debug('Permission granted for %s %s', cls, _user)
1339 log.debug('Permission granted for %s %s', cls, _user)
1338 return func(*fargs, **fkwargs)
1340 return func(*fargs, **fkwargs)
1339
1341
1340 else:
1342 else:
1341 log.debug('Permission denied for %s %s', cls, _user)
1343 log.debug('Permission denied for %s %s', cls, _user)
1342 anonymous = _user.username == User.DEFAULT_USER
1344 anonymous = _user.username == User.DEFAULT_USER
1343
1345
1344 if anonymous:
1346 if anonymous:
1345 came_from = request.path_qs
1347 came_from = request.path_qs
1346
1348
1347 import rhodecode.lib.helpers as h
1349 import rhodecode.lib.helpers as h
1348 h.flash(_('You need to be signed in to view this page'),
1350 h.flash(_('You need to be signed in to view this page'),
1349 category='warning')
1351 category='warning')
1350 return redirect(
1352 return redirect(
1351 h.route_path('login', _query={'came_from': came_from}))
1353 h.route_path('login', _query={'came_from': came_from}))
1352
1354
1353 else:
1355 else:
1354 # redirect with forbidden ret code
1356 # redirect with forbidden ret code
1355 return abort(403)
1357 return abort(403)
1356
1358
1357 def check_permissions(self, user):
1359 def check_permissions(self, user):
1358 """Dummy function for overriding"""
1360 """Dummy function for overriding"""
1359 raise NotImplementedError(
1361 raise NotImplementedError(
1360 'You have to write this function in child class')
1362 'You have to write this function in child class')
1361
1363
1362
1364
1363 class HasPermissionAllDecorator(PermsDecorator):
1365 class HasPermissionAllDecorator(PermsDecorator):
1364 """
1366 """
1365 Checks for access permission for all given predicates. All of them
1367 Checks for access permission for all given predicates. All of them
1366 have to be meet in order to fulfill the request
1368 have to be meet in order to fulfill the request
1367 """
1369 """
1368
1370
1369 def check_permissions(self, user):
1371 def check_permissions(self, user):
1370 perms = user.permissions_with_scope({})
1372 perms = user.permissions_with_scope({})
1371 if self.required_perms.issubset(perms['global']):
1373 if self.required_perms.issubset(perms['global']):
1372 return True
1374 return True
1373 return False
1375 return False
1374
1376
1375
1377
1376 class HasPermissionAnyDecorator(PermsDecorator):
1378 class HasPermissionAnyDecorator(PermsDecorator):
1377 """
1379 """
1378 Checks for access permission for any of given predicates. In order to
1380 Checks for access permission for any of given predicates. In order to
1379 fulfill the request any of predicates must be meet
1381 fulfill the request any of predicates must be meet
1380 """
1382 """
1381
1383
1382 def check_permissions(self, user):
1384 def check_permissions(self, user):
1383 perms = user.permissions_with_scope({})
1385 perms = user.permissions_with_scope({})
1384 if self.required_perms.intersection(perms['global']):
1386 if self.required_perms.intersection(perms['global']):
1385 return True
1387 return True
1386 return False
1388 return False
1387
1389
1388
1390
1389 class HasRepoPermissionAllDecorator(PermsDecorator):
1391 class HasRepoPermissionAllDecorator(PermsDecorator):
1390 """
1392 """
1391 Checks for access permission for all given predicates for specific
1393 Checks for access permission for all given predicates for specific
1392 repository. All of them have to be meet in order to fulfill the request
1394 repository. All of them have to be meet in order to fulfill the request
1393 """
1395 """
1394
1396
1395 def check_permissions(self, user):
1397 def check_permissions(self, user):
1396 perms = user.permissions
1398 perms = user.permissions
1397 repo_name = get_repo_slug(request)
1399 repo_name = get_repo_slug(request)
1398 try:
1400 try:
1399 user_perms = set([perms['repositories'][repo_name]])
1401 user_perms = set([perms['repositories'][repo_name]])
1400 except KeyError:
1402 except KeyError:
1401 return False
1403 return False
1402 if self.required_perms.issubset(user_perms):
1404 if self.required_perms.issubset(user_perms):
1403 return True
1405 return True
1404 return False
1406 return False
1405
1407
1406
1408
1407 class HasRepoPermissionAnyDecorator(PermsDecorator):
1409 class HasRepoPermissionAnyDecorator(PermsDecorator):
1408 """
1410 """
1409 Checks for access permission for any of given predicates for specific
1411 Checks for access permission for any of given predicates for specific
1410 repository. In order to fulfill the request any of predicates must be meet
1412 repository. In order to fulfill the request any of predicates must be meet
1411 """
1413 """
1412
1414
1413 def check_permissions(self, user):
1415 def check_permissions(self, user):
1414 perms = user.permissions
1416 perms = user.permissions
1415 repo_name = get_repo_slug(request)
1417 repo_name = get_repo_slug(request)
1416 try:
1418 try:
1417 user_perms = set([perms['repositories'][repo_name]])
1419 user_perms = set([perms['repositories'][repo_name]])
1418 except KeyError:
1420 except KeyError:
1419 return False
1421 return False
1420
1422
1421 if self.required_perms.intersection(user_perms):
1423 if self.required_perms.intersection(user_perms):
1422 return True
1424 return True
1423 return False
1425 return False
1424
1426
1425
1427
1426 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1428 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1427 """
1429 """
1428 Checks for access permission for all given predicates for specific
1430 Checks for access permission for all given predicates for specific
1429 repository group. All of them have to be meet in order to
1431 repository group. All of them have to be meet in order to
1430 fulfill the request
1432 fulfill the request
1431 """
1433 """
1432
1434
1433 def check_permissions(self, user):
1435 def check_permissions(self, user):
1434 perms = user.permissions
1436 perms = user.permissions
1435 group_name = get_repo_group_slug(request)
1437 group_name = get_repo_group_slug(request)
1436 try:
1438 try:
1437 user_perms = set([perms['repositories_groups'][group_name]])
1439 user_perms = set([perms['repositories_groups'][group_name]])
1438 except KeyError:
1440 except KeyError:
1439 return False
1441 return False
1440
1442
1441 if self.required_perms.issubset(user_perms):
1443 if self.required_perms.issubset(user_perms):
1442 return True
1444 return True
1443 return False
1445 return False
1444
1446
1445
1447
1446 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1448 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1447 """
1449 """
1448 Checks for access permission for any of given predicates for specific
1450 Checks for access permission for any of given predicates for specific
1449 repository group. In order to fulfill the request any
1451 repository group. In order to fulfill the request any
1450 of predicates must be met
1452 of predicates must be met
1451 """
1453 """
1452
1454
1453 def check_permissions(self, user):
1455 def check_permissions(self, user):
1454 perms = user.permissions
1456 perms = user.permissions
1455 group_name = get_repo_group_slug(request)
1457 group_name = get_repo_group_slug(request)
1456 try:
1458 try:
1457 user_perms = set([perms['repositories_groups'][group_name]])
1459 user_perms = set([perms['repositories_groups'][group_name]])
1458 except KeyError:
1460 except KeyError:
1459 return False
1461 return False
1460
1462
1461 if self.required_perms.intersection(user_perms):
1463 if self.required_perms.intersection(user_perms):
1462 return True
1464 return True
1463 return False
1465 return False
1464
1466
1465
1467
1466 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1468 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1467 """
1469 """
1468 Checks for access permission for all given predicates for specific
1470 Checks for access permission for all given predicates for specific
1469 user group. All of them have to be meet in order to fulfill the request
1471 user group. All of them have to be meet in order to fulfill the request
1470 """
1472 """
1471
1473
1472 def check_permissions(self, user):
1474 def check_permissions(self, user):
1473 perms = user.permissions
1475 perms = user.permissions
1474 group_name = get_user_group_slug(request)
1476 group_name = get_user_group_slug(request)
1475 try:
1477 try:
1476 user_perms = set([perms['user_groups'][group_name]])
1478 user_perms = set([perms['user_groups'][group_name]])
1477 except KeyError:
1479 except KeyError:
1478 return False
1480 return False
1479
1481
1480 if self.required_perms.issubset(user_perms):
1482 if self.required_perms.issubset(user_perms):
1481 return True
1483 return True
1482 return False
1484 return False
1483
1485
1484
1486
1485 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1487 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1486 """
1488 """
1487 Checks for access permission for any of given predicates for specific
1489 Checks for access permission for any of given predicates for specific
1488 user group. In order to fulfill the request any of predicates must be meet
1490 user group. In order to fulfill the request any of predicates must be meet
1489 """
1491 """
1490
1492
1491 def check_permissions(self, user):
1493 def check_permissions(self, user):
1492 perms = user.permissions
1494 perms = user.permissions
1493 group_name = get_user_group_slug(request)
1495 group_name = get_user_group_slug(request)
1494 try:
1496 try:
1495 user_perms = set([perms['user_groups'][group_name]])
1497 user_perms = set([perms['user_groups'][group_name]])
1496 except KeyError:
1498 except KeyError:
1497 return False
1499 return False
1498
1500
1499 if self.required_perms.intersection(user_perms):
1501 if self.required_perms.intersection(user_perms):
1500 return True
1502 return True
1501 return False
1503 return False
1502
1504
1503
1505
1504 # CHECK FUNCTIONS
1506 # CHECK FUNCTIONS
1505 class PermsFunction(object):
1507 class PermsFunction(object):
1506 """Base function for other check functions"""
1508 """Base function for other check functions"""
1507
1509
1508 def __init__(self, *perms):
1510 def __init__(self, *perms):
1509 self.required_perms = set(perms)
1511 self.required_perms = set(perms)
1510 self.repo_name = None
1512 self.repo_name = None
1511 self.repo_group_name = None
1513 self.repo_group_name = None
1512 self.user_group_name = None
1514 self.user_group_name = None
1513
1515
1514 def __bool__(self):
1516 def __bool__(self):
1515 frame = inspect.currentframe()
1517 frame = inspect.currentframe()
1516 stack_trace = traceback.format_stack(frame)
1518 stack_trace = traceback.format_stack(frame)
1517 log.error('Checking bool value on a class instance of perm '
1519 log.error('Checking bool value on a class instance of perm '
1518 'function is not allowed: %s' % ''.join(stack_trace))
1520 'function is not allowed: %s' % ''.join(stack_trace))
1519 # rather than throwing errors, here we always return False so if by
1521 # rather than throwing errors, here we always return False so if by
1520 # accident someone checks truth for just an instance it will always end
1522 # accident someone checks truth for just an instance it will always end
1521 # up in returning False
1523 # up in returning False
1522 return False
1524 return False
1523 __nonzero__ = __bool__
1525 __nonzero__ = __bool__
1524
1526
1525 def __call__(self, check_location='', user=None):
1527 def __call__(self, check_location='', user=None):
1526 if not user:
1528 if not user:
1527 log.debug('Using user attribute from global request')
1529 log.debug('Using user attribute from global request')
1528 # TODO: remove this someday,put as user as attribute here
1530 # TODO: remove this someday,put as user as attribute here
1529 user = request.user
1531 user = request.user
1530
1532
1531 # init auth user if not already given
1533 # init auth user if not already given
1532 if not isinstance(user, AuthUser):
1534 if not isinstance(user, AuthUser):
1533 log.debug('Wrapping user %s into AuthUser', user)
1535 log.debug('Wrapping user %s into AuthUser', user)
1534 user = AuthUser(user.user_id)
1536 user = AuthUser(user.user_id)
1535
1537
1536 cls_name = self.__class__.__name__
1538 cls_name = self.__class__.__name__
1537 check_scope = self._get_check_scope(cls_name)
1539 check_scope = self._get_check_scope(cls_name)
1538 check_location = check_location or 'unspecified location'
1540 check_location = check_location or 'unspecified location'
1539
1541
1540 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1542 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1541 self.required_perms, user, check_scope, check_location)
1543 self.required_perms, user, check_scope, check_location)
1542 if not user:
1544 if not user:
1543 log.warning('Empty user given for permission check')
1545 log.warning('Empty user given for permission check')
1544 return False
1546 return False
1545
1547
1546 if self.check_permissions(user):
1548 if self.check_permissions(user):
1547 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1549 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1548 check_scope, user, check_location)
1550 check_scope, user, check_location)
1549 return True
1551 return True
1550
1552
1551 else:
1553 else:
1552 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1554 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1553 check_scope, user, check_location)
1555 check_scope, user, check_location)
1554 return False
1556 return False
1555
1557
1556 def _get_check_scope(self, cls_name):
1558 def _get_check_scope(self, cls_name):
1557 return {
1559 return {
1558 'HasPermissionAll': 'GLOBAL',
1560 'HasPermissionAll': 'GLOBAL',
1559 'HasPermissionAny': 'GLOBAL',
1561 'HasPermissionAny': 'GLOBAL',
1560 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1562 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1561 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1563 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1562 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1564 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1563 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1565 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1564 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1566 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1565 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1567 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1566 }.get(cls_name, '?:%s' % cls_name)
1568 }.get(cls_name, '?:%s' % cls_name)
1567
1569
1568 def check_permissions(self, user):
1570 def check_permissions(self, user):
1569 """Dummy function for overriding"""
1571 """Dummy function for overriding"""
1570 raise Exception('You have to write this function in child class')
1572 raise Exception('You have to write this function in child class')
1571
1573
1572
1574
1573 class HasPermissionAll(PermsFunction):
1575 class HasPermissionAll(PermsFunction):
1574 def check_permissions(self, user):
1576 def check_permissions(self, user):
1575 perms = user.permissions_with_scope({})
1577 perms = user.permissions_with_scope({})
1576 if self.required_perms.issubset(perms.get('global')):
1578 if self.required_perms.issubset(perms.get('global')):
1577 return True
1579 return True
1578 return False
1580 return False
1579
1581
1580
1582
1581 class HasPermissionAny(PermsFunction):
1583 class HasPermissionAny(PermsFunction):
1582 def check_permissions(self, user):
1584 def check_permissions(self, user):
1583 perms = user.permissions_with_scope({})
1585 perms = user.permissions_with_scope({})
1584 if self.required_perms.intersection(perms.get('global')):
1586 if self.required_perms.intersection(perms.get('global')):
1585 return True
1587 return True
1586 return False
1588 return False
1587
1589
1588
1590
1589 class HasRepoPermissionAll(PermsFunction):
1591 class HasRepoPermissionAll(PermsFunction):
1590 def __call__(self, repo_name=None, check_location='', user=None):
1592 def __call__(self, repo_name=None, check_location='', user=None):
1591 self.repo_name = repo_name
1593 self.repo_name = repo_name
1592 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1594 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1593
1595
1594 def check_permissions(self, user):
1596 def check_permissions(self, user):
1595 if not self.repo_name:
1597 if not self.repo_name:
1596 self.repo_name = get_repo_slug(request)
1598 self.repo_name = get_repo_slug(request)
1597
1599
1598 perms = user.permissions
1600 perms = user.permissions
1599 try:
1601 try:
1600 user_perms = set([perms['repositories'][self.repo_name]])
1602 user_perms = set([perms['repositories'][self.repo_name]])
1601 except KeyError:
1603 except KeyError:
1602 return False
1604 return False
1603 if self.required_perms.issubset(user_perms):
1605 if self.required_perms.issubset(user_perms):
1604 return True
1606 return True
1605 return False
1607 return False
1606
1608
1607
1609
1608 class HasRepoPermissionAny(PermsFunction):
1610 class HasRepoPermissionAny(PermsFunction):
1609 def __call__(self, repo_name=None, check_location='', user=None):
1611 def __call__(self, repo_name=None, check_location='', user=None):
1610 self.repo_name = repo_name
1612 self.repo_name = repo_name
1611 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1613 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1612
1614
1613 def check_permissions(self, user):
1615 def check_permissions(self, user):
1614 if not self.repo_name:
1616 if not self.repo_name:
1615 self.repo_name = get_repo_slug(request)
1617 self.repo_name = get_repo_slug(request)
1616
1618
1617 perms = user.permissions
1619 perms = user.permissions
1618 try:
1620 try:
1619 user_perms = set([perms['repositories'][self.repo_name]])
1621 user_perms = set([perms['repositories'][self.repo_name]])
1620 except KeyError:
1622 except KeyError:
1621 return False
1623 return False
1622 if self.required_perms.intersection(user_perms):
1624 if self.required_perms.intersection(user_perms):
1623 return True
1625 return True
1624 return False
1626 return False
1625
1627
1626
1628
1627 class HasRepoGroupPermissionAny(PermsFunction):
1629 class HasRepoGroupPermissionAny(PermsFunction):
1628 def __call__(self, group_name=None, check_location='', user=None):
1630 def __call__(self, group_name=None, check_location='', user=None):
1629 self.repo_group_name = group_name
1631 self.repo_group_name = group_name
1630 return super(HasRepoGroupPermissionAny, self).__call__(
1632 return super(HasRepoGroupPermissionAny, self).__call__(
1631 check_location, user)
1633 check_location, user)
1632
1634
1633 def check_permissions(self, user):
1635 def check_permissions(self, user):
1634 perms = user.permissions
1636 perms = user.permissions
1635 try:
1637 try:
1636 user_perms = set(
1638 user_perms = set(
1637 [perms['repositories_groups'][self.repo_group_name]])
1639 [perms['repositories_groups'][self.repo_group_name]])
1638 except KeyError:
1640 except KeyError:
1639 return False
1641 return False
1640 if self.required_perms.intersection(user_perms):
1642 if self.required_perms.intersection(user_perms):
1641 return True
1643 return True
1642 return False
1644 return False
1643
1645
1644
1646
1645 class HasRepoGroupPermissionAll(PermsFunction):
1647 class HasRepoGroupPermissionAll(PermsFunction):
1646 def __call__(self, group_name=None, check_location='', user=None):
1648 def __call__(self, group_name=None, check_location='', user=None):
1647 self.repo_group_name = group_name
1649 self.repo_group_name = group_name
1648 return super(HasRepoGroupPermissionAll, self).__call__(
1650 return super(HasRepoGroupPermissionAll, self).__call__(
1649 check_location, user)
1651 check_location, user)
1650
1652
1651 def check_permissions(self, user):
1653 def check_permissions(self, user):
1652 perms = user.permissions
1654 perms = user.permissions
1653 try:
1655 try:
1654 user_perms = set(
1656 user_perms = set(
1655 [perms['repositories_groups'][self.repo_group_name]])
1657 [perms['repositories_groups'][self.repo_group_name]])
1656 except KeyError:
1658 except KeyError:
1657 return False
1659 return False
1658 if self.required_perms.issubset(user_perms):
1660 if self.required_perms.issubset(user_perms):
1659 return True
1661 return True
1660 return False
1662 return False
1661
1663
1662
1664
1663 class HasUserGroupPermissionAny(PermsFunction):
1665 class HasUserGroupPermissionAny(PermsFunction):
1664 def __call__(self, user_group_name=None, check_location='', user=None):
1666 def __call__(self, user_group_name=None, check_location='', user=None):
1665 self.user_group_name = user_group_name
1667 self.user_group_name = user_group_name
1666 return super(HasUserGroupPermissionAny, self).__call__(
1668 return super(HasUserGroupPermissionAny, self).__call__(
1667 check_location, user)
1669 check_location, user)
1668
1670
1669 def check_permissions(self, user):
1671 def check_permissions(self, user):
1670 perms = user.permissions
1672 perms = user.permissions
1671 try:
1673 try:
1672 user_perms = set([perms['user_groups'][self.user_group_name]])
1674 user_perms = set([perms['user_groups'][self.user_group_name]])
1673 except KeyError:
1675 except KeyError:
1674 return False
1676 return False
1675 if self.required_perms.intersection(user_perms):
1677 if self.required_perms.intersection(user_perms):
1676 return True
1678 return True
1677 return False
1679 return False
1678
1680
1679
1681
1680 class HasUserGroupPermissionAll(PermsFunction):
1682 class HasUserGroupPermissionAll(PermsFunction):
1681 def __call__(self, user_group_name=None, check_location='', user=None):
1683 def __call__(self, user_group_name=None, check_location='', user=None):
1682 self.user_group_name = user_group_name
1684 self.user_group_name = user_group_name
1683 return super(HasUserGroupPermissionAll, self).__call__(
1685 return super(HasUserGroupPermissionAll, self).__call__(
1684 check_location, user)
1686 check_location, user)
1685
1687
1686 def check_permissions(self, user):
1688 def check_permissions(self, user):
1687 perms = user.permissions
1689 perms = user.permissions
1688 try:
1690 try:
1689 user_perms = set([perms['user_groups'][self.user_group_name]])
1691 user_perms = set([perms['user_groups'][self.user_group_name]])
1690 except KeyError:
1692 except KeyError:
1691 return False
1693 return False
1692 if self.required_perms.issubset(user_perms):
1694 if self.required_perms.issubset(user_perms):
1693 return True
1695 return True
1694 return False
1696 return False
1695
1697
1696
1698
1697 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
1699 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
1698 class HasPermissionAnyMiddleware(object):
1700 class HasPermissionAnyMiddleware(object):
1699 def __init__(self, *perms):
1701 def __init__(self, *perms):
1700 self.required_perms = set(perms)
1702 self.required_perms = set(perms)
1701
1703
1702 def __call__(self, user, repo_name):
1704 def __call__(self, user, repo_name):
1703 # repo_name MUST be unicode, since we handle keys in permission
1705 # repo_name MUST be unicode, since we handle keys in permission
1704 # dict by unicode
1706 # dict by unicode
1705 repo_name = safe_unicode(repo_name)
1707 repo_name = safe_unicode(repo_name)
1706 user = AuthUser(user.user_id)
1708 user = AuthUser(user.user_id)
1707 log.debug(
1709 log.debug(
1708 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
1710 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
1709 self.required_perms, user, repo_name)
1711 self.required_perms, user, repo_name)
1710
1712
1711 if self.check_permissions(user, repo_name):
1713 if self.check_permissions(user, repo_name):
1712 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
1714 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
1713 repo_name, user, 'PermissionMiddleware')
1715 repo_name, user, 'PermissionMiddleware')
1714 return True
1716 return True
1715
1717
1716 else:
1718 else:
1717 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
1719 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
1718 repo_name, user, 'PermissionMiddleware')
1720 repo_name, user, 'PermissionMiddleware')
1719 return False
1721 return False
1720
1722
1721 def check_permissions(self, user, repo_name):
1723 def check_permissions(self, user, repo_name):
1722 perms = user.permissions_with_scope({'repo_name': repo_name})
1724 perms = user.permissions_with_scope({'repo_name': repo_name})
1723
1725
1724 try:
1726 try:
1725 user_perms = set([perms['repositories'][repo_name]])
1727 user_perms = set([perms['repositories'][repo_name]])
1726 except Exception:
1728 except Exception:
1727 log.exception('Error while accessing user permissions')
1729 log.exception('Error while accessing user permissions')
1728 return False
1730 return False
1729
1731
1730 if self.required_perms.intersection(user_perms):
1732 if self.required_perms.intersection(user_perms):
1731 return True
1733 return True
1732 return False
1734 return False
1733
1735
1734
1736
1735 # SPECIAL VERSION TO HANDLE API AUTH
1737 # SPECIAL VERSION TO HANDLE API AUTH
1736 class _BaseApiPerm(object):
1738 class _BaseApiPerm(object):
1737 def __init__(self, *perms):
1739 def __init__(self, *perms):
1738 self.required_perms = set(perms)
1740 self.required_perms = set(perms)
1739
1741
1740 def __call__(self, check_location=None, user=None, repo_name=None,
1742 def __call__(self, check_location=None, user=None, repo_name=None,
1741 group_name=None, user_group_name=None):
1743 group_name=None, user_group_name=None):
1742 cls_name = self.__class__.__name__
1744 cls_name = self.__class__.__name__
1743 check_scope = 'global:%s' % (self.required_perms,)
1745 check_scope = 'global:%s' % (self.required_perms,)
1744 if repo_name:
1746 if repo_name:
1745 check_scope += ', repo_name:%s' % (repo_name,)
1747 check_scope += ', repo_name:%s' % (repo_name,)
1746
1748
1747 if group_name:
1749 if group_name:
1748 check_scope += ', repo_group_name:%s' % (group_name,)
1750 check_scope += ', repo_group_name:%s' % (group_name,)
1749
1751
1750 if user_group_name:
1752 if user_group_name:
1751 check_scope += ', user_group_name:%s' % (user_group_name,)
1753 check_scope += ', user_group_name:%s' % (user_group_name,)
1752
1754
1753 log.debug(
1755 log.debug(
1754 'checking cls:%s %s %s @ %s'
1756 'checking cls:%s %s %s @ %s'
1755 % (cls_name, self.required_perms, check_scope, check_location))
1757 % (cls_name, self.required_perms, check_scope, check_location))
1756 if not user:
1758 if not user:
1757 log.debug('Empty User passed into arguments')
1759 log.debug('Empty User passed into arguments')
1758 return False
1760 return False
1759
1761
1760 # process user
1762 # process user
1761 if not isinstance(user, AuthUser):
1763 if not isinstance(user, AuthUser):
1762 user = AuthUser(user.user_id)
1764 user = AuthUser(user.user_id)
1763 if not check_location:
1765 if not check_location:
1764 check_location = 'unspecified'
1766 check_location = 'unspecified'
1765 if self.check_permissions(user.permissions, repo_name, group_name,
1767 if self.check_permissions(user.permissions, repo_name, group_name,
1766 user_group_name):
1768 user_group_name):
1767 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1769 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1768 check_scope, user, check_location)
1770 check_scope, user, check_location)
1769 return True
1771 return True
1770
1772
1771 else:
1773 else:
1772 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1774 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1773 check_scope, user, check_location)
1775 check_scope, user, check_location)
1774 return False
1776 return False
1775
1777
1776 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1778 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1777 user_group_name=None):
1779 user_group_name=None):
1778 """
1780 """
1779 implement in child class should return True if permissions are ok,
1781 implement in child class should return True if permissions are ok,
1780 False otherwise
1782 False otherwise
1781
1783
1782 :param perm_defs: dict with permission definitions
1784 :param perm_defs: dict with permission definitions
1783 :param repo_name: repo name
1785 :param repo_name: repo name
1784 """
1786 """
1785 raise NotImplementedError()
1787 raise NotImplementedError()
1786
1788
1787
1789
1788 class HasPermissionAllApi(_BaseApiPerm):
1790 class HasPermissionAllApi(_BaseApiPerm):
1789 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1791 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1790 user_group_name=None):
1792 user_group_name=None):
1791 if self.required_perms.issubset(perm_defs.get('global')):
1793 if self.required_perms.issubset(perm_defs.get('global')):
1792 return True
1794 return True
1793 return False
1795 return False
1794
1796
1795
1797
1796 class HasPermissionAnyApi(_BaseApiPerm):
1798 class HasPermissionAnyApi(_BaseApiPerm):
1797 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1799 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1798 user_group_name=None):
1800 user_group_name=None):
1799 if self.required_perms.intersection(perm_defs.get('global')):
1801 if self.required_perms.intersection(perm_defs.get('global')):
1800 return True
1802 return True
1801 return False
1803 return False
1802
1804
1803
1805
1804 class HasRepoPermissionAllApi(_BaseApiPerm):
1806 class HasRepoPermissionAllApi(_BaseApiPerm):
1805 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1807 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1806 user_group_name=None):
1808 user_group_name=None):
1807 try:
1809 try:
1808 _user_perms = set([perm_defs['repositories'][repo_name]])
1810 _user_perms = set([perm_defs['repositories'][repo_name]])
1809 except KeyError:
1811 except KeyError:
1810 log.warning(traceback.format_exc())
1812 log.warning(traceback.format_exc())
1811 return False
1813 return False
1812 if self.required_perms.issubset(_user_perms):
1814 if self.required_perms.issubset(_user_perms):
1813 return True
1815 return True
1814 return False
1816 return False
1815
1817
1816
1818
1817 class HasRepoPermissionAnyApi(_BaseApiPerm):
1819 class HasRepoPermissionAnyApi(_BaseApiPerm):
1818 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1820 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1819 user_group_name=None):
1821 user_group_name=None):
1820 try:
1822 try:
1821 _user_perms = set([perm_defs['repositories'][repo_name]])
1823 _user_perms = set([perm_defs['repositories'][repo_name]])
1822 except KeyError:
1824 except KeyError:
1823 log.warning(traceback.format_exc())
1825 log.warning(traceback.format_exc())
1824 return False
1826 return False
1825 if self.required_perms.intersection(_user_perms):
1827 if self.required_perms.intersection(_user_perms):
1826 return True
1828 return True
1827 return False
1829 return False
1828
1830
1829
1831
1830 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
1832 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
1831 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1833 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1832 user_group_name=None):
1834 user_group_name=None):
1833 try:
1835 try:
1834 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1836 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1835 except KeyError:
1837 except KeyError:
1836 log.warning(traceback.format_exc())
1838 log.warning(traceback.format_exc())
1837 return False
1839 return False
1838 if self.required_perms.intersection(_user_perms):
1840 if self.required_perms.intersection(_user_perms):
1839 return True
1841 return True
1840 return False
1842 return False
1841
1843
1842
1844
1843 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
1845 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
1844 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1846 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1845 user_group_name=None):
1847 user_group_name=None):
1846 try:
1848 try:
1847 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1849 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1848 except KeyError:
1850 except KeyError:
1849 log.warning(traceback.format_exc())
1851 log.warning(traceback.format_exc())
1850 return False
1852 return False
1851 if self.required_perms.issubset(_user_perms):
1853 if self.required_perms.issubset(_user_perms):
1852 return True
1854 return True
1853 return False
1855 return False
1854
1856
1855
1857
1856 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
1858 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
1857 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1859 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1858 user_group_name=None):
1860 user_group_name=None):
1859 try:
1861 try:
1860 _user_perms = set([perm_defs['user_groups'][user_group_name]])
1862 _user_perms = set([perm_defs['user_groups'][user_group_name]])
1861 except KeyError:
1863 except KeyError:
1862 log.warning(traceback.format_exc())
1864 log.warning(traceback.format_exc())
1863 return False
1865 return False
1864 if self.required_perms.intersection(_user_perms):
1866 if self.required_perms.intersection(_user_perms):
1865 return True
1867 return True
1866 return False
1868 return False
1867
1869
1868
1870
1869 def check_ip_access(source_ip, allowed_ips=None):
1871 def check_ip_access(source_ip, allowed_ips=None):
1870 """
1872 """
1871 Checks if source_ip is a subnet of any of allowed_ips.
1873 Checks if source_ip is a subnet of any of allowed_ips.
1872
1874
1873 :param source_ip:
1875 :param source_ip:
1874 :param allowed_ips: list of allowed ips together with mask
1876 :param allowed_ips: list of allowed ips together with mask
1875 """
1877 """
1876 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
1878 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
1877 source_ip_address = ipaddress.ip_address(source_ip)
1879 source_ip_address = ipaddress.ip_address(source_ip)
1878 if isinstance(allowed_ips, (tuple, list, set)):
1880 if isinstance(allowed_ips, (tuple, list, set)):
1879 for ip in allowed_ips:
1881 for ip in allowed_ips:
1880 try:
1882 try:
1881 network_address = ipaddress.ip_network(ip, strict=False)
1883 network_address = ipaddress.ip_network(ip, strict=False)
1882 if source_ip_address in network_address:
1884 if source_ip_address in network_address:
1883 log.debug('IP %s is network %s' %
1885 log.debug('IP %s is network %s' %
1884 (source_ip_address, network_address))
1886 (source_ip_address, network_address))
1885 return True
1887 return True
1886 # for any case we cannot determine the IP, don't crash just
1888 # for any case we cannot determine the IP, don't crash just
1887 # skip it and log as error, we want to say forbidden still when
1889 # skip it and log as error, we want to say forbidden still when
1888 # sending bad IP
1890 # sending bad IP
1889 except Exception:
1891 except Exception:
1890 log.error(traceback.format_exc())
1892 log.error(traceback.format_exc())
1891 continue
1893 continue
1892 return False
1894 return False
1893
1895
1894
1896
1895 def get_cython_compat_decorator(wrapper, func):
1897 def get_cython_compat_decorator(wrapper, func):
1896 """
1898 """
1897 Creates a cython compatible decorator. The previously used
1899 Creates a cython compatible decorator. The previously used
1898 decorator.decorator() function seems to be incompatible with cython.
1900 decorator.decorator() function seems to be incompatible with cython.
1899
1901
1900 :param wrapper: __wrapper method of the decorator class
1902 :param wrapper: __wrapper method of the decorator class
1901 :param func: decorated function
1903 :param func: decorated function
1902 """
1904 """
1903 @wraps(func)
1905 @wraps(func)
1904 def local_wrapper(*args, **kwds):
1906 def local_wrapper(*args, **kwds):
1905 return wrapper(func, *args, **kwds)
1907 return wrapper(func, *args, **kwds)
1906 local_wrapper.__wrapped__ = func
1908 local_wrapper.__wrapped__ = func
1907 return local_wrapper
1909 return local_wrapper
@@ -1,3861 +1,3908 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37
37
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.ext.declarative import declared_attr
39 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.hybrid import hybrid_property
40 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.orm import (
41 from sqlalchemy.orm import (
42 relationship, joinedload, class_mapper, validates, aliased)
42 relationship, joinedload, class_mapper, validates, aliased)
43 from sqlalchemy.sql.expression import true
43 from sqlalchemy.sql.expression import true
44 from beaker.cache import cache_region
44 from beaker.cache import cache_region
45 from webob.exc import HTTPNotFound
45 from webob.exc import HTTPNotFound
46 from zope.cachedescriptors.property import Lazy as LazyProperty
46 from zope.cachedescriptors.property import Lazy as LazyProperty
47
47
48 from pylons import url
48 from pylons import url
49 from pylons.i18n.translation import lazy_ugettext as _
49 from pylons.i18n.translation import lazy_ugettext as _
50
50
51 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs import get_vcs_instance
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
53 from rhodecode.lib.utils2 import (
53 from rhodecode.lib.utils2 import (
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
56 glob2re, StrictAttributeDict)
56 glob2re, StrictAttributeDict)
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
58 from rhodecode.lib.ext_json import json
58 from rhodecode.lib.ext_json import json
59 from rhodecode.lib.caching_query import FromCache
59 from rhodecode.lib.caching_query import FromCache
60 from rhodecode.lib.encrypt import AESCipher
60 from rhodecode.lib.encrypt import AESCipher
61
61
62 from rhodecode.model.meta import Base, Session
62 from rhodecode.model.meta import Base, Session
63
63
64 URL_SEP = '/'
64 URL_SEP = '/'
65 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
66
66
67 # =============================================================================
67 # =============================================================================
68 # BASE CLASSES
68 # BASE CLASSES
69 # =============================================================================
69 # =============================================================================
70
70
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
72 # beaker.session.secret if first is not set.
72 # beaker.session.secret if first is not set.
73 # and initialized at environment.py
73 # and initialized at environment.py
74 ENCRYPTION_KEY = None
74 ENCRYPTION_KEY = None
75
75
76 # used to sort permissions by types, '#' used here is not allowed to be in
76 # used to sort permissions by types, '#' used here is not allowed to be in
77 # usernames, and it's very early in sorted string.printable table.
77 # usernames, and it's very early in sorted string.printable table.
78 PERMISSION_TYPE_SORT = {
78 PERMISSION_TYPE_SORT = {
79 'admin': '####',
79 'admin': '####',
80 'write': '###',
80 'write': '###',
81 'read': '##',
81 'read': '##',
82 'none': '#',
82 'none': '#',
83 }
83 }
84
84
85
85
86 def display_sort(obj):
86 def display_sort(obj):
87 """
87 """
88 Sort function used to sort permissions in .permissions() function of
88 Sort function used to sort permissions in .permissions() function of
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
90 of all other resources
90 of all other resources
91 """
91 """
92
92
93 if obj.username == User.DEFAULT_USER:
93 if obj.username == User.DEFAULT_USER:
94 return '#####'
94 return '#####'
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
96 return prefix + obj.username
96 return prefix + obj.username
97
97
98
98
99 def _hash_key(k):
99 def _hash_key(k):
100 return md5_safe(k)
100 return md5_safe(k)
101
101
102
102
103 class EncryptedTextValue(TypeDecorator):
103 class EncryptedTextValue(TypeDecorator):
104 """
104 """
105 Special column for encrypted long text data, use like::
105 Special column for encrypted long text data, use like::
106
106
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
108
108
109 This column is intelligent so if value is in unencrypted form it return
109 This column is intelligent so if value is in unencrypted form it return
110 unencrypted form, but on save it always encrypts
110 unencrypted form, but on save it always encrypts
111 """
111 """
112 impl = Text
112 impl = Text
113
113
114 def process_bind_param(self, value, dialect):
114 def process_bind_param(self, value, dialect):
115 if not value:
115 if not value:
116 return value
116 return value
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
118 # protect against double encrypting if someone manually starts
118 # protect against double encrypting if someone manually starts
119 # doing
119 # doing
120 raise ValueError('value needs to be in unencrypted format, ie. '
120 raise ValueError('value needs to be in unencrypted format, ie. '
121 'not starting with enc$aes')
121 'not starting with enc$aes')
122 return 'enc$aes_hmac$%s' % AESCipher(
122 return 'enc$aes_hmac$%s' % AESCipher(
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
124
124
125 def process_result_value(self, value, dialect):
125 def process_result_value(self, value, dialect):
126 import rhodecode
126 import rhodecode
127
127
128 if not value:
128 if not value:
129 return value
129 return value
130
130
131 parts = value.split('$', 3)
131 parts = value.split('$', 3)
132 if not len(parts) == 3:
132 if not len(parts) == 3:
133 # probably not encrypted values
133 # probably not encrypted values
134 return value
134 return value
135 else:
135 else:
136 if parts[0] != 'enc':
136 if parts[0] != 'enc':
137 # parts ok but without our header ?
137 # parts ok but without our header ?
138 return value
138 return value
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
140 'rhodecode.encrypted_values.strict') or True)
140 'rhodecode.encrypted_values.strict') or True)
141 # at that stage we know it's our encryption
141 # at that stage we know it's our encryption
142 if parts[1] == 'aes':
142 if parts[1] == 'aes':
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
144 elif parts[1] == 'aes_hmac':
144 elif parts[1] == 'aes_hmac':
145 decrypted_data = AESCipher(
145 decrypted_data = AESCipher(
146 ENCRYPTION_KEY, hmac=True,
146 ENCRYPTION_KEY, hmac=True,
147 strict_verification=enc_strict_mode).decrypt(parts[2])
147 strict_verification=enc_strict_mode).decrypt(parts[2])
148 else:
148 else:
149 raise ValueError(
149 raise ValueError(
150 'Encryption type part is wrong, must be `aes` '
150 'Encryption type part is wrong, must be `aes` '
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
152 return decrypted_data
152 return decrypted_data
153
153
154
154
155 class BaseModel(object):
155 class BaseModel(object):
156 """
156 """
157 Base Model for all classes
157 Base Model for all classes
158 """
158 """
159
159
160 @classmethod
160 @classmethod
161 def _get_keys(cls):
161 def _get_keys(cls):
162 """return column names for this model """
162 """return column names for this model """
163 return class_mapper(cls).c.keys()
163 return class_mapper(cls).c.keys()
164
164
165 def get_dict(self):
165 def get_dict(self):
166 """
166 """
167 return dict with keys and values corresponding
167 return dict with keys and values corresponding
168 to this model data """
168 to this model data """
169
169
170 d = {}
170 d = {}
171 for k in self._get_keys():
171 for k in self._get_keys():
172 d[k] = getattr(self, k)
172 d[k] = getattr(self, k)
173
173
174 # also use __json__() if present to get additional fields
174 # also use __json__() if present to get additional fields
175 _json_attr = getattr(self, '__json__', None)
175 _json_attr = getattr(self, '__json__', None)
176 if _json_attr:
176 if _json_attr:
177 # update with attributes from __json__
177 # update with attributes from __json__
178 if callable(_json_attr):
178 if callable(_json_attr):
179 _json_attr = _json_attr()
179 _json_attr = _json_attr()
180 for k, val in _json_attr.iteritems():
180 for k, val in _json_attr.iteritems():
181 d[k] = val
181 d[k] = val
182 return d
182 return d
183
183
184 def get_appstruct(self):
184 def get_appstruct(self):
185 """return list with keys and values tuples corresponding
185 """return list with keys and values tuples corresponding
186 to this model data """
186 to this model data """
187
187
188 l = []
188 l = []
189 for k in self._get_keys():
189 for k in self._get_keys():
190 l.append((k, getattr(self, k),))
190 l.append((k, getattr(self, k),))
191 return l
191 return l
192
192
193 def populate_obj(self, populate_dict):
193 def populate_obj(self, populate_dict):
194 """populate model with data from given populate_dict"""
194 """populate model with data from given populate_dict"""
195
195
196 for k in self._get_keys():
196 for k in self._get_keys():
197 if k in populate_dict:
197 if k in populate_dict:
198 setattr(self, k, populate_dict[k])
198 setattr(self, k, populate_dict[k])
199
199
200 @classmethod
200 @classmethod
201 def query(cls):
201 def query(cls):
202 return Session().query(cls)
202 return Session().query(cls)
203
203
204 @classmethod
204 @classmethod
205 def get(cls, id_):
205 def get(cls, id_):
206 if id_:
206 if id_:
207 return cls.query().get(id_)
207 return cls.query().get(id_)
208
208
209 @classmethod
209 @classmethod
210 def get_or_404(cls, id_):
210 def get_or_404(cls, id_):
211 try:
211 try:
212 id_ = int(id_)
212 id_ = int(id_)
213 except (TypeError, ValueError):
213 except (TypeError, ValueError):
214 raise HTTPNotFound
214 raise HTTPNotFound
215
215
216 res = cls.query().get(id_)
216 res = cls.query().get(id_)
217 if not res:
217 if not res:
218 raise HTTPNotFound
218 raise HTTPNotFound
219 return res
219 return res
220
220
221 @classmethod
221 @classmethod
222 def getAll(cls):
222 def getAll(cls):
223 # deprecated and left for backward compatibility
223 # deprecated and left for backward compatibility
224 return cls.get_all()
224 return cls.get_all()
225
225
226 @classmethod
226 @classmethod
227 def get_all(cls):
227 def get_all(cls):
228 return cls.query().all()
228 return cls.query().all()
229
229
230 @classmethod
230 @classmethod
231 def delete(cls, id_):
231 def delete(cls, id_):
232 obj = cls.query().get(id_)
232 obj = cls.query().get(id_)
233 Session().delete(obj)
233 Session().delete(obj)
234
234
235 @classmethod
235 @classmethod
236 def identity_cache(cls, session, attr_name, value):
236 def identity_cache(cls, session, attr_name, value):
237 exist_in_session = []
237 exist_in_session = []
238 for (item_cls, pkey), instance in session.identity_map.items():
238 for (item_cls, pkey), instance in session.identity_map.items():
239 if cls == item_cls and getattr(instance, attr_name) == value:
239 if cls == item_cls and getattr(instance, attr_name) == value:
240 exist_in_session.append(instance)
240 exist_in_session.append(instance)
241 if exist_in_session:
241 if exist_in_session:
242 if len(exist_in_session) == 1:
242 if len(exist_in_session) == 1:
243 return exist_in_session[0]
243 return exist_in_session[0]
244 log.exception(
244 log.exception(
245 'multiple objects with attr %s and '
245 'multiple objects with attr %s and '
246 'value %s found with same name: %r',
246 'value %s found with same name: %r',
247 attr_name, value, exist_in_session)
247 attr_name, value, exist_in_session)
248
248
249 def __repr__(self):
249 def __repr__(self):
250 if hasattr(self, '__unicode__'):
250 if hasattr(self, '__unicode__'):
251 # python repr needs to return str
251 # python repr needs to return str
252 try:
252 try:
253 return safe_str(self.__unicode__())
253 return safe_str(self.__unicode__())
254 except UnicodeDecodeError:
254 except UnicodeDecodeError:
255 pass
255 pass
256 return '<DB:%s>' % (self.__class__.__name__)
256 return '<DB:%s>' % (self.__class__.__name__)
257
257
258
258
259 class RhodeCodeSetting(Base, BaseModel):
259 class RhodeCodeSetting(Base, BaseModel):
260 __tablename__ = 'rhodecode_settings'
260 __tablename__ = 'rhodecode_settings'
261 __table_args__ = (
261 __table_args__ = (
262 UniqueConstraint('app_settings_name'),
262 UniqueConstraint('app_settings_name'),
263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
265 )
265 )
266
266
267 SETTINGS_TYPES = {
267 SETTINGS_TYPES = {
268 'str': safe_str,
268 'str': safe_str,
269 'int': safe_int,
269 'int': safe_int,
270 'unicode': safe_unicode,
270 'unicode': safe_unicode,
271 'bool': str2bool,
271 'bool': str2bool,
272 'list': functools.partial(aslist, sep=',')
272 'list': functools.partial(aslist, sep=',')
273 }
273 }
274 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
274 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
275 GLOBAL_CONF_KEY = 'app_settings'
275 GLOBAL_CONF_KEY = 'app_settings'
276
276
277 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
277 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
278 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
278 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
279 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
279 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
280 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
280 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
281
281
282 def __init__(self, key='', val='', type='unicode'):
282 def __init__(self, key='', val='', type='unicode'):
283 self.app_settings_name = key
283 self.app_settings_name = key
284 self.app_settings_type = type
284 self.app_settings_type = type
285 self.app_settings_value = val
285 self.app_settings_value = val
286
286
287 @validates('_app_settings_value')
287 @validates('_app_settings_value')
288 def validate_settings_value(self, key, val):
288 def validate_settings_value(self, key, val):
289 assert type(val) == unicode
289 assert type(val) == unicode
290 return val
290 return val
291
291
292 @hybrid_property
292 @hybrid_property
293 def app_settings_value(self):
293 def app_settings_value(self):
294 v = self._app_settings_value
294 v = self._app_settings_value
295 _type = self.app_settings_type
295 _type = self.app_settings_type
296 if _type:
296 if _type:
297 _type = self.app_settings_type.split('.')[0]
297 _type = self.app_settings_type.split('.')[0]
298 # decode the encrypted value
298 # decode the encrypted value
299 if 'encrypted' in self.app_settings_type:
299 if 'encrypted' in self.app_settings_type:
300 cipher = EncryptedTextValue()
300 cipher = EncryptedTextValue()
301 v = safe_unicode(cipher.process_result_value(v, None))
301 v = safe_unicode(cipher.process_result_value(v, None))
302
302
303 converter = self.SETTINGS_TYPES.get(_type) or \
303 converter = self.SETTINGS_TYPES.get(_type) or \
304 self.SETTINGS_TYPES['unicode']
304 self.SETTINGS_TYPES['unicode']
305 return converter(v)
305 return converter(v)
306
306
307 @app_settings_value.setter
307 @app_settings_value.setter
308 def app_settings_value(self, val):
308 def app_settings_value(self, val):
309 """
309 """
310 Setter that will always make sure we use unicode in app_settings_value
310 Setter that will always make sure we use unicode in app_settings_value
311
311
312 :param val:
312 :param val:
313 """
313 """
314 val = safe_unicode(val)
314 val = safe_unicode(val)
315 # encode the encrypted value
315 # encode the encrypted value
316 if 'encrypted' in self.app_settings_type:
316 if 'encrypted' in self.app_settings_type:
317 cipher = EncryptedTextValue()
317 cipher = EncryptedTextValue()
318 val = safe_unicode(cipher.process_bind_param(val, None))
318 val = safe_unicode(cipher.process_bind_param(val, None))
319 self._app_settings_value = val
319 self._app_settings_value = val
320
320
321 @hybrid_property
321 @hybrid_property
322 def app_settings_type(self):
322 def app_settings_type(self):
323 return self._app_settings_type
323 return self._app_settings_type
324
324
325 @app_settings_type.setter
325 @app_settings_type.setter
326 def app_settings_type(self, val):
326 def app_settings_type(self, val):
327 if val.split('.')[0] not in self.SETTINGS_TYPES:
327 if val.split('.')[0] not in self.SETTINGS_TYPES:
328 raise Exception('type must be one of %s got %s'
328 raise Exception('type must be one of %s got %s'
329 % (self.SETTINGS_TYPES.keys(), val))
329 % (self.SETTINGS_TYPES.keys(), val))
330 self._app_settings_type = val
330 self._app_settings_type = val
331
331
332 def __unicode__(self):
332 def __unicode__(self):
333 return u"<%s('%s:%s[%s]')>" % (
333 return u"<%s('%s:%s[%s]')>" % (
334 self.__class__.__name__,
334 self.__class__.__name__,
335 self.app_settings_name, self.app_settings_value,
335 self.app_settings_name, self.app_settings_value,
336 self.app_settings_type
336 self.app_settings_type
337 )
337 )
338
338
339
339
340 class RhodeCodeUi(Base, BaseModel):
340 class RhodeCodeUi(Base, BaseModel):
341 __tablename__ = 'rhodecode_ui'
341 __tablename__ = 'rhodecode_ui'
342 __table_args__ = (
342 __table_args__ = (
343 UniqueConstraint('ui_key'),
343 UniqueConstraint('ui_key'),
344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
346 )
346 )
347
347
348 HOOK_REPO_SIZE = 'changegroup.repo_size'
348 HOOK_REPO_SIZE = 'changegroup.repo_size'
349 # HG
349 # HG
350 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
350 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
351 HOOK_PULL = 'outgoing.pull_logger'
351 HOOK_PULL = 'outgoing.pull_logger'
352 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
352 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
353 HOOK_PUSH = 'changegroup.push_logger'
353 HOOK_PUSH = 'changegroup.push_logger'
354
354
355 # TODO: johbo: Unify way how hooks are configured for git and hg,
355 # TODO: johbo: Unify way how hooks are configured for git and hg,
356 # git part is currently hardcoded.
356 # git part is currently hardcoded.
357
357
358 # SVN PATTERNS
358 # SVN PATTERNS
359 SVN_BRANCH_ID = 'vcs_svn_branch'
359 SVN_BRANCH_ID = 'vcs_svn_branch'
360 SVN_TAG_ID = 'vcs_svn_tag'
360 SVN_TAG_ID = 'vcs_svn_tag'
361
361
362 ui_id = Column(
362 ui_id = Column(
363 "ui_id", Integer(), nullable=False, unique=True, default=None,
363 "ui_id", Integer(), nullable=False, unique=True, default=None,
364 primary_key=True)
364 primary_key=True)
365 ui_section = Column(
365 ui_section = Column(
366 "ui_section", String(255), nullable=True, unique=None, default=None)
366 "ui_section", String(255), nullable=True, unique=None, default=None)
367 ui_key = Column(
367 ui_key = Column(
368 "ui_key", String(255), nullable=True, unique=None, default=None)
368 "ui_key", String(255), nullable=True, unique=None, default=None)
369 ui_value = Column(
369 ui_value = Column(
370 "ui_value", String(255), nullable=True, unique=None, default=None)
370 "ui_value", String(255), nullable=True, unique=None, default=None)
371 ui_active = Column(
371 ui_active = Column(
372 "ui_active", Boolean(), nullable=True, unique=None, default=True)
372 "ui_active", Boolean(), nullable=True, unique=None, default=True)
373
373
374 def __repr__(self):
374 def __repr__(self):
375 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
375 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
376 self.ui_key, self.ui_value)
376 self.ui_key, self.ui_value)
377
377
378
378
379 class RepoRhodeCodeSetting(Base, BaseModel):
379 class RepoRhodeCodeSetting(Base, BaseModel):
380 __tablename__ = 'repo_rhodecode_settings'
380 __tablename__ = 'repo_rhodecode_settings'
381 __table_args__ = (
381 __table_args__ = (
382 UniqueConstraint(
382 UniqueConstraint(
383 'app_settings_name', 'repository_id',
383 'app_settings_name', 'repository_id',
384 name='uq_repo_rhodecode_setting_name_repo_id'),
384 name='uq_repo_rhodecode_setting_name_repo_id'),
385 {'extend_existing': True, 'mysql_engine': 'InnoDB',
385 {'extend_existing': True, 'mysql_engine': 'InnoDB',
386 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
386 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
387 )
387 )
388
388
389 repository_id = Column(
389 repository_id = Column(
390 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
390 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
391 nullable=False)
391 nullable=False)
392 app_settings_id = Column(
392 app_settings_id = Column(
393 "app_settings_id", Integer(), nullable=False, unique=True,
393 "app_settings_id", Integer(), nullable=False, unique=True,
394 default=None, primary_key=True)
394 default=None, primary_key=True)
395 app_settings_name = Column(
395 app_settings_name = Column(
396 "app_settings_name", String(255), nullable=True, unique=None,
396 "app_settings_name", String(255), nullable=True, unique=None,
397 default=None)
397 default=None)
398 _app_settings_value = Column(
398 _app_settings_value = Column(
399 "app_settings_value", String(4096), nullable=True, unique=None,
399 "app_settings_value", String(4096), nullable=True, unique=None,
400 default=None)
400 default=None)
401 _app_settings_type = Column(
401 _app_settings_type = Column(
402 "app_settings_type", String(255), nullable=True, unique=None,
402 "app_settings_type", String(255), nullable=True, unique=None,
403 default=None)
403 default=None)
404
404
405 repository = relationship('Repository')
405 repository = relationship('Repository')
406
406
407 def __init__(self, repository_id, key='', val='', type='unicode'):
407 def __init__(self, repository_id, key='', val='', type='unicode'):
408 self.repository_id = repository_id
408 self.repository_id = repository_id
409 self.app_settings_name = key
409 self.app_settings_name = key
410 self.app_settings_type = type
410 self.app_settings_type = type
411 self.app_settings_value = val
411 self.app_settings_value = val
412
412
413 @validates('_app_settings_value')
413 @validates('_app_settings_value')
414 def validate_settings_value(self, key, val):
414 def validate_settings_value(self, key, val):
415 assert type(val) == unicode
415 assert type(val) == unicode
416 return val
416 return val
417
417
418 @hybrid_property
418 @hybrid_property
419 def app_settings_value(self):
419 def app_settings_value(self):
420 v = self._app_settings_value
420 v = self._app_settings_value
421 type_ = self.app_settings_type
421 type_ = self.app_settings_type
422 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
422 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
423 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
423 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
424 return converter(v)
424 return converter(v)
425
425
426 @app_settings_value.setter
426 @app_settings_value.setter
427 def app_settings_value(self, val):
427 def app_settings_value(self, val):
428 """
428 """
429 Setter that will always make sure we use unicode in app_settings_value
429 Setter that will always make sure we use unicode in app_settings_value
430
430
431 :param val:
431 :param val:
432 """
432 """
433 self._app_settings_value = safe_unicode(val)
433 self._app_settings_value = safe_unicode(val)
434
434
435 @hybrid_property
435 @hybrid_property
436 def app_settings_type(self):
436 def app_settings_type(self):
437 return self._app_settings_type
437 return self._app_settings_type
438
438
439 @app_settings_type.setter
439 @app_settings_type.setter
440 def app_settings_type(self, val):
440 def app_settings_type(self, val):
441 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
441 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
442 if val not in SETTINGS_TYPES:
442 if val not in SETTINGS_TYPES:
443 raise Exception('type must be one of %s got %s'
443 raise Exception('type must be one of %s got %s'
444 % (SETTINGS_TYPES.keys(), val))
444 % (SETTINGS_TYPES.keys(), val))
445 self._app_settings_type = val
445 self._app_settings_type = val
446
446
447 def __unicode__(self):
447 def __unicode__(self):
448 return u"<%s('%s:%s:%s[%s]')>" % (
448 return u"<%s('%s:%s:%s[%s]')>" % (
449 self.__class__.__name__, self.repository.repo_name,
449 self.__class__.__name__, self.repository.repo_name,
450 self.app_settings_name, self.app_settings_value,
450 self.app_settings_name, self.app_settings_value,
451 self.app_settings_type
451 self.app_settings_type
452 )
452 )
453
453
454
454
455 class RepoRhodeCodeUi(Base, BaseModel):
455 class RepoRhodeCodeUi(Base, BaseModel):
456 __tablename__ = 'repo_rhodecode_ui'
456 __tablename__ = 'repo_rhodecode_ui'
457 __table_args__ = (
457 __table_args__ = (
458 UniqueConstraint(
458 UniqueConstraint(
459 'repository_id', 'ui_section', 'ui_key',
459 'repository_id', 'ui_section', 'ui_key',
460 name='uq_repo_rhodecode_ui_repository_id_section_key'),
460 name='uq_repo_rhodecode_ui_repository_id_section_key'),
461 {'extend_existing': True, 'mysql_engine': 'InnoDB',
461 {'extend_existing': True, 'mysql_engine': 'InnoDB',
462 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
462 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
463 )
463 )
464
464
465 repository_id = Column(
465 repository_id = Column(
466 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
466 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
467 nullable=False)
467 nullable=False)
468 ui_id = Column(
468 ui_id = Column(
469 "ui_id", Integer(), nullable=False, unique=True, default=None,
469 "ui_id", Integer(), nullable=False, unique=True, default=None,
470 primary_key=True)
470 primary_key=True)
471 ui_section = Column(
471 ui_section = Column(
472 "ui_section", String(255), nullable=True, unique=None, default=None)
472 "ui_section", String(255), nullable=True, unique=None, default=None)
473 ui_key = Column(
473 ui_key = Column(
474 "ui_key", String(255), nullable=True, unique=None, default=None)
474 "ui_key", String(255), nullable=True, unique=None, default=None)
475 ui_value = Column(
475 ui_value = Column(
476 "ui_value", String(255), nullable=True, unique=None, default=None)
476 "ui_value", String(255), nullable=True, unique=None, default=None)
477 ui_active = Column(
477 ui_active = Column(
478 "ui_active", Boolean(), nullable=True, unique=None, default=True)
478 "ui_active", Boolean(), nullable=True, unique=None, default=True)
479
479
480 repository = relationship('Repository')
480 repository = relationship('Repository')
481
481
482 def __repr__(self):
482 def __repr__(self):
483 return '<%s[%s:%s]%s=>%s]>' % (
483 return '<%s[%s:%s]%s=>%s]>' % (
484 self.__class__.__name__, self.repository.repo_name,
484 self.__class__.__name__, self.repository.repo_name,
485 self.ui_section, self.ui_key, self.ui_value)
485 self.ui_section, self.ui_key, self.ui_value)
486
486
487
487
488 class User(Base, BaseModel):
488 class User(Base, BaseModel):
489 __tablename__ = 'users'
489 __tablename__ = 'users'
490 __table_args__ = (
490 __table_args__ = (
491 UniqueConstraint('username'), UniqueConstraint('email'),
491 UniqueConstraint('username'), UniqueConstraint('email'),
492 Index('u_username_idx', 'username'),
492 Index('u_username_idx', 'username'),
493 Index('u_email_idx', 'email'),
493 Index('u_email_idx', 'email'),
494 {'extend_existing': True, 'mysql_engine': 'InnoDB',
494 {'extend_existing': True, 'mysql_engine': 'InnoDB',
495 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
495 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
496 )
496 )
497 DEFAULT_USER = 'default'
497 DEFAULT_USER = 'default'
498 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
498 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
499 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
499 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
500
500
501 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
501 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
502 username = Column("username", String(255), nullable=True, unique=None, default=None)
502 username = Column("username", String(255), nullable=True, unique=None, default=None)
503 password = Column("password", String(255), nullable=True, unique=None, default=None)
503 password = Column("password", String(255), nullable=True, unique=None, default=None)
504 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
504 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
505 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
505 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
506 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
506 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
507 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
507 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
508 _email = Column("email", String(255), nullable=True, unique=None, default=None)
508 _email = Column("email", String(255), nullable=True, unique=None, default=None)
509 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
509 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
510 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
510 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
511 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
511 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
512 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
512 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
513 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
513 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
514 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
514 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
515 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
515 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
516
516
517 user_log = relationship('UserLog')
517 user_log = relationship('UserLog')
518 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
518 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
519
519
520 repositories = relationship('Repository')
520 repositories = relationship('Repository')
521 repository_groups = relationship('RepoGroup')
521 repository_groups = relationship('RepoGroup')
522 user_groups = relationship('UserGroup')
522 user_groups = relationship('UserGroup')
523
523
524 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
524 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
525 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
525 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
526
526
527 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
527 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
528 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
528 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
529 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
529 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
530
530
531 group_member = relationship('UserGroupMember', cascade='all')
531 group_member = relationship('UserGroupMember', cascade='all')
532
532
533 notifications = relationship('UserNotification', cascade='all')
533 notifications = relationship('UserNotification', cascade='all')
534 # notifications assigned to this user
534 # notifications assigned to this user
535 user_created_notifications = relationship('Notification', cascade='all')
535 user_created_notifications = relationship('Notification', cascade='all')
536 # comments created by this user
536 # comments created by this user
537 user_comments = relationship('ChangesetComment', cascade='all')
537 user_comments = relationship('ChangesetComment', cascade='all')
538 # user profile extra info
538 # user profile extra info
539 user_emails = relationship('UserEmailMap', cascade='all')
539 user_emails = relationship('UserEmailMap', cascade='all')
540 user_ip_map = relationship('UserIpMap', cascade='all')
540 user_ip_map = relationship('UserIpMap', cascade='all')
541 user_auth_tokens = relationship('UserApiKeys', cascade='all')
541 user_auth_tokens = relationship('UserApiKeys', cascade='all')
542 # gists
542 # gists
543 user_gists = relationship('Gist', cascade='all')
543 user_gists = relationship('Gist', cascade='all')
544 # user pull requests
544 # user pull requests
545 user_pull_requests = relationship('PullRequest', cascade='all')
545 user_pull_requests = relationship('PullRequest', cascade='all')
546 # external identities
546 # external identities
547 extenal_identities = relationship(
547 extenal_identities = relationship(
548 'ExternalIdentity',
548 'ExternalIdentity',
549 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
549 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
550 cascade='all')
550 cascade='all')
551
551
552 def __unicode__(self):
552 def __unicode__(self):
553 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
553 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
554 self.user_id, self.username)
554 self.user_id, self.username)
555
555
556 @hybrid_property
556 @hybrid_property
557 def email(self):
557 def email(self):
558 return self._email
558 return self._email
559
559
560 @email.setter
560 @email.setter
561 def email(self, val):
561 def email(self, val):
562 self._email = val.lower() if val else None
562 self._email = val.lower() if val else None
563
563
564 @property
564 @property
565 def firstname(self):
565 def firstname(self):
566 # alias for future
566 # alias for future
567 return self.name
567 return self.name
568
568
569 @property
569 @property
570 def emails(self):
570 def emails(self):
571 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
571 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
572 return [self.email] + [x.email for x in other]
572 return [self.email] + [x.email for x in other]
573
573
574 @property
574 @property
575 def auth_tokens(self):
575 def auth_tokens(self):
576 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
576 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
577
577
578 @property
578 @property
579 def extra_auth_tokens(self):
579 def extra_auth_tokens(self):
580 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
580 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
581
581
582 @property
582 @property
583 def feed_token(self):
583 def feed_token(self):
584 return self.get_feed_token()
585
586 def get_feed_token(self):
584 feed_tokens = UserApiKeys.query()\
587 feed_tokens = UserApiKeys.query()\
585 .filter(UserApiKeys.user == self)\
588 .filter(UserApiKeys.user == self)\
586 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
589 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
587 .all()
590 .all()
588 if feed_tokens:
591 if feed_tokens:
589 return feed_tokens[0].api_key
592 return feed_tokens[0].api_key
590 else:
593 return 'NO_FEED_TOKEN_AVAILABLE'
591 # use the main token so we don't end up with nothing...
592 return self.api_key
593
594
594 @classmethod
595 @classmethod
595 def extra_valid_auth_tokens(cls, user, role=None):
596 def extra_valid_auth_tokens(cls, user, role=None):
596 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
597 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
597 .filter(or_(UserApiKeys.expires == -1,
598 .filter(or_(UserApiKeys.expires == -1,
598 UserApiKeys.expires >= time.time()))
599 UserApiKeys.expires >= time.time()))
599 if role:
600 if role:
600 tokens = tokens.filter(or_(UserApiKeys.role == role,
601 tokens = tokens.filter(or_(UserApiKeys.role == role,
601 UserApiKeys.role == UserApiKeys.ROLE_ALL))
602 UserApiKeys.role == UserApiKeys.ROLE_ALL))
602 return tokens.all()
603 return tokens.all()
603
604
605 def authenticate_by_token(self, auth_token, roles=None,
606 include_builtin_token=False):
607 from rhodecode.lib import auth
608
609 log.debug('Trying to authenticate user: %s via auth-token, '
610 'and roles: %s', self, roles)
611
612 if not auth_token:
613 return False
614
615 crypto_backend = auth.crypto_backend()
616
617 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
618 tokens_q = UserApiKeys.query()\
619 .filter(UserApiKeys.user_id == self.user_id)\
620 .filter(or_(UserApiKeys.expires == -1,
621 UserApiKeys.expires >= time.time()))
622
623 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
624
625 maybe_builtin = []
626 if include_builtin_token:
627 maybe_builtin = [AttributeDict({'api_key': self.api_key})]
628
629 plain_tokens = []
630 hash_tokens = []
631
632 for token in tokens_q.all() + maybe_builtin:
633 if token.api_key.startswith(crypto_backend.ENC_PREF):
634 hash_tokens.append(token.api_key)
635 else:
636 plain_tokens.append(token.api_key)
637
638 is_plain_match = auth_token in plain_tokens
639 if is_plain_match:
640 return True
641
642 for hashed in hash_tokens:
643 # marcink: this is expensive to calculate, but the most secure
644 match = crypto_backend.hash_check(auth_token, hashed)
645 if match:
646 return True
647
648 return False
649
604 @property
650 @property
605 def builtin_token_roles(self):
651 def builtin_token_roles(self):
606 return map(UserApiKeys._get_role_name, [
652 roles = [
607 UserApiKeys.ROLE_API, UserApiKeys.ROLE_FEED, UserApiKeys.ROLE_HTTP
653 UserApiKeys.ROLE_API, UserApiKeys.ROLE_FEED, UserApiKeys.ROLE_HTTP
608 ])
654 ]
655 return map(UserApiKeys._get_role_name, roles)
609
656
610 @property
657 @property
611 def ip_addresses(self):
658 def ip_addresses(self):
612 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
659 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
613 return [x.ip_addr for x in ret]
660 return [x.ip_addr for x in ret]
614
661
615 @property
662 @property
616 def username_and_name(self):
663 def username_and_name(self):
617 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
664 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
618
665
619 @property
666 @property
620 def username_or_name_or_email(self):
667 def username_or_name_or_email(self):
621 full_name = self.full_name if self.full_name is not ' ' else None
668 full_name = self.full_name if self.full_name is not ' ' else None
622 return self.username or full_name or self.email
669 return self.username or full_name or self.email
623
670
624 @property
671 @property
625 def full_name(self):
672 def full_name(self):
626 return '%s %s' % (self.firstname, self.lastname)
673 return '%s %s' % (self.firstname, self.lastname)
627
674
628 @property
675 @property
629 def full_name_or_username(self):
676 def full_name_or_username(self):
630 return ('%s %s' % (self.firstname, self.lastname)
677 return ('%s %s' % (self.firstname, self.lastname)
631 if (self.firstname and self.lastname) else self.username)
678 if (self.firstname and self.lastname) else self.username)
632
679
633 @property
680 @property
634 def full_contact(self):
681 def full_contact(self):
635 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
682 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
636
683
637 @property
684 @property
638 def short_contact(self):
685 def short_contact(self):
639 return '%s %s' % (self.firstname, self.lastname)
686 return '%s %s' % (self.firstname, self.lastname)
640
687
641 @property
688 @property
642 def is_admin(self):
689 def is_admin(self):
643 return self.admin
690 return self.admin
644
691
645 @property
692 @property
646 def AuthUser(self):
693 def AuthUser(self):
647 """
694 """
648 Returns instance of AuthUser for this user
695 Returns instance of AuthUser for this user
649 """
696 """
650 from rhodecode.lib.auth import AuthUser
697 from rhodecode.lib.auth import AuthUser
651 return AuthUser(user_id=self.user_id, api_key=self.api_key,
698 return AuthUser(user_id=self.user_id, api_key=self.api_key,
652 username=self.username)
699 username=self.username)
653
700
654 @hybrid_property
701 @hybrid_property
655 def user_data(self):
702 def user_data(self):
656 if not self._user_data:
703 if not self._user_data:
657 return {}
704 return {}
658
705
659 try:
706 try:
660 return json.loads(self._user_data)
707 return json.loads(self._user_data)
661 except TypeError:
708 except TypeError:
662 return {}
709 return {}
663
710
664 @user_data.setter
711 @user_data.setter
665 def user_data(self, val):
712 def user_data(self, val):
666 if not isinstance(val, dict):
713 if not isinstance(val, dict):
667 raise Exception('user_data must be dict, got %s' % type(val))
714 raise Exception('user_data must be dict, got %s' % type(val))
668 try:
715 try:
669 self._user_data = json.dumps(val)
716 self._user_data = json.dumps(val)
670 except Exception:
717 except Exception:
671 log.error(traceback.format_exc())
718 log.error(traceback.format_exc())
672
719
673 @classmethod
720 @classmethod
674 def get_by_username(cls, username, case_insensitive=False,
721 def get_by_username(cls, username, case_insensitive=False,
675 cache=False, identity_cache=False):
722 cache=False, identity_cache=False):
676 session = Session()
723 session = Session()
677
724
678 if case_insensitive:
725 if case_insensitive:
679 q = cls.query().filter(
726 q = cls.query().filter(
680 func.lower(cls.username) == func.lower(username))
727 func.lower(cls.username) == func.lower(username))
681 else:
728 else:
682 q = cls.query().filter(cls.username == username)
729 q = cls.query().filter(cls.username == username)
683
730
684 if cache:
731 if cache:
685 if identity_cache:
732 if identity_cache:
686 val = cls.identity_cache(session, 'username', username)
733 val = cls.identity_cache(session, 'username', username)
687 if val:
734 if val:
688 return val
735 return val
689 else:
736 else:
690 q = q.options(
737 q = q.options(
691 FromCache("sql_cache_short",
738 FromCache("sql_cache_short",
692 "get_user_by_name_%s" % _hash_key(username)))
739 "get_user_by_name_%s" % _hash_key(username)))
693
740
694 return q.scalar()
741 return q.scalar()
695
742
696 @classmethod
743 @classmethod
697 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
744 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
698 q = cls.query().filter(cls.api_key == auth_token)
745 q = cls.query().filter(cls.api_key == auth_token)
699
746
700 if cache:
747 if cache:
701 q = q.options(FromCache("sql_cache_short",
748 q = q.options(FromCache("sql_cache_short",
702 "get_auth_token_%s" % auth_token))
749 "get_auth_token_%s" % auth_token))
703 res = q.scalar()
750 res = q.scalar()
704
751
705 if fallback and not res:
752 if fallback and not res:
706 #fallback to additional keys
753 #fallback to additional keys
707 _res = UserApiKeys.query()\
754 _res = UserApiKeys.query()\
708 .filter(UserApiKeys.api_key == auth_token)\
755 .filter(UserApiKeys.api_key == auth_token)\
709 .filter(or_(UserApiKeys.expires == -1,
756 .filter(or_(UserApiKeys.expires == -1,
710 UserApiKeys.expires >= time.time()))\
757 UserApiKeys.expires >= time.time()))\
711 .first()
758 .first()
712 if _res:
759 if _res:
713 res = _res.user
760 res = _res.user
714 return res
761 return res
715
762
716 @classmethod
763 @classmethod
717 def get_by_email(cls, email, case_insensitive=False, cache=False):
764 def get_by_email(cls, email, case_insensitive=False, cache=False):
718
765
719 if case_insensitive:
766 if case_insensitive:
720 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
767 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
721
768
722 else:
769 else:
723 q = cls.query().filter(cls.email == email)
770 q = cls.query().filter(cls.email == email)
724
771
725 if cache:
772 if cache:
726 q = q.options(FromCache("sql_cache_short",
773 q = q.options(FromCache("sql_cache_short",
727 "get_email_key_%s" % _hash_key(email)))
774 "get_email_key_%s" % _hash_key(email)))
728
775
729 ret = q.scalar()
776 ret = q.scalar()
730 if ret is None:
777 if ret is None:
731 q = UserEmailMap.query()
778 q = UserEmailMap.query()
732 # try fetching in alternate email map
779 # try fetching in alternate email map
733 if case_insensitive:
780 if case_insensitive:
734 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
781 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
735 else:
782 else:
736 q = q.filter(UserEmailMap.email == email)
783 q = q.filter(UserEmailMap.email == email)
737 q = q.options(joinedload(UserEmailMap.user))
784 q = q.options(joinedload(UserEmailMap.user))
738 if cache:
785 if cache:
739 q = q.options(FromCache("sql_cache_short",
786 q = q.options(FromCache("sql_cache_short",
740 "get_email_map_key_%s" % email))
787 "get_email_map_key_%s" % email))
741 ret = getattr(q.scalar(), 'user', None)
788 ret = getattr(q.scalar(), 'user', None)
742
789
743 return ret
790 return ret
744
791
745 @classmethod
792 @classmethod
746 def get_from_cs_author(cls, author):
793 def get_from_cs_author(cls, author):
747 """
794 """
748 Tries to get User objects out of commit author string
795 Tries to get User objects out of commit author string
749
796
750 :param author:
797 :param author:
751 """
798 """
752 from rhodecode.lib.helpers import email, author_name
799 from rhodecode.lib.helpers import email, author_name
753 # Valid email in the attribute passed, see if they're in the system
800 # Valid email in the attribute passed, see if they're in the system
754 _email = email(author)
801 _email = email(author)
755 if _email:
802 if _email:
756 user = cls.get_by_email(_email, case_insensitive=True)
803 user = cls.get_by_email(_email, case_insensitive=True)
757 if user:
804 if user:
758 return user
805 return user
759 # Maybe we can match by username?
806 # Maybe we can match by username?
760 _author = author_name(author)
807 _author = author_name(author)
761 user = cls.get_by_username(_author, case_insensitive=True)
808 user = cls.get_by_username(_author, case_insensitive=True)
762 if user:
809 if user:
763 return user
810 return user
764
811
765 def update_userdata(self, **kwargs):
812 def update_userdata(self, **kwargs):
766 usr = self
813 usr = self
767 old = usr.user_data
814 old = usr.user_data
768 old.update(**kwargs)
815 old.update(**kwargs)
769 usr.user_data = old
816 usr.user_data = old
770 Session().add(usr)
817 Session().add(usr)
771 log.debug('updated userdata with ', kwargs)
818 log.debug('updated userdata with ', kwargs)
772
819
773 def update_lastlogin(self):
820 def update_lastlogin(self):
774 """Update user lastlogin"""
821 """Update user lastlogin"""
775 self.last_login = datetime.datetime.now()
822 self.last_login = datetime.datetime.now()
776 Session().add(self)
823 Session().add(self)
777 log.debug('updated user %s lastlogin', self.username)
824 log.debug('updated user %s lastlogin', self.username)
778
825
779 def update_lastactivity(self):
826 def update_lastactivity(self):
780 """Update user lastactivity"""
827 """Update user lastactivity"""
781 usr = self
828 usr = self
782 old = usr.user_data
829 old = usr.user_data
783 old.update({'last_activity': time.time()})
830 old.update({'last_activity': time.time()})
784 usr.user_data = old
831 usr.user_data = old
785 Session().add(usr)
832 Session().add(usr)
786 log.debug('updated user %s lastactivity', usr.username)
833 log.debug('updated user %s lastactivity', usr.username)
787
834
788 def update_password(self, new_password, change_api_key=False):
835 def update_password(self, new_password, change_api_key=False):
789 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
836 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
790
837
791 self.password = get_crypt_password(new_password)
838 self.password = get_crypt_password(new_password)
792 if change_api_key:
839 if change_api_key:
793 self.api_key = generate_auth_token(self.username)
840 self.api_key = generate_auth_token(self.username)
794 Session().add(self)
841 Session().add(self)
795
842
796 @classmethod
843 @classmethod
797 def get_first_super_admin(cls):
844 def get_first_super_admin(cls):
798 user = User.query().filter(User.admin == true()).first()
845 user = User.query().filter(User.admin == true()).first()
799 if user is None:
846 if user is None:
800 raise Exception('FATAL: Missing administrative account!')
847 raise Exception('FATAL: Missing administrative account!')
801 return user
848 return user
802
849
803 @classmethod
850 @classmethod
804 def get_all_super_admins(cls):
851 def get_all_super_admins(cls):
805 """
852 """
806 Returns all admin accounts sorted by username
853 Returns all admin accounts sorted by username
807 """
854 """
808 return User.query().filter(User.admin == true())\
855 return User.query().filter(User.admin == true())\
809 .order_by(User.username.asc()).all()
856 .order_by(User.username.asc()).all()
810
857
811 @classmethod
858 @classmethod
812 def get_default_user(cls, cache=False):
859 def get_default_user(cls, cache=False):
813 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
860 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
814 if user is None:
861 if user is None:
815 raise Exception('FATAL: Missing default account!')
862 raise Exception('FATAL: Missing default account!')
816 return user
863 return user
817
864
818 def _get_default_perms(self, user, suffix=''):
865 def _get_default_perms(self, user, suffix=''):
819 from rhodecode.model.permission import PermissionModel
866 from rhodecode.model.permission import PermissionModel
820 return PermissionModel().get_default_perms(user.user_perms, suffix)
867 return PermissionModel().get_default_perms(user.user_perms, suffix)
821
868
822 def get_default_perms(self, suffix=''):
869 def get_default_perms(self, suffix=''):
823 return self._get_default_perms(self, suffix)
870 return self._get_default_perms(self, suffix)
824
871
825 def get_api_data(self, include_secrets=False, details='full'):
872 def get_api_data(self, include_secrets=False, details='full'):
826 """
873 """
827 Common function for generating user related data for API
874 Common function for generating user related data for API
828
875
829 :param include_secrets: By default secrets in the API data will be replaced
876 :param include_secrets: By default secrets in the API data will be replaced
830 by a placeholder value to prevent exposing this data by accident. In case
877 by a placeholder value to prevent exposing this data by accident. In case
831 this data shall be exposed, set this flag to ``True``.
878 this data shall be exposed, set this flag to ``True``.
832
879
833 :param details: details can be 'basic|full' basic gives only a subset of
880 :param details: details can be 'basic|full' basic gives only a subset of
834 the available user information that includes user_id, name and emails.
881 the available user information that includes user_id, name and emails.
835 """
882 """
836 user = self
883 user = self
837 user_data = self.user_data
884 user_data = self.user_data
838 data = {
885 data = {
839 'user_id': user.user_id,
886 'user_id': user.user_id,
840 'username': user.username,
887 'username': user.username,
841 'firstname': user.name,
888 'firstname': user.name,
842 'lastname': user.lastname,
889 'lastname': user.lastname,
843 'email': user.email,
890 'email': user.email,
844 'emails': user.emails,
891 'emails': user.emails,
845 }
892 }
846 if details == 'basic':
893 if details == 'basic':
847 return data
894 return data
848
895
849 api_key_length = 40
896 api_key_length = 40
850 api_key_replacement = '*' * api_key_length
897 api_key_replacement = '*' * api_key_length
851
898
852 extras = {
899 extras = {
853 'api_key': api_key_replacement,
900 'api_key': api_key_replacement,
854 'api_keys': [api_key_replacement],
901 'api_keys': [api_key_replacement],
855 'active': user.active,
902 'active': user.active,
856 'admin': user.admin,
903 'admin': user.admin,
857 'extern_type': user.extern_type,
904 'extern_type': user.extern_type,
858 'extern_name': user.extern_name,
905 'extern_name': user.extern_name,
859 'last_login': user.last_login,
906 'last_login': user.last_login,
860 'ip_addresses': user.ip_addresses,
907 'ip_addresses': user.ip_addresses,
861 'language': user_data.get('language')
908 'language': user_data.get('language')
862 }
909 }
863 data.update(extras)
910 data.update(extras)
864
911
865 if include_secrets:
912 if include_secrets:
866 data['api_key'] = user.api_key
913 data['api_key'] = user.api_key
867 data['api_keys'] = user.auth_tokens
914 data['api_keys'] = user.auth_tokens
868 return data
915 return data
869
916
870 def __json__(self):
917 def __json__(self):
871 data = {
918 data = {
872 'full_name': self.full_name,
919 'full_name': self.full_name,
873 'full_name_or_username': self.full_name_or_username,
920 'full_name_or_username': self.full_name_or_username,
874 'short_contact': self.short_contact,
921 'short_contact': self.short_contact,
875 'full_contact': self.full_contact,
922 'full_contact': self.full_contact,
876 }
923 }
877 data.update(self.get_api_data())
924 data.update(self.get_api_data())
878 return data
925 return data
879
926
880
927
881 class UserApiKeys(Base, BaseModel):
928 class UserApiKeys(Base, BaseModel):
882 __tablename__ = 'user_api_keys'
929 __tablename__ = 'user_api_keys'
883 __table_args__ = (
930 __table_args__ = (
884 Index('uak_api_key_idx', 'api_key'),
931 Index('uak_api_key_idx', 'api_key'),
885 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
932 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
886 UniqueConstraint('api_key'),
933 UniqueConstraint('api_key'),
887 {'extend_existing': True, 'mysql_engine': 'InnoDB',
934 {'extend_existing': True, 'mysql_engine': 'InnoDB',
888 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
935 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
889 )
936 )
890 __mapper_args__ = {}
937 __mapper_args__ = {}
891
938
892 # ApiKey role
939 # ApiKey role
893 ROLE_ALL = 'token_role_all'
940 ROLE_ALL = 'token_role_all'
894 ROLE_HTTP = 'token_role_http'
941 ROLE_HTTP = 'token_role_http'
895 ROLE_VCS = 'token_role_vcs'
942 ROLE_VCS = 'token_role_vcs'
896 ROLE_API = 'token_role_api'
943 ROLE_API = 'token_role_api'
897 ROLE_FEED = 'token_role_feed'
944 ROLE_FEED = 'token_role_feed'
898 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
945 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
899
946
900 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
947 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
901 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
948 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
902 api_key = Column("api_key", String(255), nullable=False, unique=True)
949 api_key = Column("api_key", String(255), nullable=False, unique=True)
903 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
950 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
904 expires = Column('expires', Float(53), nullable=False)
951 expires = Column('expires', Float(53), nullable=False)
905 role = Column('role', String(255), nullable=True)
952 role = Column('role', String(255), nullable=True)
906 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
953 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
907
954
908 user = relationship('User', lazy='joined')
955 user = relationship('User', lazy='joined')
909
956
910 @classmethod
957 @classmethod
911 def _get_role_name(cls, role):
958 def _get_role_name(cls, role):
912 return {
959 return {
913 cls.ROLE_ALL: _('all'),
960 cls.ROLE_ALL: _('all'),
914 cls.ROLE_HTTP: _('http/web interface'),
961 cls.ROLE_HTTP: _('http/web interface'),
915 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
962 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
916 cls.ROLE_API: _('api calls'),
963 cls.ROLE_API: _('api calls'),
917 cls.ROLE_FEED: _('feed access'),
964 cls.ROLE_FEED: _('feed access'),
918 }.get(role, role)
965 }.get(role, role)
919
966
920 @property
967 @property
921 def expired(self):
968 def expired(self):
922 if self.expires == -1:
969 if self.expires == -1:
923 return False
970 return False
924 return time.time() > self.expires
971 return time.time() > self.expires
925
972
926 @property
973 @property
927 def role_humanized(self):
974 def role_humanized(self):
928 return self._get_role_name(self.role)
975 return self._get_role_name(self.role)
929
976
930
977
931 class UserEmailMap(Base, BaseModel):
978 class UserEmailMap(Base, BaseModel):
932 __tablename__ = 'user_email_map'
979 __tablename__ = 'user_email_map'
933 __table_args__ = (
980 __table_args__ = (
934 Index('uem_email_idx', 'email'),
981 Index('uem_email_idx', 'email'),
935 UniqueConstraint('email'),
982 UniqueConstraint('email'),
936 {'extend_existing': True, 'mysql_engine': 'InnoDB',
983 {'extend_existing': True, 'mysql_engine': 'InnoDB',
937 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
984 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
938 )
985 )
939 __mapper_args__ = {}
986 __mapper_args__ = {}
940
987
941 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
988 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
942 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
989 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
943 _email = Column("email", String(255), nullable=True, unique=False, default=None)
990 _email = Column("email", String(255), nullable=True, unique=False, default=None)
944 user = relationship('User', lazy='joined')
991 user = relationship('User', lazy='joined')
945
992
946 @validates('_email')
993 @validates('_email')
947 def validate_email(self, key, email):
994 def validate_email(self, key, email):
948 # check if this email is not main one
995 # check if this email is not main one
949 main_email = Session().query(User).filter(User.email == email).scalar()
996 main_email = Session().query(User).filter(User.email == email).scalar()
950 if main_email is not None:
997 if main_email is not None:
951 raise AttributeError('email %s is present is user table' % email)
998 raise AttributeError('email %s is present is user table' % email)
952 return email
999 return email
953
1000
954 @hybrid_property
1001 @hybrid_property
955 def email(self):
1002 def email(self):
956 return self._email
1003 return self._email
957
1004
958 @email.setter
1005 @email.setter
959 def email(self, val):
1006 def email(self, val):
960 self._email = val.lower() if val else None
1007 self._email = val.lower() if val else None
961
1008
962
1009
963 class UserIpMap(Base, BaseModel):
1010 class UserIpMap(Base, BaseModel):
964 __tablename__ = 'user_ip_map'
1011 __tablename__ = 'user_ip_map'
965 __table_args__ = (
1012 __table_args__ = (
966 UniqueConstraint('user_id', 'ip_addr'),
1013 UniqueConstraint('user_id', 'ip_addr'),
967 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1014 {'extend_existing': True, 'mysql_engine': 'InnoDB',
968 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1015 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
969 )
1016 )
970 __mapper_args__ = {}
1017 __mapper_args__ = {}
971
1018
972 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1019 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
973 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1020 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
974 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1021 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
975 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1022 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
976 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1023 description = Column("description", String(10000), nullable=True, unique=None, default=None)
977 user = relationship('User', lazy='joined')
1024 user = relationship('User', lazy='joined')
978
1025
979 @classmethod
1026 @classmethod
980 def _get_ip_range(cls, ip_addr):
1027 def _get_ip_range(cls, ip_addr):
981 net = ipaddress.ip_network(ip_addr, strict=False)
1028 net = ipaddress.ip_network(ip_addr, strict=False)
982 return [str(net.network_address), str(net.broadcast_address)]
1029 return [str(net.network_address), str(net.broadcast_address)]
983
1030
984 def __json__(self):
1031 def __json__(self):
985 return {
1032 return {
986 'ip_addr': self.ip_addr,
1033 'ip_addr': self.ip_addr,
987 'ip_range': self._get_ip_range(self.ip_addr),
1034 'ip_range': self._get_ip_range(self.ip_addr),
988 }
1035 }
989
1036
990 def __unicode__(self):
1037 def __unicode__(self):
991 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1038 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
992 self.user_id, self.ip_addr)
1039 self.user_id, self.ip_addr)
993
1040
994 class UserLog(Base, BaseModel):
1041 class UserLog(Base, BaseModel):
995 __tablename__ = 'user_logs'
1042 __tablename__ = 'user_logs'
996 __table_args__ = (
1043 __table_args__ = (
997 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1044 {'extend_existing': True, 'mysql_engine': 'InnoDB',
998 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1045 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
999 )
1046 )
1000 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1047 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1001 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1048 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1002 username = Column("username", String(255), nullable=True, unique=None, default=None)
1049 username = Column("username", String(255), nullable=True, unique=None, default=None)
1003 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1050 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1004 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1051 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1005 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1052 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1006 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1053 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1007 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1054 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1008
1055
1009 def __unicode__(self):
1056 def __unicode__(self):
1010 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1057 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1011 self.repository_name,
1058 self.repository_name,
1012 self.action)
1059 self.action)
1013
1060
1014 @property
1061 @property
1015 def action_as_day(self):
1062 def action_as_day(self):
1016 return datetime.date(*self.action_date.timetuple()[:3])
1063 return datetime.date(*self.action_date.timetuple()[:3])
1017
1064
1018 user = relationship('User')
1065 user = relationship('User')
1019 repository = relationship('Repository', cascade='')
1066 repository = relationship('Repository', cascade='')
1020
1067
1021
1068
1022 class UserGroup(Base, BaseModel):
1069 class UserGroup(Base, BaseModel):
1023 __tablename__ = 'users_groups'
1070 __tablename__ = 'users_groups'
1024 __table_args__ = (
1071 __table_args__ = (
1025 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1072 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1026 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1073 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1027 )
1074 )
1028
1075
1029 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1076 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1030 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1077 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1031 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1078 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1032 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1079 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1033 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1080 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1034 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1081 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1035 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1082 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1036 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1083 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1037
1084
1038 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1085 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1039 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1086 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1040 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1087 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1041 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1088 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1042 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1089 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1043 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1090 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1044
1091
1045 user = relationship('User')
1092 user = relationship('User')
1046
1093
1047 @hybrid_property
1094 @hybrid_property
1048 def group_data(self):
1095 def group_data(self):
1049 if not self._group_data:
1096 if not self._group_data:
1050 return {}
1097 return {}
1051
1098
1052 try:
1099 try:
1053 return json.loads(self._group_data)
1100 return json.loads(self._group_data)
1054 except TypeError:
1101 except TypeError:
1055 return {}
1102 return {}
1056
1103
1057 @group_data.setter
1104 @group_data.setter
1058 def group_data(self, val):
1105 def group_data(self, val):
1059 try:
1106 try:
1060 self._group_data = json.dumps(val)
1107 self._group_data = json.dumps(val)
1061 except Exception:
1108 except Exception:
1062 log.error(traceback.format_exc())
1109 log.error(traceback.format_exc())
1063
1110
1064 def __unicode__(self):
1111 def __unicode__(self):
1065 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1112 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1066 self.users_group_id,
1113 self.users_group_id,
1067 self.users_group_name)
1114 self.users_group_name)
1068
1115
1069 @classmethod
1116 @classmethod
1070 def get_by_group_name(cls, group_name, cache=False,
1117 def get_by_group_name(cls, group_name, cache=False,
1071 case_insensitive=False):
1118 case_insensitive=False):
1072 if case_insensitive:
1119 if case_insensitive:
1073 q = cls.query().filter(func.lower(cls.users_group_name) ==
1120 q = cls.query().filter(func.lower(cls.users_group_name) ==
1074 func.lower(group_name))
1121 func.lower(group_name))
1075
1122
1076 else:
1123 else:
1077 q = cls.query().filter(cls.users_group_name == group_name)
1124 q = cls.query().filter(cls.users_group_name == group_name)
1078 if cache:
1125 if cache:
1079 q = q.options(FromCache(
1126 q = q.options(FromCache(
1080 "sql_cache_short",
1127 "sql_cache_short",
1081 "get_group_%s" % _hash_key(group_name)))
1128 "get_group_%s" % _hash_key(group_name)))
1082 return q.scalar()
1129 return q.scalar()
1083
1130
1084 @classmethod
1131 @classmethod
1085 def get(cls, user_group_id, cache=False):
1132 def get(cls, user_group_id, cache=False):
1086 user_group = cls.query()
1133 user_group = cls.query()
1087 if cache:
1134 if cache:
1088 user_group = user_group.options(FromCache("sql_cache_short",
1135 user_group = user_group.options(FromCache("sql_cache_short",
1089 "get_users_group_%s" % user_group_id))
1136 "get_users_group_%s" % user_group_id))
1090 return user_group.get(user_group_id)
1137 return user_group.get(user_group_id)
1091
1138
1092 def permissions(self, with_admins=True, with_owner=True):
1139 def permissions(self, with_admins=True, with_owner=True):
1093 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1140 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1094 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1141 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1095 joinedload(UserUserGroupToPerm.user),
1142 joinedload(UserUserGroupToPerm.user),
1096 joinedload(UserUserGroupToPerm.permission),)
1143 joinedload(UserUserGroupToPerm.permission),)
1097
1144
1098 # get owners and admins and permissions. We do a trick of re-writing
1145 # get owners and admins and permissions. We do a trick of re-writing
1099 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1146 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1100 # has a global reference and changing one object propagates to all
1147 # has a global reference and changing one object propagates to all
1101 # others. This means if admin is also an owner admin_row that change
1148 # others. This means if admin is also an owner admin_row that change
1102 # would propagate to both objects
1149 # would propagate to both objects
1103 perm_rows = []
1150 perm_rows = []
1104 for _usr in q.all():
1151 for _usr in q.all():
1105 usr = AttributeDict(_usr.user.get_dict())
1152 usr = AttributeDict(_usr.user.get_dict())
1106 usr.permission = _usr.permission.permission_name
1153 usr.permission = _usr.permission.permission_name
1107 perm_rows.append(usr)
1154 perm_rows.append(usr)
1108
1155
1109 # filter the perm rows by 'default' first and then sort them by
1156 # filter the perm rows by 'default' first and then sort them by
1110 # admin,write,read,none permissions sorted again alphabetically in
1157 # admin,write,read,none permissions sorted again alphabetically in
1111 # each group
1158 # each group
1112 perm_rows = sorted(perm_rows, key=display_sort)
1159 perm_rows = sorted(perm_rows, key=display_sort)
1113
1160
1114 _admin_perm = 'usergroup.admin'
1161 _admin_perm = 'usergroup.admin'
1115 owner_row = []
1162 owner_row = []
1116 if with_owner:
1163 if with_owner:
1117 usr = AttributeDict(self.user.get_dict())
1164 usr = AttributeDict(self.user.get_dict())
1118 usr.owner_row = True
1165 usr.owner_row = True
1119 usr.permission = _admin_perm
1166 usr.permission = _admin_perm
1120 owner_row.append(usr)
1167 owner_row.append(usr)
1121
1168
1122 super_admin_rows = []
1169 super_admin_rows = []
1123 if with_admins:
1170 if with_admins:
1124 for usr in User.get_all_super_admins():
1171 for usr in User.get_all_super_admins():
1125 # if this admin is also owner, don't double the record
1172 # if this admin is also owner, don't double the record
1126 if usr.user_id == owner_row[0].user_id:
1173 if usr.user_id == owner_row[0].user_id:
1127 owner_row[0].admin_row = True
1174 owner_row[0].admin_row = True
1128 else:
1175 else:
1129 usr = AttributeDict(usr.get_dict())
1176 usr = AttributeDict(usr.get_dict())
1130 usr.admin_row = True
1177 usr.admin_row = True
1131 usr.permission = _admin_perm
1178 usr.permission = _admin_perm
1132 super_admin_rows.append(usr)
1179 super_admin_rows.append(usr)
1133
1180
1134 return super_admin_rows + owner_row + perm_rows
1181 return super_admin_rows + owner_row + perm_rows
1135
1182
1136 def permission_user_groups(self):
1183 def permission_user_groups(self):
1137 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1184 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1138 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1185 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1139 joinedload(UserGroupUserGroupToPerm.target_user_group),
1186 joinedload(UserGroupUserGroupToPerm.target_user_group),
1140 joinedload(UserGroupUserGroupToPerm.permission),)
1187 joinedload(UserGroupUserGroupToPerm.permission),)
1141
1188
1142 perm_rows = []
1189 perm_rows = []
1143 for _user_group in q.all():
1190 for _user_group in q.all():
1144 usr = AttributeDict(_user_group.user_group.get_dict())
1191 usr = AttributeDict(_user_group.user_group.get_dict())
1145 usr.permission = _user_group.permission.permission_name
1192 usr.permission = _user_group.permission.permission_name
1146 perm_rows.append(usr)
1193 perm_rows.append(usr)
1147
1194
1148 return perm_rows
1195 return perm_rows
1149
1196
1150 def _get_default_perms(self, user_group, suffix=''):
1197 def _get_default_perms(self, user_group, suffix=''):
1151 from rhodecode.model.permission import PermissionModel
1198 from rhodecode.model.permission import PermissionModel
1152 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1199 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1153
1200
1154 def get_default_perms(self, suffix=''):
1201 def get_default_perms(self, suffix=''):
1155 return self._get_default_perms(self, suffix)
1202 return self._get_default_perms(self, suffix)
1156
1203
1157 def get_api_data(self, with_group_members=True, include_secrets=False):
1204 def get_api_data(self, with_group_members=True, include_secrets=False):
1158 """
1205 """
1159 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1206 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1160 basically forwarded.
1207 basically forwarded.
1161
1208
1162 """
1209 """
1163 user_group = self
1210 user_group = self
1164
1211
1165 data = {
1212 data = {
1166 'users_group_id': user_group.users_group_id,
1213 'users_group_id': user_group.users_group_id,
1167 'group_name': user_group.users_group_name,
1214 'group_name': user_group.users_group_name,
1168 'group_description': user_group.user_group_description,
1215 'group_description': user_group.user_group_description,
1169 'active': user_group.users_group_active,
1216 'active': user_group.users_group_active,
1170 'owner': user_group.user.username,
1217 'owner': user_group.user.username,
1171 }
1218 }
1172 if with_group_members:
1219 if with_group_members:
1173 users = []
1220 users = []
1174 for user in user_group.members:
1221 for user in user_group.members:
1175 user = user.user
1222 user = user.user
1176 users.append(user.get_api_data(include_secrets=include_secrets))
1223 users.append(user.get_api_data(include_secrets=include_secrets))
1177 data['users'] = users
1224 data['users'] = users
1178
1225
1179 return data
1226 return data
1180
1227
1181
1228
1182 class UserGroupMember(Base, BaseModel):
1229 class UserGroupMember(Base, BaseModel):
1183 __tablename__ = 'users_groups_members'
1230 __tablename__ = 'users_groups_members'
1184 __table_args__ = (
1231 __table_args__ = (
1185 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1232 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1186 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1233 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1187 )
1234 )
1188
1235
1189 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1236 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1190 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1237 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1191 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1238 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1192
1239
1193 user = relationship('User', lazy='joined')
1240 user = relationship('User', lazy='joined')
1194 users_group = relationship('UserGroup')
1241 users_group = relationship('UserGroup')
1195
1242
1196 def __init__(self, gr_id='', u_id=''):
1243 def __init__(self, gr_id='', u_id=''):
1197 self.users_group_id = gr_id
1244 self.users_group_id = gr_id
1198 self.user_id = u_id
1245 self.user_id = u_id
1199
1246
1200
1247
1201 class RepositoryField(Base, BaseModel):
1248 class RepositoryField(Base, BaseModel):
1202 __tablename__ = 'repositories_fields'
1249 __tablename__ = 'repositories_fields'
1203 __table_args__ = (
1250 __table_args__ = (
1204 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1251 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1205 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1252 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1206 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1253 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1207 )
1254 )
1208 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1255 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1209
1256
1210 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1257 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1211 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1258 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1212 field_key = Column("field_key", String(250))
1259 field_key = Column("field_key", String(250))
1213 field_label = Column("field_label", String(1024), nullable=False)
1260 field_label = Column("field_label", String(1024), nullable=False)
1214 field_value = Column("field_value", String(10000), nullable=False)
1261 field_value = Column("field_value", String(10000), nullable=False)
1215 field_desc = Column("field_desc", String(1024), nullable=False)
1262 field_desc = Column("field_desc", String(1024), nullable=False)
1216 field_type = Column("field_type", String(255), nullable=False, unique=None)
1263 field_type = Column("field_type", String(255), nullable=False, unique=None)
1217 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1264 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1218
1265
1219 repository = relationship('Repository')
1266 repository = relationship('Repository')
1220
1267
1221 @property
1268 @property
1222 def field_key_prefixed(self):
1269 def field_key_prefixed(self):
1223 return 'ex_%s' % self.field_key
1270 return 'ex_%s' % self.field_key
1224
1271
1225 @classmethod
1272 @classmethod
1226 def un_prefix_key(cls, key):
1273 def un_prefix_key(cls, key):
1227 if key.startswith(cls.PREFIX):
1274 if key.startswith(cls.PREFIX):
1228 return key[len(cls.PREFIX):]
1275 return key[len(cls.PREFIX):]
1229 return key
1276 return key
1230
1277
1231 @classmethod
1278 @classmethod
1232 def get_by_key_name(cls, key, repo):
1279 def get_by_key_name(cls, key, repo):
1233 row = cls.query()\
1280 row = cls.query()\
1234 .filter(cls.repository == repo)\
1281 .filter(cls.repository == repo)\
1235 .filter(cls.field_key == key).scalar()
1282 .filter(cls.field_key == key).scalar()
1236 return row
1283 return row
1237
1284
1238
1285
1239 class Repository(Base, BaseModel):
1286 class Repository(Base, BaseModel):
1240 __tablename__ = 'repositories'
1287 __tablename__ = 'repositories'
1241 __table_args__ = (
1288 __table_args__ = (
1242 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1289 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1243 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1290 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1244 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1291 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1245 )
1292 )
1246 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1293 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1247 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1294 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1248
1295
1249 STATE_CREATED = 'repo_state_created'
1296 STATE_CREATED = 'repo_state_created'
1250 STATE_PENDING = 'repo_state_pending'
1297 STATE_PENDING = 'repo_state_pending'
1251 STATE_ERROR = 'repo_state_error'
1298 STATE_ERROR = 'repo_state_error'
1252
1299
1253 LOCK_AUTOMATIC = 'lock_auto'
1300 LOCK_AUTOMATIC = 'lock_auto'
1254 LOCK_API = 'lock_api'
1301 LOCK_API = 'lock_api'
1255 LOCK_WEB = 'lock_web'
1302 LOCK_WEB = 'lock_web'
1256 LOCK_PULL = 'lock_pull'
1303 LOCK_PULL = 'lock_pull'
1257
1304
1258 NAME_SEP = URL_SEP
1305 NAME_SEP = URL_SEP
1259
1306
1260 repo_id = Column(
1307 repo_id = Column(
1261 "repo_id", Integer(), nullable=False, unique=True, default=None,
1308 "repo_id", Integer(), nullable=False, unique=True, default=None,
1262 primary_key=True)
1309 primary_key=True)
1263 _repo_name = Column(
1310 _repo_name = Column(
1264 "repo_name", Text(), nullable=False, default=None)
1311 "repo_name", Text(), nullable=False, default=None)
1265 _repo_name_hash = Column(
1312 _repo_name_hash = Column(
1266 "repo_name_hash", String(255), nullable=False, unique=True)
1313 "repo_name_hash", String(255), nullable=False, unique=True)
1267 repo_state = Column("repo_state", String(255), nullable=True)
1314 repo_state = Column("repo_state", String(255), nullable=True)
1268
1315
1269 clone_uri = Column(
1316 clone_uri = Column(
1270 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1317 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1271 default=None)
1318 default=None)
1272 repo_type = Column(
1319 repo_type = Column(
1273 "repo_type", String(255), nullable=False, unique=False, default=None)
1320 "repo_type", String(255), nullable=False, unique=False, default=None)
1274 user_id = Column(
1321 user_id = Column(
1275 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1322 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1276 unique=False, default=None)
1323 unique=False, default=None)
1277 private = Column(
1324 private = Column(
1278 "private", Boolean(), nullable=True, unique=None, default=None)
1325 "private", Boolean(), nullable=True, unique=None, default=None)
1279 enable_statistics = Column(
1326 enable_statistics = Column(
1280 "statistics", Boolean(), nullable=True, unique=None, default=True)
1327 "statistics", Boolean(), nullable=True, unique=None, default=True)
1281 enable_downloads = Column(
1328 enable_downloads = Column(
1282 "downloads", Boolean(), nullable=True, unique=None, default=True)
1329 "downloads", Boolean(), nullable=True, unique=None, default=True)
1283 description = Column(
1330 description = Column(
1284 "description", String(10000), nullable=True, unique=None, default=None)
1331 "description", String(10000), nullable=True, unique=None, default=None)
1285 created_on = Column(
1332 created_on = Column(
1286 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1333 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1287 default=datetime.datetime.now)
1334 default=datetime.datetime.now)
1288 updated_on = Column(
1335 updated_on = Column(
1289 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1336 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1290 default=datetime.datetime.now)
1337 default=datetime.datetime.now)
1291 _landing_revision = Column(
1338 _landing_revision = Column(
1292 "landing_revision", String(255), nullable=False, unique=False,
1339 "landing_revision", String(255), nullable=False, unique=False,
1293 default=None)
1340 default=None)
1294 enable_locking = Column(
1341 enable_locking = Column(
1295 "enable_locking", Boolean(), nullable=False, unique=None,
1342 "enable_locking", Boolean(), nullable=False, unique=None,
1296 default=False)
1343 default=False)
1297 _locked = Column(
1344 _locked = Column(
1298 "locked", String(255), nullable=True, unique=False, default=None)
1345 "locked", String(255), nullable=True, unique=False, default=None)
1299 _changeset_cache = Column(
1346 _changeset_cache = Column(
1300 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1347 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1301
1348
1302 fork_id = Column(
1349 fork_id = Column(
1303 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1350 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1304 nullable=True, unique=False, default=None)
1351 nullable=True, unique=False, default=None)
1305 group_id = Column(
1352 group_id = Column(
1306 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1353 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1307 unique=False, default=None)
1354 unique=False, default=None)
1308
1355
1309 user = relationship('User', lazy='joined')
1356 user = relationship('User', lazy='joined')
1310 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1357 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1311 group = relationship('RepoGroup', lazy='joined')
1358 group = relationship('RepoGroup', lazy='joined')
1312 repo_to_perm = relationship(
1359 repo_to_perm = relationship(
1313 'UserRepoToPerm', cascade='all',
1360 'UserRepoToPerm', cascade='all',
1314 order_by='UserRepoToPerm.repo_to_perm_id')
1361 order_by='UserRepoToPerm.repo_to_perm_id')
1315 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1362 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1316 stats = relationship('Statistics', cascade='all', uselist=False)
1363 stats = relationship('Statistics', cascade='all', uselist=False)
1317
1364
1318 followers = relationship(
1365 followers = relationship(
1319 'UserFollowing',
1366 'UserFollowing',
1320 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1367 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1321 cascade='all')
1368 cascade='all')
1322 extra_fields = relationship(
1369 extra_fields = relationship(
1323 'RepositoryField', cascade="all, delete, delete-orphan")
1370 'RepositoryField', cascade="all, delete, delete-orphan")
1324 logs = relationship('UserLog')
1371 logs = relationship('UserLog')
1325 comments = relationship(
1372 comments = relationship(
1326 'ChangesetComment', cascade="all, delete, delete-orphan")
1373 'ChangesetComment', cascade="all, delete, delete-orphan")
1327 pull_requests_source = relationship(
1374 pull_requests_source = relationship(
1328 'PullRequest',
1375 'PullRequest',
1329 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1376 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1330 cascade="all, delete, delete-orphan")
1377 cascade="all, delete, delete-orphan")
1331 pull_requests_target = relationship(
1378 pull_requests_target = relationship(
1332 'PullRequest',
1379 'PullRequest',
1333 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1380 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1334 cascade="all, delete, delete-orphan")
1381 cascade="all, delete, delete-orphan")
1335 ui = relationship('RepoRhodeCodeUi', cascade="all")
1382 ui = relationship('RepoRhodeCodeUi', cascade="all")
1336 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1383 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1337 integrations = relationship('Integration',
1384 integrations = relationship('Integration',
1338 cascade="all, delete, delete-orphan")
1385 cascade="all, delete, delete-orphan")
1339
1386
1340 def __unicode__(self):
1387 def __unicode__(self):
1341 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1388 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1342 safe_unicode(self.repo_name))
1389 safe_unicode(self.repo_name))
1343
1390
1344 @hybrid_property
1391 @hybrid_property
1345 def landing_rev(self):
1392 def landing_rev(self):
1346 # always should return [rev_type, rev]
1393 # always should return [rev_type, rev]
1347 if self._landing_revision:
1394 if self._landing_revision:
1348 _rev_info = self._landing_revision.split(':')
1395 _rev_info = self._landing_revision.split(':')
1349 if len(_rev_info) < 2:
1396 if len(_rev_info) < 2:
1350 _rev_info.insert(0, 'rev')
1397 _rev_info.insert(0, 'rev')
1351 return [_rev_info[0], _rev_info[1]]
1398 return [_rev_info[0], _rev_info[1]]
1352 return [None, None]
1399 return [None, None]
1353
1400
1354 @landing_rev.setter
1401 @landing_rev.setter
1355 def landing_rev(self, val):
1402 def landing_rev(self, val):
1356 if ':' not in val:
1403 if ':' not in val:
1357 raise ValueError('value must be delimited with `:` and consist '
1404 raise ValueError('value must be delimited with `:` and consist '
1358 'of <rev_type>:<rev>, got %s instead' % val)
1405 'of <rev_type>:<rev>, got %s instead' % val)
1359 self._landing_revision = val
1406 self._landing_revision = val
1360
1407
1361 @hybrid_property
1408 @hybrid_property
1362 def locked(self):
1409 def locked(self):
1363 if self._locked:
1410 if self._locked:
1364 user_id, timelocked, reason = self._locked.split(':')
1411 user_id, timelocked, reason = self._locked.split(':')
1365 lock_values = int(user_id), timelocked, reason
1412 lock_values = int(user_id), timelocked, reason
1366 else:
1413 else:
1367 lock_values = [None, None, None]
1414 lock_values = [None, None, None]
1368 return lock_values
1415 return lock_values
1369
1416
1370 @locked.setter
1417 @locked.setter
1371 def locked(self, val):
1418 def locked(self, val):
1372 if val and isinstance(val, (list, tuple)):
1419 if val and isinstance(val, (list, tuple)):
1373 self._locked = ':'.join(map(str, val))
1420 self._locked = ':'.join(map(str, val))
1374 else:
1421 else:
1375 self._locked = None
1422 self._locked = None
1376
1423
1377 @hybrid_property
1424 @hybrid_property
1378 def changeset_cache(self):
1425 def changeset_cache(self):
1379 from rhodecode.lib.vcs.backends.base import EmptyCommit
1426 from rhodecode.lib.vcs.backends.base import EmptyCommit
1380 dummy = EmptyCommit().__json__()
1427 dummy = EmptyCommit().__json__()
1381 if not self._changeset_cache:
1428 if not self._changeset_cache:
1382 return dummy
1429 return dummy
1383 try:
1430 try:
1384 return json.loads(self._changeset_cache)
1431 return json.loads(self._changeset_cache)
1385 except TypeError:
1432 except TypeError:
1386 return dummy
1433 return dummy
1387 except Exception:
1434 except Exception:
1388 log.error(traceback.format_exc())
1435 log.error(traceback.format_exc())
1389 return dummy
1436 return dummy
1390
1437
1391 @changeset_cache.setter
1438 @changeset_cache.setter
1392 def changeset_cache(self, val):
1439 def changeset_cache(self, val):
1393 try:
1440 try:
1394 self._changeset_cache = json.dumps(val)
1441 self._changeset_cache = json.dumps(val)
1395 except Exception:
1442 except Exception:
1396 log.error(traceback.format_exc())
1443 log.error(traceback.format_exc())
1397
1444
1398 @hybrid_property
1445 @hybrid_property
1399 def repo_name(self):
1446 def repo_name(self):
1400 return self._repo_name
1447 return self._repo_name
1401
1448
1402 @repo_name.setter
1449 @repo_name.setter
1403 def repo_name(self, value):
1450 def repo_name(self, value):
1404 self._repo_name = value
1451 self._repo_name = value
1405 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1452 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1406
1453
1407 @classmethod
1454 @classmethod
1408 def normalize_repo_name(cls, repo_name):
1455 def normalize_repo_name(cls, repo_name):
1409 """
1456 """
1410 Normalizes os specific repo_name to the format internally stored inside
1457 Normalizes os specific repo_name to the format internally stored inside
1411 database using URL_SEP
1458 database using URL_SEP
1412
1459
1413 :param cls:
1460 :param cls:
1414 :param repo_name:
1461 :param repo_name:
1415 """
1462 """
1416 return cls.NAME_SEP.join(repo_name.split(os.sep))
1463 return cls.NAME_SEP.join(repo_name.split(os.sep))
1417
1464
1418 @classmethod
1465 @classmethod
1419 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1466 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1420 session = Session()
1467 session = Session()
1421 q = session.query(cls).filter(cls.repo_name == repo_name)
1468 q = session.query(cls).filter(cls.repo_name == repo_name)
1422
1469
1423 if cache:
1470 if cache:
1424 if identity_cache:
1471 if identity_cache:
1425 val = cls.identity_cache(session, 'repo_name', repo_name)
1472 val = cls.identity_cache(session, 'repo_name', repo_name)
1426 if val:
1473 if val:
1427 return val
1474 return val
1428 else:
1475 else:
1429 q = q.options(
1476 q = q.options(
1430 FromCache("sql_cache_short",
1477 FromCache("sql_cache_short",
1431 "get_repo_by_name_%s" % _hash_key(repo_name)))
1478 "get_repo_by_name_%s" % _hash_key(repo_name)))
1432
1479
1433 return q.scalar()
1480 return q.scalar()
1434
1481
1435 @classmethod
1482 @classmethod
1436 def get_by_full_path(cls, repo_full_path):
1483 def get_by_full_path(cls, repo_full_path):
1437 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1484 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1438 repo_name = cls.normalize_repo_name(repo_name)
1485 repo_name = cls.normalize_repo_name(repo_name)
1439 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1486 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1440
1487
1441 @classmethod
1488 @classmethod
1442 def get_repo_forks(cls, repo_id):
1489 def get_repo_forks(cls, repo_id):
1443 return cls.query().filter(Repository.fork_id == repo_id)
1490 return cls.query().filter(Repository.fork_id == repo_id)
1444
1491
1445 @classmethod
1492 @classmethod
1446 def base_path(cls):
1493 def base_path(cls):
1447 """
1494 """
1448 Returns base path when all repos are stored
1495 Returns base path when all repos are stored
1449
1496
1450 :param cls:
1497 :param cls:
1451 """
1498 """
1452 q = Session().query(RhodeCodeUi)\
1499 q = Session().query(RhodeCodeUi)\
1453 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1500 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1454 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1501 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1455 return q.one().ui_value
1502 return q.one().ui_value
1456
1503
1457 @classmethod
1504 @classmethod
1458 def is_valid(cls, repo_name):
1505 def is_valid(cls, repo_name):
1459 """
1506 """
1460 returns True if given repo name is a valid filesystem repository
1507 returns True if given repo name is a valid filesystem repository
1461
1508
1462 :param cls:
1509 :param cls:
1463 :param repo_name:
1510 :param repo_name:
1464 """
1511 """
1465 from rhodecode.lib.utils import is_valid_repo
1512 from rhodecode.lib.utils import is_valid_repo
1466
1513
1467 return is_valid_repo(repo_name, cls.base_path())
1514 return is_valid_repo(repo_name, cls.base_path())
1468
1515
1469 @classmethod
1516 @classmethod
1470 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1517 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1471 case_insensitive=True):
1518 case_insensitive=True):
1472 q = Repository.query()
1519 q = Repository.query()
1473
1520
1474 if not isinstance(user_id, Optional):
1521 if not isinstance(user_id, Optional):
1475 q = q.filter(Repository.user_id == user_id)
1522 q = q.filter(Repository.user_id == user_id)
1476
1523
1477 if not isinstance(group_id, Optional):
1524 if not isinstance(group_id, Optional):
1478 q = q.filter(Repository.group_id == group_id)
1525 q = q.filter(Repository.group_id == group_id)
1479
1526
1480 if case_insensitive:
1527 if case_insensitive:
1481 q = q.order_by(func.lower(Repository.repo_name))
1528 q = q.order_by(func.lower(Repository.repo_name))
1482 else:
1529 else:
1483 q = q.order_by(Repository.repo_name)
1530 q = q.order_by(Repository.repo_name)
1484 return q.all()
1531 return q.all()
1485
1532
1486 @property
1533 @property
1487 def forks(self):
1534 def forks(self):
1488 """
1535 """
1489 Return forks of this repo
1536 Return forks of this repo
1490 """
1537 """
1491 return Repository.get_repo_forks(self.repo_id)
1538 return Repository.get_repo_forks(self.repo_id)
1492
1539
1493 @property
1540 @property
1494 def parent(self):
1541 def parent(self):
1495 """
1542 """
1496 Returns fork parent
1543 Returns fork parent
1497 """
1544 """
1498 return self.fork
1545 return self.fork
1499
1546
1500 @property
1547 @property
1501 def just_name(self):
1548 def just_name(self):
1502 return self.repo_name.split(self.NAME_SEP)[-1]
1549 return self.repo_name.split(self.NAME_SEP)[-1]
1503
1550
1504 @property
1551 @property
1505 def groups_with_parents(self):
1552 def groups_with_parents(self):
1506 groups = []
1553 groups = []
1507 if self.group is None:
1554 if self.group is None:
1508 return groups
1555 return groups
1509
1556
1510 cur_gr = self.group
1557 cur_gr = self.group
1511 groups.insert(0, cur_gr)
1558 groups.insert(0, cur_gr)
1512 while 1:
1559 while 1:
1513 gr = getattr(cur_gr, 'parent_group', None)
1560 gr = getattr(cur_gr, 'parent_group', None)
1514 cur_gr = cur_gr.parent_group
1561 cur_gr = cur_gr.parent_group
1515 if gr is None:
1562 if gr is None:
1516 break
1563 break
1517 groups.insert(0, gr)
1564 groups.insert(0, gr)
1518
1565
1519 return groups
1566 return groups
1520
1567
1521 @property
1568 @property
1522 def groups_and_repo(self):
1569 def groups_and_repo(self):
1523 return self.groups_with_parents, self
1570 return self.groups_with_parents, self
1524
1571
1525 @LazyProperty
1572 @LazyProperty
1526 def repo_path(self):
1573 def repo_path(self):
1527 """
1574 """
1528 Returns base full path for that repository means where it actually
1575 Returns base full path for that repository means where it actually
1529 exists on a filesystem
1576 exists on a filesystem
1530 """
1577 """
1531 q = Session().query(RhodeCodeUi).filter(
1578 q = Session().query(RhodeCodeUi).filter(
1532 RhodeCodeUi.ui_key == self.NAME_SEP)
1579 RhodeCodeUi.ui_key == self.NAME_SEP)
1533 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1580 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1534 return q.one().ui_value
1581 return q.one().ui_value
1535
1582
1536 @property
1583 @property
1537 def repo_full_path(self):
1584 def repo_full_path(self):
1538 p = [self.repo_path]
1585 p = [self.repo_path]
1539 # we need to split the name by / since this is how we store the
1586 # we need to split the name by / since this is how we store the
1540 # names in the database, but that eventually needs to be converted
1587 # names in the database, but that eventually needs to be converted
1541 # into a valid system path
1588 # into a valid system path
1542 p += self.repo_name.split(self.NAME_SEP)
1589 p += self.repo_name.split(self.NAME_SEP)
1543 return os.path.join(*map(safe_unicode, p))
1590 return os.path.join(*map(safe_unicode, p))
1544
1591
1545 @property
1592 @property
1546 def cache_keys(self):
1593 def cache_keys(self):
1547 """
1594 """
1548 Returns associated cache keys for that repo
1595 Returns associated cache keys for that repo
1549 """
1596 """
1550 return CacheKey.query()\
1597 return CacheKey.query()\
1551 .filter(CacheKey.cache_args == self.repo_name)\
1598 .filter(CacheKey.cache_args == self.repo_name)\
1552 .order_by(CacheKey.cache_key)\
1599 .order_by(CacheKey.cache_key)\
1553 .all()
1600 .all()
1554
1601
1555 def get_new_name(self, repo_name):
1602 def get_new_name(self, repo_name):
1556 """
1603 """
1557 returns new full repository name based on assigned group and new new
1604 returns new full repository name based on assigned group and new new
1558
1605
1559 :param group_name:
1606 :param group_name:
1560 """
1607 """
1561 path_prefix = self.group.full_path_splitted if self.group else []
1608 path_prefix = self.group.full_path_splitted if self.group else []
1562 return self.NAME_SEP.join(path_prefix + [repo_name])
1609 return self.NAME_SEP.join(path_prefix + [repo_name])
1563
1610
1564 @property
1611 @property
1565 def _config(self):
1612 def _config(self):
1566 """
1613 """
1567 Returns db based config object.
1614 Returns db based config object.
1568 """
1615 """
1569 from rhodecode.lib.utils import make_db_config
1616 from rhodecode.lib.utils import make_db_config
1570 return make_db_config(clear_session=False, repo=self)
1617 return make_db_config(clear_session=False, repo=self)
1571
1618
1572 def permissions(self, with_admins=True, with_owner=True):
1619 def permissions(self, with_admins=True, with_owner=True):
1573 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1620 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1574 q = q.options(joinedload(UserRepoToPerm.repository),
1621 q = q.options(joinedload(UserRepoToPerm.repository),
1575 joinedload(UserRepoToPerm.user),
1622 joinedload(UserRepoToPerm.user),
1576 joinedload(UserRepoToPerm.permission),)
1623 joinedload(UserRepoToPerm.permission),)
1577
1624
1578 # get owners and admins and permissions. We do a trick of re-writing
1625 # get owners and admins and permissions. We do a trick of re-writing
1579 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1626 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1580 # has a global reference and changing one object propagates to all
1627 # has a global reference and changing one object propagates to all
1581 # others. This means if admin is also an owner admin_row that change
1628 # others. This means if admin is also an owner admin_row that change
1582 # would propagate to both objects
1629 # would propagate to both objects
1583 perm_rows = []
1630 perm_rows = []
1584 for _usr in q.all():
1631 for _usr in q.all():
1585 usr = AttributeDict(_usr.user.get_dict())
1632 usr = AttributeDict(_usr.user.get_dict())
1586 usr.permission = _usr.permission.permission_name
1633 usr.permission = _usr.permission.permission_name
1587 perm_rows.append(usr)
1634 perm_rows.append(usr)
1588
1635
1589 # filter the perm rows by 'default' first and then sort them by
1636 # filter the perm rows by 'default' first and then sort them by
1590 # admin,write,read,none permissions sorted again alphabetically in
1637 # admin,write,read,none permissions sorted again alphabetically in
1591 # each group
1638 # each group
1592 perm_rows = sorted(perm_rows, key=display_sort)
1639 perm_rows = sorted(perm_rows, key=display_sort)
1593
1640
1594 _admin_perm = 'repository.admin'
1641 _admin_perm = 'repository.admin'
1595 owner_row = []
1642 owner_row = []
1596 if with_owner:
1643 if with_owner:
1597 usr = AttributeDict(self.user.get_dict())
1644 usr = AttributeDict(self.user.get_dict())
1598 usr.owner_row = True
1645 usr.owner_row = True
1599 usr.permission = _admin_perm
1646 usr.permission = _admin_perm
1600 owner_row.append(usr)
1647 owner_row.append(usr)
1601
1648
1602 super_admin_rows = []
1649 super_admin_rows = []
1603 if with_admins:
1650 if with_admins:
1604 for usr in User.get_all_super_admins():
1651 for usr in User.get_all_super_admins():
1605 # if this admin is also owner, don't double the record
1652 # if this admin is also owner, don't double the record
1606 if usr.user_id == owner_row[0].user_id:
1653 if usr.user_id == owner_row[0].user_id:
1607 owner_row[0].admin_row = True
1654 owner_row[0].admin_row = True
1608 else:
1655 else:
1609 usr = AttributeDict(usr.get_dict())
1656 usr = AttributeDict(usr.get_dict())
1610 usr.admin_row = True
1657 usr.admin_row = True
1611 usr.permission = _admin_perm
1658 usr.permission = _admin_perm
1612 super_admin_rows.append(usr)
1659 super_admin_rows.append(usr)
1613
1660
1614 return super_admin_rows + owner_row + perm_rows
1661 return super_admin_rows + owner_row + perm_rows
1615
1662
1616 def permission_user_groups(self):
1663 def permission_user_groups(self):
1617 q = UserGroupRepoToPerm.query().filter(
1664 q = UserGroupRepoToPerm.query().filter(
1618 UserGroupRepoToPerm.repository == self)
1665 UserGroupRepoToPerm.repository == self)
1619 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1666 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1620 joinedload(UserGroupRepoToPerm.users_group),
1667 joinedload(UserGroupRepoToPerm.users_group),
1621 joinedload(UserGroupRepoToPerm.permission),)
1668 joinedload(UserGroupRepoToPerm.permission),)
1622
1669
1623 perm_rows = []
1670 perm_rows = []
1624 for _user_group in q.all():
1671 for _user_group in q.all():
1625 usr = AttributeDict(_user_group.users_group.get_dict())
1672 usr = AttributeDict(_user_group.users_group.get_dict())
1626 usr.permission = _user_group.permission.permission_name
1673 usr.permission = _user_group.permission.permission_name
1627 perm_rows.append(usr)
1674 perm_rows.append(usr)
1628
1675
1629 return perm_rows
1676 return perm_rows
1630
1677
1631 def get_api_data(self, include_secrets=False):
1678 def get_api_data(self, include_secrets=False):
1632 """
1679 """
1633 Common function for generating repo api data
1680 Common function for generating repo api data
1634
1681
1635 :param include_secrets: See :meth:`User.get_api_data`.
1682 :param include_secrets: See :meth:`User.get_api_data`.
1636
1683
1637 """
1684 """
1638 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1685 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1639 # move this methods on models level.
1686 # move this methods on models level.
1640 from rhodecode.model.settings import SettingsModel
1687 from rhodecode.model.settings import SettingsModel
1641
1688
1642 repo = self
1689 repo = self
1643 _user_id, _time, _reason = self.locked
1690 _user_id, _time, _reason = self.locked
1644
1691
1645 data = {
1692 data = {
1646 'repo_id': repo.repo_id,
1693 'repo_id': repo.repo_id,
1647 'repo_name': repo.repo_name,
1694 'repo_name': repo.repo_name,
1648 'repo_type': repo.repo_type,
1695 'repo_type': repo.repo_type,
1649 'clone_uri': repo.clone_uri or '',
1696 'clone_uri': repo.clone_uri or '',
1650 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1697 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1651 'private': repo.private,
1698 'private': repo.private,
1652 'created_on': repo.created_on,
1699 'created_on': repo.created_on,
1653 'description': repo.description,
1700 'description': repo.description,
1654 'landing_rev': repo.landing_rev,
1701 'landing_rev': repo.landing_rev,
1655 'owner': repo.user.username,
1702 'owner': repo.user.username,
1656 'fork_of': repo.fork.repo_name if repo.fork else None,
1703 'fork_of': repo.fork.repo_name if repo.fork else None,
1657 'enable_statistics': repo.enable_statistics,
1704 'enable_statistics': repo.enable_statistics,
1658 'enable_locking': repo.enable_locking,
1705 'enable_locking': repo.enable_locking,
1659 'enable_downloads': repo.enable_downloads,
1706 'enable_downloads': repo.enable_downloads,
1660 'last_changeset': repo.changeset_cache,
1707 'last_changeset': repo.changeset_cache,
1661 'locked_by': User.get(_user_id).get_api_data(
1708 'locked_by': User.get(_user_id).get_api_data(
1662 include_secrets=include_secrets) if _user_id else None,
1709 include_secrets=include_secrets) if _user_id else None,
1663 'locked_date': time_to_datetime(_time) if _time else None,
1710 'locked_date': time_to_datetime(_time) if _time else None,
1664 'lock_reason': _reason if _reason else None,
1711 'lock_reason': _reason if _reason else None,
1665 }
1712 }
1666
1713
1667 # TODO: mikhail: should be per-repo settings here
1714 # TODO: mikhail: should be per-repo settings here
1668 rc_config = SettingsModel().get_all_settings()
1715 rc_config = SettingsModel().get_all_settings()
1669 repository_fields = str2bool(
1716 repository_fields = str2bool(
1670 rc_config.get('rhodecode_repository_fields'))
1717 rc_config.get('rhodecode_repository_fields'))
1671 if repository_fields:
1718 if repository_fields:
1672 for f in self.extra_fields:
1719 for f in self.extra_fields:
1673 data[f.field_key_prefixed] = f.field_value
1720 data[f.field_key_prefixed] = f.field_value
1674
1721
1675 return data
1722 return data
1676
1723
1677 @classmethod
1724 @classmethod
1678 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1725 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1679 if not lock_time:
1726 if not lock_time:
1680 lock_time = time.time()
1727 lock_time = time.time()
1681 if not lock_reason:
1728 if not lock_reason:
1682 lock_reason = cls.LOCK_AUTOMATIC
1729 lock_reason = cls.LOCK_AUTOMATIC
1683 repo.locked = [user_id, lock_time, lock_reason]
1730 repo.locked = [user_id, lock_time, lock_reason]
1684 Session().add(repo)
1731 Session().add(repo)
1685 Session().commit()
1732 Session().commit()
1686
1733
1687 @classmethod
1734 @classmethod
1688 def unlock(cls, repo):
1735 def unlock(cls, repo):
1689 repo.locked = None
1736 repo.locked = None
1690 Session().add(repo)
1737 Session().add(repo)
1691 Session().commit()
1738 Session().commit()
1692
1739
1693 @classmethod
1740 @classmethod
1694 def getlock(cls, repo):
1741 def getlock(cls, repo):
1695 return repo.locked
1742 return repo.locked
1696
1743
1697 def is_user_lock(self, user_id):
1744 def is_user_lock(self, user_id):
1698 if self.lock[0]:
1745 if self.lock[0]:
1699 lock_user_id = safe_int(self.lock[0])
1746 lock_user_id = safe_int(self.lock[0])
1700 user_id = safe_int(user_id)
1747 user_id = safe_int(user_id)
1701 # both are ints, and they are equal
1748 # both are ints, and they are equal
1702 return all([lock_user_id, user_id]) and lock_user_id == user_id
1749 return all([lock_user_id, user_id]) and lock_user_id == user_id
1703
1750
1704 return False
1751 return False
1705
1752
1706 def get_locking_state(self, action, user_id, only_when_enabled=True):
1753 def get_locking_state(self, action, user_id, only_when_enabled=True):
1707 """
1754 """
1708 Checks locking on this repository, if locking is enabled and lock is
1755 Checks locking on this repository, if locking is enabled and lock is
1709 present returns a tuple of make_lock, locked, locked_by.
1756 present returns a tuple of make_lock, locked, locked_by.
1710 make_lock can have 3 states None (do nothing) True, make lock
1757 make_lock can have 3 states None (do nothing) True, make lock
1711 False release lock, This value is later propagated to hooks, which
1758 False release lock, This value is later propagated to hooks, which
1712 do the locking. Think about this as signals passed to hooks what to do.
1759 do the locking. Think about this as signals passed to hooks what to do.
1713
1760
1714 """
1761 """
1715 # TODO: johbo: This is part of the business logic and should be moved
1762 # TODO: johbo: This is part of the business logic and should be moved
1716 # into the RepositoryModel.
1763 # into the RepositoryModel.
1717
1764
1718 if action not in ('push', 'pull'):
1765 if action not in ('push', 'pull'):
1719 raise ValueError("Invalid action value: %s" % repr(action))
1766 raise ValueError("Invalid action value: %s" % repr(action))
1720
1767
1721 # defines if locked error should be thrown to user
1768 # defines if locked error should be thrown to user
1722 currently_locked = False
1769 currently_locked = False
1723 # defines if new lock should be made, tri-state
1770 # defines if new lock should be made, tri-state
1724 make_lock = None
1771 make_lock = None
1725 repo = self
1772 repo = self
1726 user = User.get(user_id)
1773 user = User.get(user_id)
1727
1774
1728 lock_info = repo.locked
1775 lock_info = repo.locked
1729
1776
1730 if repo and (repo.enable_locking or not only_when_enabled):
1777 if repo and (repo.enable_locking or not only_when_enabled):
1731 if action == 'push':
1778 if action == 'push':
1732 # check if it's already locked !, if it is compare users
1779 # check if it's already locked !, if it is compare users
1733 locked_by_user_id = lock_info[0]
1780 locked_by_user_id = lock_info[0]
1734 if user.user_id == locked_by_user_id:
1781 if user.user_id == locked_by_user_id:
1735 log.debug(
1782 log.debug(
1736 'Got `push` action from user %s, now unlocking', user)
1783 'Got `push` action from user %s, now unlocking', user)
1737 # unlock if we have push from user who locked
1784 # unlock if we have push from user who locked
1738 make_lock = False
1785 make_lock = False
1739 else:
1786 else:
1740 # we're not the same user who locked, ban with
1787 # we're not the same user who locked, ban with
1741 # code defined in settings (default is 423 HTTP Locked) !
1788 # code defined in settings (default is 423 HTTP Locked) !
1742 log.debug('Repo %s is currently locked by %s', repo, user)
1789 log.debug('Repo %s is currently locked by %s', repo, user)
1743 currently_locked = True
1790 currently_locked = True
1744 elif action == 'pull':
1791 elif action == 'pull':
1745 # [0] user [1] date
1792 # [0] user [1] date
1746 if lock_info[0] and lock_info[1]:
1793 if lock_info[0] and lock_info[1]:
1747 log.debug('Repo %s is currently locked by %s', repo, user)
1794 log.debug('Repo %s is currently locked by %s', repo, user)
1748 currently_locked = True
1795 currently_locked = True
1749 else:
1796 else:
1750 log.debug('Setting lock on repo %s by %s', repo, user)
1797 log.debug('Setting lock on repo %s by %s', repo, user)
1751 make_lock = True
1798 make_lock = True
1752
1799
1753 else:
1800 else:
1754 log.debug('Repository %s do not have locking enabled', repo)
1801 log.debug('Repository %s do not have locking enabled', repo)
1755
1802
1756 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1803 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1757 make_lock, currently_locked, lock_info)
1804 make_lock, currently_locked, lock_info)
1758
1805
1759 from rhodecode.lib.auth import HasRepoPermissionAny
1806 from rhodecode.lib.auth import HasRepoPermissionAny
1760 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1807 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1761 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1808 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1762 # if we don't have at least write permission we cannot make a lock
1809 # if we don't have at least write permission we cannot make a lock
1763 log.debug('lock state reset back to FALSE due to lack '
1810 log.debug('lock state reset back to FALSE due to lack '
1764 'of at least read permission')
1811 'of at least read permission')
1765 make_lock = False
1812 make_lock = False
1766
1813
1767 return make_lock, currently_locked, lock_info
1814 return make_lock, currently_locked, lock_info
1768
1815
1769 @property
1816 @property
1770 def last_db_change(self):
1817 def last_db_change(self):
1771 return self.updated_on
1818 return self.updated_on
1772
1819
1773 @property
1820 @property
1774 def clone_uri_hidden(self):
1821 def clone_uri_hidden(self):
1775 clone_uri = self.clone_uri
1822 clone_uri = self.clone_uri
1776 if clone_uri:
1823 if clone_uri:
1777 import urlobject
1824 import urlobject
1778 url_obj = urlobject.URLObject(clone_uri)
1825 url_obj = urlobject.URLObject(clone_uri)
1779 if url_obj.password:
1826 if url_obj.password:
1780 clone_uri = url_obj.with_password('*****')
1827 clone_uri = url_obj.with_password('*****')
1781 return clone_uri
1828 return clone_uri
1782
1829
1783 def clone_url(self, **override):
1830 def clone_url(self, **override):
1784 qualified_home_url = url('home', qualified=True)
1831 qualified_home_url = url('home', qualified=True)
1785
1832
1786 uri_tmpl = None
1833 uri_tmpl = None
1787 if 'with_id' in override:
1834 if 'with_id' in override:
1788 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1835 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1789 del override['with_id']
1836 del override['with_id']
1790
1837
1791 if 'uri_tmpl' in override:
1838 if 'uri_tmpl' in override:
1792 uri_tmpl = override['uri_tmpl']
1839 uri_tmpl = override['uri_tmpl']
1793 del override['uri_tmpl']
1840 del override['uri_tmpl']
1794
1841
1795 # we didn't override our tmpl from **overrides
1842 # we didn't override our tmpl from **overrides
1796 if not uri_tmpl:
1843 if not uri_tmpl:
1797 uri_tmpl = self.DEFAULT_CLONE_URI
1844 uri_tmpl = self.DEFAULT_CLONE_URI
1798 try:
1845 try:
1799 from pylons import tmpl_context as c
1846 from pylons import tmpl_context as c
1800 uri_tmpl = c.clone_uri_tmpl
1847 uri_tmpl = c.clone_uri_tmpl
1801 except Exception:
1848 except Exception:
1802 # in any case if we call this outside of request context,
1849 # in any case if we call this outside of request context,
1803 # ie, not having tmpl_context set up
1850 # ie, not having tmpl_context set up
1804 pass
1851 pass
1805
1852
1806 return get_clone_url(uri_tmpl=uri_tmpl,
1853 return get_clone_url(uri_tmpl=uri_tmpl,
1807 qualifed_home_url=qualified_home_url,
1854 qualifed_home_url=qualified_home_url,
1808 repo_name=self.repo_name,
1855 repo_name=self.repo_name,
1809 repo_id=self.repo_id, **override)
1856 repo_id=self.repo_id, **override)
1810
1857
1811 def set_state(self, state):
1858 def set_state(self, state):
1812 self.repo_state = state
1859 self.repo_state = state
1813 Session().add(self)
1860 Session().add(self)
1814 #==========================================================================
1861 #==========================================================================
1815 # SCM PROPERTIES
1862 # SCM PROPERTIES
1816 #==========================================================================
1863 #==========================================================================
1817
1864
1818 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1865 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1819 return get_commit_safe(
1866 return get_commit_safe(
1820 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1867 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1821
1868
1822 def get_changeset(self, rev=None, pre_load=None):
1869 def get_changeset(self, rev=None, pre_load=None):
1823 warnings.warn("Use get_commit", DeprecationWarning)
1870 warnings.warn("Use get_commit", DeprecationWarning)
1824 commit_id = None
1871 commit_id = None
1825 commit_idx = None
1872 commit_idx = None
1826 if isinstance(rev, basestring):
1873 if isinstance(rev, basestring):
1827 commit_id = rev
1874 commit_id = rev
1828 else:
1875 else:
1829 commit_idx = rev
1876 commit_idx = rev
1830 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1877 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1831 pre_load=pre_load)
1878 pre_load=pre_load)
1832
1879
1833 def get_landing_commit(self):
1880 def get_landing_commit(self):
1834 """
1881 """
1835 Returns landing commit, or if that doesn't exist returns the tip
1882 Returns landing commit, or if that doesn't exist returns the tip
1836 """
1883 """
1837 _rev_type, _rev = self.landing_rev
1884 _rev_type, _rev = self.landing_rev
1838 commit = self.get_commit(_rev)
1885 commit = self.get_commit(_rev)
1839 if isinstance(commit, EmptyCommit):
1886 if isinstance(commit, EmptyCommit):
1840 return self.get_commit()
1887 return self.get_commit()
1841 return commit
1888 return commit
1842
1889
1843 def update_commit_cache(self, cs_cache=None, config=None):
1890 def update_commit_cache(self, cs_cache=None, config=None):
1844 """
1891 """
1845 Update cache of last changeset for repository, keys should be::
1892 Update cache of last changeset for repository, keys should be::
1846
1893
1847 short_id
1894 short_id
1848 raw_id
1895 raw_id
1849 revision
1896 revision
1850 parents
1897 parents
1851 message
1898 message
1852 date
1899 date
1853 author
1900 author
1854
1901
1855 :param cs_cache:
1902 :param cs_cache:
1856 """
1903 """
1857 from rhodecode.lib.vcs.backends.base import BaseChangeset
1904 from rhodecode.lib.vcs.backends.base import BaseChangeset
1858 if cs_cache is None:
1905 if cs_cache is None:
1859 # use no-cache version here
1906 # use no-cache version here
1860 scm_repo = self.scm_instance(cache=False, config=config)
1907 scm_repo = self.scm_instance(cache=False, config=config)
1861 if scm_repo:
1908 if scm_repo:
1862 cs_cache = scm_repo.get_commit(
1909 cs_cache = scm_repo.get_commit(
1863 pre_load=["author", "date", "message", "parents"])
1910 pre_load=["author", "date", "message", "parents"])
1864 else:
1911 else:
1865 cs_cache = EmptyCommit()
1912 cs_cache = EmptyCommit()
1866
1913
1867 if isinstance(cs_cache, BaseChangeset):
1914 if isinstance(cs_cache, BaseChangeset):
1868 cs_cache = cs_cache.__json__()
1915 cs_cache = cs_cache.__json__()
1869
1916
1870 def is_outdated(new_cs_cache):
1917 def is_outdated(new_cs_cache):
1871 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1918 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1872 new_cs_cache['revision'] != self.changeset_cache['revision']):
1919 new_cs_cache['revision'] != self.changeset_cache['revision']):
1873 return True
1920 return True
1874 return False
1921 return False
1875
1922
1876 # check if we have maybe already latest cached revision
1923 # check if we have maybe already latest cached revision
1877 if is_outdated(cs_cache) or not self.changeset_cache:
1924 if is_outdated(cs_cache) or not self.changeset_cache:
1878 _default = datetime.datetime.fromtimestamp(0)
1925 _default = datetime.datetime.fromtimestamp(0)
1879 last_change = cs_cache.get('date') or _default
1926 last_change = cs_cache.get('date') or _default
1880 log.debug('updated repo %s with new cs cache %s',
1927 log.debug('updated repo %s with new cs cache %s',
1881 self.repo_name, cs_cache)
1928 self.repo_name, cs_cache)
1882 self.updated_on = last_change
1929 self.updated_on = last_change
1883 self.changeset_cache = cs_cache
1930 self.changeset_cache = cs_cache
1884 Session().add(self)
1931 Session().add(self)
1885 Session().commit()
1932 Session().commit()
1886 else:
1933 else:
1887 log.debug('Skipping update_commit_cache for repo:`%s` '
1934 log.debug('Skipping update_commit_cache for repo:`%s` '
1888 'commit already with latest changes', self.repo_name)
1935 'commit already with latest changes', self.repo_name)
1889
1936
1890 @property
1937 @property
1891 def tip(self):
1938 def tip(self):
1892 return self.get_commit('tip')
1939 return self.get_commit('tip')
1893
1940
1894 @property
1941 @property
1895 def author(self):
1942 def author(self):
1896 return self.tip.author
1943 return self.tip.author
1897
1944
1898 @property
1945 @property
1899 def last_change(self):
1946 def last_change(self):
1900 return self.scm_instance().last_change
1947 return self.scm_instance().last_change
1901
1948
1902 def get_comments(self, revisions=None):
1949 def get_comments(self, revisions=None):
1903 """
1950 """
1904 Returns comments for this repository grouped by revisions
1951 Returns comments for this repository grouped by revisions
1905
1952
1906 :param revisions: filter query by revisions only
1953 :param revisions: filter query by revisions only
1907 """
1954 """
1908 cmts = ChangesetComment.query()\
1955 cmts = ChangesetComment.query()\
1909 .filter(ChangesetComment.repo == self)
1956 .filter(ChangesetComment.repo == self)
1910 if revisions:
1957 if revisions:
1911 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1958 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1912 grouped = collections.defaultdict(list)
1959 grouped = collections.defaultdict(list)
1913 for cmt in cmts.all():
1960 for cmt in cmts.all():
1914 grouped[cmt.revision].append(cmt)
1961 grouped[cmt.revision].append(cmt)
1915 return grouped
1962 return grouped
1916
1963
1917 def statuses(self, revisions=None):
1964 def statuses(self, revisions=None):
1918 """
1965 """
1919 Returns statuses for this repository
1966 Returns statuses for this repository
1920
1967
1921 :param revisions: list of revisions to get statuses for
1968 :param revisions: list of revisions to get statuses for
1922 """
1969 """
1923 statuses = ChangesetStatus.query()\
1970 statuses = ChangesetStatus.query()\
1924 .filter(ChangesetStatus.repo == self)\
1971 .filter(ChangesetStatus.repo == self)\
1925 .filter(ChangesetStatus.version == 0)
1972 .filter(ChangesetStatus.version == 0)
1926
1973
1927 if revisions:
1974 if revisions:
1928 # Try doing the filtering in chunks to avoid hitting limits
1975 # Try doing the filtering in chunks to avoid hitting limits
1929 size = 500
1976 size = 500
1930 status_results = []
1977 status_results = []
1931 for chunk in xrange(0, len(revisions), size):
1978 for chunk in xrange(0, len(revisions), size):
1932 status_results += statuses.filter(
1979 status_results += statuses.filter(
1933 ChangesetStatus.revision.in_(
1980 ChangesetStatus.revision.in_(
1934 revisions[chunk: chunk+size])
1981 revisions[chunk: chunk+size])
1935 ).all()
1982 ).all()
1936 else:
1983 else:
1937 status_results = statuses.all()
1984 status_results = statuses.all()
1938
1985
1939 grouped = {}
1986 grouped = {}
1940
1987
1941 # maybe we have open new pullrequest without a status?
1988 # maybe we have open new pullrequest without a status?
1942 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1989 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1943 status_lbl = ChangesetStatus.get_status_lbl(stat)
1990 status_lbl = ChangesetStatus.get_status_lbl(stat)
1944 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1991 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1945 for rev in pr.revisions:
1992 for rev in pr.revisions:
1946 pr_id = pr.pull_request_id
1993 pr_id = pr.pull_request_id
1947 pr_repo = pr.target_repo.repo_name
1994 pr_repo = pr.target_repo.repo_name
1948 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1995 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1949
1996
1950 for stat in status_results:
1997 for stat in status_results:
1951 pr_id = pr_repo = None
1998 pr_id = pr_repo = None
1952 if stat.pull_request:
1999 if stat.pull_request:
1953 pr_id = stat.pull_request.pull_request_id
2000 pr_id = stat.pull_request.pull_request_id
1954 pr_repo = stat.pull_request.target_repo.repo_name
2001 pr_repo = stat.pull_request.target_repo.repo_name
1955 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2002 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1956 pr_id, pr_repo]
2003 pr_id, pr_repo]
1957 return grouped
2004 return grouped
1958
2005
1959 # ==========================================================================
2006 # ==========================================================================
1960 # SCM CACHE INSTANCE
2007 # SCM CACHE INSTANCE
1961 # ==========================================================================
2008 # ==========================================================================
1962
2009
1963 def scm_instance(self, **kwargs):
2010 def scm_instance(self, **kwargs):
1964 import rhodecode
2011 import rhodecode
1965
2012
1966 # Passing a config will not hit the cache currently only used
2013 # Passing a config will not hit the cache currently only used
1967 # for repo2dbmapper
2014 # for repo2dbmapper
1968 config = kwargs.pop('config', None)
2015 config = kwargs.pop('config', None)
1969 cache = kwargs.pop('cache', None)
2016 cache = kwargs.pop('cache', None)
1970 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2017 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1971 # if cache is NOT defined use default global, else we have a full
2018 # if cache is NOT defined use default global, else we have a full
1972 # control over cache behaviour
2019 # control over cache behaviour
1973 if cache is None and full_cache and not config:
2020 if cache is None and full_cache and not config:
1974 return self._get_instance_cached()
2021 return self._get_instance_cached()
1975 return self._get_instance(cache=bool(cache), config=config)
2022 return self._get_instance(cache=bool(cache), config=config)
1976
2023
1977 def _get_instance_cached(self):
2024 def _get_instance_cached(self):
1978 @cache_region('long_term')
2025 @cache_region('long_term')
1979 def _get_repo(cache_key):
2026 def _get_repo(cache_key):
1980 return self._get_instance()
2027 return self._get_instance()
1981
2028
1982 invalidator_context = CacheKey.repo_context_cache(
2029 invalidator_context = CacheKey.repo_context_cache(
1983 _get_repo, self.repo_name, None, thread_scoped=True)
2030 _get_repo, self.repo_name, None, thread_scoped=True)
1984
2031
1985 with invalidator_context as context:
2032 with invalidator_context as context:
1986 context.invalidate()
2033 context.invalidate()
1987 repo = context.compute()
2034 repo = context.compute()
1988
2035
1989 return repo
2036 return repo
1990
2037
1991 def _get_instance(self, cache=True, config=None):
2038 def _get_instance(self, cache=True, config=None):
1992 config = config or self._config
2039 config = config or self._config
1993 custom_wire = {
2040 custom_wire = {
1994 'cache': cache # controls the vcs.remote cache
2041 'cache': cache # controls the vcs.remote cache
1995 }
2042 }
1996 repo = get_vcs_instance(
2043 repo = get_vcs_instance(
1997 repo_path=safe_str(self.repo_full_path),
2044 repo_path=safe_str(self.repo_full_path),
1998 config=config,
2045 config=config,
1999 with_wire=custom_wire,
2046 with_wire=custom_wire,
2000 create=False,
2047 create=False,
2001 _vcs_alias=self.repo_type)
2048 _vcs_alias=self.repo_type)
2002
2049
2003 return repo
2050 return repo
2004
2051
2005 def __json__(self):
2052 def __json__(self):
2006 return {'landing_rev': self.landing_rev}
2053 return {'landing_rev': self.landing_rev}
2007
2054
2008 def get_dict(self):
2055 def get_dict(self):
2009
2056
2010 # Since we transformed `repo_name` to a hybrid property, we need to
2057 # Since we transformed `repo_name` to a hybrid property, we need to
2011 # keep compatibility with the code which uses `repo_name` field.
2058 # keep compatibility with the code which uses `repo_name` field.
2012
2059
2013 result = super(Repository, self).get_dict()
2060 result = super(Repository, self).get_dict()
2014 result['repo_name'] = result.pop('_repo_name', None)
2061 result['repo_name'] = result.pop('_repo_name', None)
2015 return result
2062 return result
2016
2063
2017
2064
2018 class RepoGroup(Base, BaseModel):
2065 class RepoGroup(Base, BaseModel):
2019 __tablename__ = 'groups'
2066 __tablename__ = 'groups'
2020 __table_args__ = (
2067 __table_args__ = (
2021 UniqueConstraint('group_name', 'group_parent_id'),
2068 UniqueConstraint('group_name', 'group_parent_id'),
2022 CheckConstraint('group_id != group_parent_id'),
2069 CheckConstraint('group_id != group_parent_id'),
2023 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2070 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2024 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2071 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2025 )
2072 )
2026 __mapper_args__ = {'order_by': 'group_name'}
2073 __mapper_args__ = {'order_by': 'group_name'}
2027
2074
2028 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2075 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2029
2076
2030 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2077 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2031 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2078 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2032 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2079 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2033 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2080 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2034 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2081 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2035 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2082 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2036 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2083 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2037 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2084 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2038
2085
2039 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2086 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2040 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2087 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2041 parent_group = relationship('RepoGroup', remote_side=group_id)
2088 parent_group = relationship('RepoGroup', remote_side=group_id)
2042 user = relationship('User')
2089 user = relationship('User')
2043 integrations = relationship('Integration',
2090 integrations = relationship('Integration',
2044 cascade="all, delete, delete-orphan")
2091 cascade="all, delete, delete-orphan")
2045
2092
2046 def __init__(self, group_name='', parent_group=None):
2093 def __init__(self, group_name='', parent_group=None):
2047 self.group_name = group_name
2094 self.group_name = group_name
2048 self.parent_group = parent_group
2095 self.parent_group = parent_group
2049
2096
2050 def __unicode__(self):
2097 def __unicode__(self):
2051 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2098 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2052 self.group_name)
2099 self.group_name)
2053
2100
2054 @classmethod
2101 @classmethod
2055 def _generate_choice(cls, repo_group):
2102 def _generate_choice(cls, repo_group):
2056 from webhelpers.html import literal as _literal
2103 from webhelpers.html import literal as _literal
2057 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2104 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2058 return repo_group.group_id, _name(repo_group.full_path_splitted)
2105 return repo_group.group_id, _name(repo_group.full_path_splitted)
2059
2106
2060 @classmethod
2107 @classmethod
2061 def groups_choices(cls, groups=None, show_empty_group=True):
2108 def groups_choices(cls, groups=None, show_empty_group=True):
2062 if not groups:
2109 if not groups:
2063 groups = cls.query().all()
2110 groups = cls.query().all()
2064
2111
2065 repo_groups = []
2112 repo_groups = []
2066 if show_empty_group:
2113 if show_empty_group:
2067 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2114 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2068
2115
2069 repo_groups.extend([cls._generate_choice(x) for x in groups])
2116 repo_groups.extend([cls._generate_choice(x) for x in groups])
2070
2117
2071 repo_groups = sorted(
2118 repo_groups = sorted(
2072 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2119 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2073 return repo_groups
2120 return repo_groups
2074
2121
2075 @classmethod
2122 @classmethod
2076 def url_sep(cls):
2123 def url_sep(cls):
2077 return URL_SEP
2124 return URL_SEP
2078
2125
2079 @classmethod
2126 @classmethod
2080 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2127 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2081 if case_insensitive:
2128 if case_insensitive:
2082 gr = cls.query().filter(func.lower(cls.group_name)
2129 gr = cls.query().filter(func.lower(cls.group_name)
2083 == func.lower(group_name))
2130 == func.lower(group_name))
2084 else:
2131 else:
2085 gr = cls.query().filter(cls.group_name == group_name)
2132 gr = cls.query().filter(cls.group_name == group_name)
2086 if cache:
2133 if cache:
2087 gr = gr.options(FromCache(
2134 gr = gr.options(FromCache(
2088 "sql_cache_short",
2135 "sql_cache_short",
2089 "get_group_%s" % _hash_key(group_name)))
2136 "get_group_%s" % _hash_key(group_name)))
2090 return gr.scalar()
2137 return gr.scalar()
2091
2138
2092 @classmethod
2139 @classmethod
2093 def get_user_personal_repo_group(cls, user_id):
2140 def get_user_personal_repo_group(cls, user_id):
2094 user = User.get(user_id)
2141 user = User.get(user_id)
2095 return cls.query()\
2142 return cls.query()\
2096 .filter(cls.personal == true())\
2143 .filter(cls.personal == true())\
2097 .filter(cls.user == user).scalar()
2144 .filter(cls.user == user).scalar()
2098
2145
2099 @classmethod
2146 @classmethod
2100 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2147 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2101 case_insensitive=True):
2148 case_insensitive=True):
2102 q = RepoGroup.query()
2149 q = RepoGroup.query()
2103
2150
2104 if not isinstance(user_id, Optional):
2151 if not isinstance(user_id, Optional):
2105 q = q.filter(RepoGroup.user_id == user_id)
2152 q = q.filter(RepoGroup.user_id == user_id)
2106
2153
2107 if not isinstance(group_id, Optional):
2154 if not isinstance(group_id, Optional):
2108 q = q.filter(RepoGroup.group_parent_id == group_id)
2155 q = q.filter(RepoGroup.group_parent_id == group_id)
2109
2156
2110 if case_insensitive:
2157 if case_insensitive:
2111 q = q.order_by(func.lower(RepoGroup.group_name))
2158 q = q.order_by(func.lower(RepoGroup.group_name))
2112 else:
2159 else:
2113 q = q.order_by(RepoGroup.group_name)
2160 q = q.order_by(RepoGroup.group_name)
2114 return q.all()
2161 return q.all()
2115
2162
2116 @property
2163 @property
2117 def parents(self):
2164 def parents(self):
2118 parents_recursion_limit = 10
2165 parents_recursion_limit = 10
2119 groups = []
2166 groups = []
2120 if self.parent_group is None:
2167 if self.parent_group is None:
2121 return groups
2168 return groups
2122 cur_gr = self.parent_group
2169 cur_gr = self.parent_group
2123 groups.insert(0, cur_gr)
2170 groups.insert(0, cur_gr)
2124 cnt = 0
2171 cnt = 0
2125 while 1:
2172 while 1:
2126 cnt += 1
2173 cnt += 1
2127 gr = getattr(cur_gr, 'parent_group', None)
2174 gr = getattr(cur_gr, 'parent_group', None)
2128 cur_gr = cur_gr.parent_group
2175 cur_gr = cur_gr.parent_group
2129 if gr is None:
2176 if gr is None:
2130 break
2177 break
2131 if cnt == parents_recursion_limit:
2178 if cnt == parents_recursion_limit:
2132 # this will prevent accidental infinit loops
2179 # this will prevent accidental infinit loops
2133 log.error(('more than %s parents found for group %s, stopping '
2180 log.error(('more than %s parents found for group %s, stopping '
2134 'recursive parent fetching' % (parents_recursion_limit, self)))
2181 'recursive parent fetching' % (parents_recursion_limit, self)))
2135 break
2182 break
2136
2183
2137 groups.insert(0, gr)
2184 groups.insert(0, gr)
2138 return groups
2185 return groups
2139
2186
2140 @property
2187 @property
2141 def children(self):
2188 def children(self):
2142 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2189 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2143
2190
2144 @property
2191 @property
2145 def name(self):
2192 def name(self):
2146 return self.group_name.split(RepoGroup.url_sep())[-1]
2193 return self.group_name.split(RepoGroup.url_sep())[-1]
2147
2194
2148 @property
2195 @property
2149 def full_path(self):
2196 def full_path(self):
2150 return self.group_name
2197 return self.group_name
2151
2198
2152 @property
2199 @property
2153 def full_path_splitted(self):
2200 def full_path_splitted(self):
2154 return self.group_name.split(RepoGroup.url_sep())
2201 return self.group_name.split(RepoGroup.url_sep())
2155
2202
2156 @property
2203 @property
2157 def repositories(self):
2204 def repositories(self):
2158 return Repository.query()\
2205 return Repository.query()\
2159 .filter(Repository.group == self)\
2206 .filter(Repository.group == self)\
2160 .order_by(Repository.repo_name)
2207 .order_by(Repository.repo_name)
2161
2208
2162 @property
2209 @property
2163 def repositories_recursive_count(self):
2210 def repositories_recursive_count(self):
2164 cnt = self.repositories.count()
2211 cnt = self.repositories.count()
2165
2212
2166 def children_count(group):
2213 def children_count(group):
2167 cnt = 0
2214 cnt = 0
2168 for child in group.children:
2215 for child in group.children:
2169 cnt += child.repositories.count()
2216 cnt += child.repositories.count()
2170 cnt += children_count(child)
2217 cnt += children_count(child)
2171 return cnt
2218 return cnt
2172
2219
2173 return cnt + children_count(self)
2220 return cnt + children_count(self)
2174
2221
2175 def _recursive_objects(self, include_repos=True):
2222 def _recursive_objects(self, include_repos=True):
2176 all_ = []
2223 all_ = []
2177
2224
2178 def _get_members(root_gr):
2225 def _get_members(root_gr):
2179 if include_repos:
2226 if include_repos:
2180 for r in root_gr.repositories:
2227 for r in root_gr.repositories:
2181 all_.append(r)
2228 all_.append(r)
2182 childs = root_gr.children.all()
2229 childs = root_gr.children.all()
2183 if childs:
2230 if childs:
2184 for gr in childs:
2231 for gr in childs:
2185 all_.append(gr)
2232 all_.append(gr)
2186 _get_members(gr)
2233 _get_members(gr)
2187
2234
2188 _get_members(self)
2235 _get_members(self)
2189 return [self] + all_
2236 return [self] + all_
2190
2237
2191 def recursive_groups_and_repos(self):
2238 def recursive_groups_and_repos(self):
2192 """
2239 """
2193 Recursive return all groups, with repositories in those groups
2240 Recursive return all groups, with repositories in those groups
2194 """
2241 """
2195 return self._recursive_objects()
2242 return self._recursive_objects()
2196
2243
2197 def recursive_groups(self):
2244 def recursive_groups(self):
2198 """
2245 """
2199 Returns all children groups for this group including children of children
2246 Returns all children groups for this group including children of children
2200 """
2247 """
2201 return self._recursive_objects(include_repos=False)
2248 return self._recursive_objects(include_repos=False)
2202
2249
2203 def get_new_name(self, group_name):
2250 def get_new_name(self, group_name):
2204 """
2251 """
2205 returns new full group name based on parent and new name
2252 returns new full group name based on parent and new name
2206
2253
2207 :param group_name:
2254 :param group_name:
2208 """
2255 """
2209 path_prefix = (self.parent_group.full_path_splitted if
2256 path_prefix = (self.parent_group.full_path_splitted if
2210 self.parent_group else [])
2257 self.parent_group else [])
2211 return RepoGroup.url_sep().join(path_prefix + [group_name])
2258 return RepoGroup.url_sep().join(path_prefix + [group_name])
2212
2259
2213 def permissions(self, with_admins=True, with_owner=True):
2260 def permissions(self, with_admins=True, with_owner=True):
2214 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2261 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2215 q = q.options(joinedload(UserRepoGroupToPerm.group),
2262 q = q.options(joinedload(UserRepoGroupToPerm.group),
2216 joinedload(UserRepoGroupToPerm.user),
2263 joinedload(UserRepoGroupToPerm.user),
2217 joinedload(UserRepoGroupToPerm.permission),)
2264 joinedload(UserRepoGroupToPerm.permission),)
2218
2265
2219 # get owners and admins and permissions. We do a trick of re-writing
2266 # get owners and admins and permissions. We do a trick of re-writing
2220 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2267 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2221 # has a global reference and changing one object propagates to all
2268 # has a global reference and changing one object propagates to all
2222 # others. This means if admin is also an owner admin_row that change
2269 # others. This means if admin is also an owner admin_row that change
2223 # would propagate to both objects
2270 # would propagate to both objects
2224 perm_rows = []
2271 perm_rows = []
2225 for _usr in q.all():
2272 for _usr in q.all():
2226 usr = AttributeDict(_usr.user.get_dict())
2273 usr = AttributeDict(_usr.user.get_dict())
2227 usr.permission = _usr.permission.permission_name
2274 usr.permission = _usr.permission.permission_name
2228 perm_rows.append(usr)
2275 perm_rows.append(usr)
2229
2276
2230 # filter the perm rows by 'default' first and then sort them by
2277 # filter the perm rows by 'default' first and then sort them by
2231 # admin,write,read,none permissions sorted again alphabetically in
2278 # admin,write,read,none permissions sorted again alphabetically in
2232 # each group
2279 # each group
2233 perm_rows = sorted(perm_rows, key=display_sort)
2280 perm_rows = sorted(perm_rows, key=display_sort)
2234
2281
2235 _admin_perm = 'group.admin'
2282 _admin_perm = 'group.admin'
2236 owner_row = []
2283 owner_row = []
2237 if with_owner:
2284 if with_owner:
2238 usr = AttributeDict(self.user.get_dict())
2285 usr = AttributeDict(self.user.get_dict())
2239 usr.owner_row = True
2286 usr.owner_row = True
2240 usr.permission = _admin_perm
2287 usr.permission = _admin_perm
2241 owner_row.append(usr)
2288 owner_row.append(usr)
2242
2289
2243 super_admin_rows = []
2290 super_admin_rows = []
2244 if with_admins:
2291 if with_admins:
2245 for usr in User.get_all_super_admins():
2292 for usr in User.get_all_super_admins():
2246 # if this admin is also owner, don't double the record
2293 # if this admin is also owner, don't double the record
2247 if usr.user_id == owner_row[0].user_id:
2294 if usr.user_id == owner_row[0].user_id:
2248 owner_row[0].admin_row = True
2295 owner_row[0].admin_row = True
2249 else:
2296 else:
2250 usr = AttributeDict(usr.get_dict())
2297 usr = AttributeDict(usr.get_dict())
2251 usr.admin_row = True
2298 usr.admin_row = True
2252 usr.permission = _admin_perm
2299 usr.permission = _admin_perm
2253 super_admin_rows.append(usr)
2300 super_admin_rows.append(usr)
2254
2301
2255 return super_admin_rows + owner_row + perm_rows
2302 return super_admin_rows + owner_row + perm_rows
2256
2303
2257 def permission_user_groups(self):
2304 def permission_user_groups(self):
2258 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2305 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2259 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2306 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2260 joinedload(UserGroupRepoGroupToPerm.users_group),
2307 joinedload(UserGroupRepoGroupToPerm.users_group),
2261 joinedload(UserGroupRepoGroupToPerm.permission),)
2308 joinedload(UserGroupRepoGroupToPerm.permission),)
2262
2309
2263 perm_rows = []
2310 perm_rows = []
2264 for _user_group in q.all():
2311 for _user_group in q.all():
2265 usr = AttributeDict(_user_group.users_group.get_dict())
2312 usr = AttributeDict(_user_group.users_group.get_dict())
2266 usr.permission = _user_group.permission.permission_name
2313 usr.permission = _user_group.permission.permission_name
2267 perm_rows.append(usr)
2314 perm_rows.append(usr)
2268
2315
2269 return perm_rows
2316 return perm_rows
2270
2317
2271 def get_api_data(self):
2318 def get_api_data(self):
2272 """
2319 """
2273 Common function for generating api data
2320 Common function for generating api data
2274
2321
2275 """
2322 """
2276 group = self
2323 group = self
2277 data = {
2324 data = {
2278 'group_id': group.group_id,
2325 'group_id': group.group_id,
2279 'group_name': group.group_name,
2326 'group_name': group.group_name,
2280 'group_description': group.group_description,
2327 'group_description': group.group_description,
2281 'parent_group': group.parent_group.group_name if group.parent_group else None,
2328 'parent_group': group.parent_group.group_name if group.parent_group else None,
2282 'repositories': [x.repo_name for x in group.repositories],
2329 'repositories': [x.repo_name for x in group.repositories],
2283 'owner': group.user.username,
2330 'owner': group.user.username,
2284 }
2331 }
2285 return data
2332 return data
2286
2333
2287
2334
2288 class Permission(Base, BaseModel):
2335 class Permission(Base, BaseModel):
2289 __tablename__ = 'permissions'
2336 __tablename__ = 'permissions'
2290 __table_args__ = (
2337 __table_args__ = (
2291 Index('p_perm_name_idx', 'permission_name'),
2338 Index('p_perm_name_idx', 'permission_name'),
2292 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2339 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2293 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2340 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2294 )
2341 )
2295 PERMS = [
2342 PERMS = [
2296 ('hg.admin', _('RhodeCode Super Administrator')),
2343 ('hg.admin', _('RhodeCode Super Administrator')),
2297
2344
2298 ('repository.none', _('Repository no access')),
2345 ('repository.none', _('Repository no access')),
2299 ('repository.read', _('Repository read access')),
2346 ('repository.read', _('Repository read access')),
2300 ('repository.write', _('Repository write access')),
2347 ('repository.write', _('Repository write access')),
2301 ('repository.admin', _('Repository admin access')),
2348 ('repository.admin', _('Repository admin access')),
2302
2349
2303 ('group.none', _('Repository group no access')),
2350 ('group.none', _('Repository group no access')),
2304 ('group.read', _('Repository group read access')),
2351 ('group.read', _('Repository group read access')),
2305 ('group.write', _('Repository group write access')),
2352 ('group.write', _('Repository group write access')),
2306 ('group.admin', _('Repository group admin access')),
2353 ('group.admin', _('Repository group admin access')),
2307
2354
2308 ('usergroup.none', _('User group no access')),
2355 ('usergroup.none', _('User group no access')),
2309 ('usergroup.read', _('User group read access')),
2356 ('usergroup.read', _('User group read access')),
2310 ('usergroup.write', _('User group write access')),
2357 ('usergroup.write', _('User group write access')),
2311 ('usergroup.admin', _('User group admin access')),
2358 ('usergroup.admin', _('User group admin access')),
2312
2359
2313 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2360 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2314 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2361 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2315
2362
2316 ('hg.usergroup.create.false', _('User Group creation disabled')),
2363 ('hg.usergroup.create.false', _('User Group creation disabled')),
2317 ('hg.usergroup.create.true', _('User Group creation enabled')),
2364 ('hg.usergroup.create.true', _('User Group creation enabled')),
2318
2365
2319 ('hg.create.none', _('Repository creation disabled')),
2366 ('hg.create.none', _('Repository creation disabled')),
2320 ('hg.create.repository', _('Repository creation enabled')),
2367 ('hg.create.repository', _('Repository creation enabled')),
2321 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2368 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2322 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2369 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2323
2370
2324 ('hg.fork.none', _('Repository forking disabled')),
2371 ('hg.fork.none', _('Repository forking disabled')),
2325 ('hg.fork.repository', _('Repository forking enabled')),
2372 ('hg.fork.repository', _('Repository forking enabled')),
2326
2373
2327 ('hg.register.none', _('Registration disabled')),
2374 ('hg.register.none', _('Registration disabled')),
2328 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2375 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2329 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2376 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2330
2377
2331 ('hg.password_reset.enabled', _('Password reset enabled')),
2378 ('hg.password_reset.enabled', _('Password reset enabled')),
2332 ('hg.password_reset.hidden', _('Password reset hidden')),
2379 ('hg.password_reset.hidden', _('Password reset hidden')),
2333 ('hg.password_reset.disabled', _('Password reset disabled')),
2380 ('hg.password_reset.disabled', _('Password reset disabled')),
2334
2381
2335 ('hg.extern_activate.manual', _('Manual activation of external account')),
2382 ('hg.extern_activate.manual', _('Manual activation of external account')),
2336 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2383 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2337
2384
2338 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2385 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2339 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2386 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2340 ]
2387 ]
2341
2388
2342 # definition of system default permissions for DEFAULT user
2389 # definition of system default permissions for DEFAULT user
2343 DEFAULT_USER_PERMISSIONS = [
2390 DEFAULT_USER_PERMISSIONS = [
2344 'repository.read',
2391 'repository.read',
2345 'group.read',
2392 'group.read',
2346 'usergroup.read',
2393 'usergroup.read',
2347 'hg.create.repository',
2394 'hg.create.repository',
2348 'hg.repogroup.create.false',
2395 'hg.repogroup.create.false',
2349 'hg.usergroup.create.false',
2396 'hg.usergroup.create.false',
2350 'hg.create.write_on_repogroup.true',
2397 'hg.create.write_on_repogroup.true',
2351 'hg.fork.repository',
2398 'hg.fork.repository',
2352 'hg.register.manual_activate',
2399 'hg.register.manual_activate',
2353 'hg.password_reset.enabled',
2400 'hg.password_reset.enabled',
2354 'hg.extern_activate.auto',
2401 'hg.extern_activate.auto',
2355 'hg.inherit_default_perms.true',
2402 'hg.inherit_default_perms.true',
2356 ]
2403 ]
2357
2404
2358 # defines which permissions are more important higher the more important
2405 # defines which permissions are more important higher the more important
2359 # Weight defines which permissions are more important.
2406 # Weight defines which permissions are more important.
2360 # The higher number the more important.
2407 # The higher number the more important.
2361 PERM_WEIGHTS = {
2408 PERM_WEIGHTS = {
2362 'repository.none': 0,
2409 'repository.none': 0,
2363 'repository.read': 1,
2410 'repository.read': 1,
2364 'repository.write': 3,
2411 'repository.write': 3,
2365 'repository.admin': 4,
2412 'repository.admin': 4,
2366
2413
2367 'group.none': 0,
2414 'group.none': 0,
2368 'group.read': 1,
2415 'group.read': 1,
2369 'group.write': 3,
2416 'group.write': 3,
2370 'group.admin': 4,
2417 'group.admin': 4,
2371
2418
2372 'usergroup.none': 0,
2419 'usergroup.none': 0,
2373 'usergroup.read': 1,
2420 'usergroup.read': 1,
2374 'usergroup.write': 3,
2421 'usergroup.write': 3,
2375 'usergroup.admin': 4,
2422 'usergroup.admin': 4,
2376
2423
2377 'hg.repogroup.create.false': 0,
2424 'hg.repogroup.create.false': 0,
2378 'hg.repogroup.create.true': 1,
2425 'hg.repogroup.create.true': 1,
2379
2426
2380 'hg.usergroup.create.false': 0,
2427 'hg.usergroup.create.false': 0,
2381 'hg.usergroup.create.true': 1,
2428 'hg.usergroup.create.true': 1,
2382
2429
2383 'hg.fork.none': 0,
2430 'hg.fork.none': 0,
2384 'hg.fork.repository': 1,
2431 'hg.fork.repository': 1,
2385 'hg.create.none': 0,
2432 'hg.create.none': 0,
2386 'hg.create.repository': 1
2433 'hg.create.repository': 1
2387 }
2434 }
2388
2435
2389 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2436 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2390 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2437 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2391 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2438 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2392
2439
2393 def __unicode__(self):
2440 def __unicode__(self):
2394 return u"<%s('%s:%s')>" % (
2441 return u"<%s('%s:%s')>" % (
2395 self.__class__.__name__, self.permission_id, self.permission_name
2442 self.__class__.__name__, self.permission_id, self.permission_name
2396 )
2443 )
2397
2444
2398 @classmethod
2445 @classmethod
2399 def get_by_key(cls, key):
2446 def get_by_key(cls, key):
2400 return cls.query().filter(cls.permission_name == key).scalar()
2447 return cls.query().filter(cls.permission_name == key).scalar()
2401
2448
2402 @classmethod
2449 @classmethod
2403 def get_default_repo_perms(cls, user_id, repo_id=None):
2450 def get_default_repo_perms(cls, user_id, repo_id=None):
2404 q = Session().query(UserRepoToPerm, Repository, Permission)\
2451 q = Session().query(UserRepoToPerm, Repository, Permission)\
2405 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2452 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2406 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2453 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2407 .filter(UserRepoToPerm.user_id == user_id)
2454 .filter(UserRepoToPerm.user_id == user_id)
2408 if repo_id:
2455 if repo_id:
2409 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2456 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2410 return q.all()
2457 return q.all()
2411
2458
2412 @classmethod
2459 @classmethod
2413 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2460 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2414 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2461 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2415 .join(
2462 .join(
2416 Permission,
2463 Permission,
2417 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2464 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2418 .join(
2465 .join(
2419 Repository,
2466 Repository,
2420 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2467 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2421 .join(
2468 .join(
2422 UserGroup,
2469 UserGroup,
2423 UserGroupRepoToPerm.users_group_id ==
2470 UserGroupRepoToPerm.users_group_id ==
2424 UserGroup.users_group_id)\
2471 UserGroup.users_group_id)\
2425 .join(
2472 .join(
2426 UserGroupMember,
2473 UserGroupMember,
2427 UserGroupRepoToPerm.users_group_id ==
2474 UserGroupRepoToPerm.users_group_id ==
2428 UserGroupMember.users_group_id)\
2475 UserGroupMember.users_group_id)\
2429 .filter(
2476 .filter(
2430 UserGroupMember.user_id == user_id,
2477 UserGroupMember.user_id == user_id,
2431 UserGroup.users_group_active == true())
2478 UserGroup.users_group_active == true())
2432 if repo_id:
2479 if repo_id:
2433 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2480 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2434 return q.all()
2481 return q.all()
2435
2482
2436 @classmethod
2483 @classmethod
2437 def get_default_group_perms(cls, user_id, repo_group_id=None):
2484 def get_default_group_perms(cls, user_id, repo_group_id=None):
2438 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2485 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2439 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2486 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2440 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2487 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2441 .filter(UserRepoGroupToPerm.user_id == user_id)
2488 .filter(UserRepoGroupToPerm.user_id == user_id)
2442 if repo_group_id:
2489 if repo_group_id:
2443 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2490 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2444 return q.all()
2491 return q.all()
2445
2492
2446 @classmethod
2493 @classmethod
2447 def get_default_group_perms_from_user_group(
2494 def get_default_group_perms_from_user_group(
2448 cls, user_id, repo_group_id=None):
2495 cls, user_id, repo_group_id=None):
2449 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2496 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2450 .join(
2497 .join(
2451 Permission,
2498 Permission,
2452 UserGroupRepoGroupToPerm.permission_id ==
2499 UserGroupRepoGroupToPerm.permission_id ==
2453 Permission.permission_id)\
2500 Permission.permission_id)\
2454 .join(
2501 .join(
2455 RepoGroup,
2502 RepoGroup,
2456 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2503 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2457 .join(
2504 .join(
2458 UserGroup,
2505 UserGroup,
2459 UserGroupRepoGroupToPerm.users_group_id ==
2506 UserGroupRepoGroupToPerm.users_group_id ==
2460 UserGroup.users_group_id)\
2507 UserGroup.users_group_id)\
2461 .join(
2508 .join(
2462 UserGroupMember,
2509 UserGroupMember,
2463 UserGroupRepoGroupToPerm.users_group_id ==
2510 UserGroupRepoGroupToPerm.users_group_id ==
2464 UserGroupMember.users_group_id)\
2511 UserGroupMember.users_group_id)\
2465 .filter(
2512 .filter(
2466 UserGroupMember.user_id == user_id,
2513 UserGroupMember.user_id == user_id,
2467 UserGroup.users_group_active == true())
2514 UserGroup.users_group_active == true())
2468 if repo_group_id:
2515 if repo_group_id:
2469 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2516 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2470 return q.all()
2517 return q.all()
2471
2518
2472 @classmethod
2519 @classmethod
2473 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2520 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2474 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2521 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2475 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2522 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2476 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2523 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2477 .filter(UserUserGroupToPerm.user_id == user_id)
2524 .filter(UserUserGroupToPerm.user_id == user_id)
2478 if user_group_id:
2525 if user_group_id:
2479 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2526 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2480 return q.all()
2527 return q.all()
2481
2528
2482 @classmethod
2529 @classmethod
2483 def get_default_user_group_perms_from_user_group(
2530 def get_default_user_group_perms_from_user_group(
2484 cls, user_id, user_group_id=None):
2531 cls, user_id, user_group_id=None):
2485 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2532 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2486 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2533 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2487 .join(
2534 .join(
2488 Permission,
2535 Permission,
2489 UserGroupUserGroupToPerm.permission_id ==
2536 UserGroupUserGroupToPerm.permission_id ==
2490 Permission.permission_id)\
2537 Permission.permission_id)\
2491 .join(
2538 .join(
2492 TargetUserGroup,
2539 TargetUserGroup,
2493 UserGroupUserGroupToPerm.target_user_group_id ==
2540 UserGroupUserGroupToPerm.target_user_group_id ==
2494 TargetUserGroup.users_group_id)\
2541 TargetUserGroup.users_group_id)\
2495 .join(
2542 .join(
2496 UserGroup,
2543 UserGroup,
2497 UserGroupUserGroupToPerm.user_group_id ==
2544 UserGroupUserGroupToPerm.user_group_id ==
2498 UserGroup.users_group_id)\
2545 UserGroup.users_group_id)\
2499 .join(
2546 .join(
2500 UserGroupMember,
2547 UserGroupMember,
2501 UserGroupUserGroupToPerm.user_group_id ==
2548 UserGroupUserGroupToPerm.user_group_id ==
2502 UserGroupMember.users_group_id)\
2549 UserGroupMember.users_group_id)\
2503 .filter(
2550 .filter(
2504 UserGroupMember.user_id == user_id,
2551 UserGroupMember.user_id == user_id,
2505 UserGroup.users_group_active == true())
2552 UserGroup.users_group_active == true())
2506 if user_group_id:
2553 if user_group_id:
2507 q = q.filter(
2554 q = q.filter(
2508 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2555 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2509
2556
2510 return q.all()
2557 return q.all()
2511
2558
2512
2559
2513 class UserRepoToPerm(Base, BaseModel):
2560 class UserRepoToPerm(Base, BaseModel):
2514 __tablename__ = 'repo_to_perm'
2561 __tablename__ = 'repo_to_perm'
2515 __table_args__ = (
2562 __table_args__ = (
2516 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2563 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2517 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2564 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2518 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2565 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2519 )
2566 )
2520 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2567 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2521 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2568 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2522 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2569 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2523 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2570 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2524
2571
2525 user = relationship('User')
2572 user = relationship('User')
2526 repository = relationship('Repository')
2573 repository = relationship('Repository')
2527 permission = relationship('Permission')
2574 permission = relationship('Permission')
2528
2575
2529 @classmethod
2576 @classmethod
2530 def create(cls, user, repository, permission):
2577 def create(cls, user, repository, permission):
2531 n = cls()
2578 n = cls()
2532 n.user = user
2579 n.user = user
2533 n.repository = repository
2580 n.repository = repository
2534 n.permission = permission
2581 n.permission = permission
2535 Session().add(n)
2582 Session().add(n)
2536 return n
2583 return n
2537
2584
2538 def __unicode__(self):
2585 def __unicode__(self):
2539 return u'<%s => %s >' % (self.user, self.repository)
2586 return u'<%s => %s >' % (self.user, self.repository)
2540
2587
2541
2588
2542 class UserUserGroupToPerm(Base, BaseModel):
2589 class UserUserGroupToPerm(Base, BaseModel):
2543 __tablename__ = 'user_user_group_to_perm'
2590 __tablename__ = 'user_user_group_to_perm'
2544 __table_args__ = (
2591 __table_args__ = (
2545 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2592 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2593 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2547 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2594 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2548 )
2595 )
2549 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2596 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2550 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2597 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2551 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2598 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2552 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2599 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2553
2600
2554 user = relationship('User')
2601 user = relationship('User')
2555 user_group = relationship('UserGroup')
2602 user_group = relationship('UserGroup')
2556 permission = relationship('Permission')
2603 permission = relationship('Permission')
2557
2604
2558 @classmethod
2605 @classmethod
2559 def create(cls, user, user_group, permission):
2606 def create(cls, user, user_group, permission):
2560 n = cls()
2607 n = cls()
2561 n.user = user
2608 n.user = user
2562 n.user_group = user_group
2609 n.user_group = user_group
2563 n.permission = permission
2610 n.permission = permission
2564 Session().add(n)
2611 Session().add(n)
2565 return n
2612 return n
2566
2613
2567 def __unicode__(self):
2614 def __unicode__(self):
2568 return u'<%s => %s >' % (self.user, self.user_group)
2615 return u'<%s => %s >' % (self.user, self.user_group)
2569
2616
2570
2617
2571 class UserToPerm(Base, BaseModel):
2618 class UserToPerm(Base, BaseModel):
2572 __tablename__ = 'user_to_perm'
2619 __tablename__ = 'user_to_perm'
2573 __table_args__ = (
2620 __table_args__ = (
2574 UniqueConstraint('user_id', 'permission_id'),
2621 UniqueConstraint('user_id', 'permission_id'),
2575 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2622 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2576 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2623 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2577 )
2624 )
2578 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2625 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2579 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2626 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2580 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2627 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2581
2628
2582 user = relationship('User')
2629 user = relationship('User')
2583 permission = relationship('Permission', lazy='joined')
2630 permission = relationship('Permission', lazy='joined')
2584
2631
2585 def __unicode__(self):
2632 def __unicode__(self):
2586 return u'<%s => %s >' % (self.user, self.permission)
2633 return u'<%s => %s >' % (self.user, self.permission)
2587
2634
2588
2635
2589 class UserGroupRepoToPerm(Base, BaseModel):
2636 class UserGroupRepoToPerm(Base, BaseModel):
2590 __tablename__ = 'users_group_repo_to_perm'
2637 __tablename__ = 'users_group_repo_to_perm'
2591 __table_args__ = (
2638 __table_args__ = (
2592 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2639 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2593 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2640 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2594 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2641 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2595 )
2642 )
2596 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2643 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2597 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2644 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2598 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2645 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2599 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2646 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2600
2647
2601 users_group = relationship('UserGroup')
2648 users_group = relationship('UserGroup')
2602 permission = relationship('Permission')
2649 permission = relationship('Permission')
2603 repository = relationship('Repository')
2650 repository = relationship('Repository')
2604
2651
2605 @classmethod
2652 @classmethod
2606 def create(cls, users_group, repository, permission):
2653 def create(cls, users_group, repository, permission):
2607 n = cls()
2654 n = cls()
2608 n.users_group = users_group
2655 n.users_group = users_group
2609 n.repository = repository
2656 n.repository = repository
2610 n.permission = permission
2657 n.permission = permission
2611 Session().add(n)
2658 Session().add(n)
2612 return n
2659 return n
2613
2660
2614 def __unicode__(self):
2661 def __unicode__(self):
2615 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2662 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2616
2663
2617
2664
2618 class UserGroupUserGroupToPerm(Base, BaseModel):
2665 class UserGroupUserGroupToPerm(Base, BaseModel):
2619 __tablename__ = 'user_group_user_group_to_perm'
2666 __tablename__ = 'user_group_user_group_to_perm'
2620 __table_args__ = (
2667 __table_args__ = (
2621 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2668 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2622 CheckConstraint('target_user_group_id != user_group_id'),
2669 CheckConstraint('target_user_group_id != user_group_id'),
2623 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2670 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2624 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2671 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2625 )
2672 )
2626 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2673 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2627 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2674 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2628 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2675 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2629 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2676 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2630
2677
2631 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2678 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2632 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2679 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2633 permission = relationship('Permission')
2680 permission = relationship('Permission')
2634
2681
2635 @classmethod
2682 @classmethod
2636 def create(cls, target_user_group, user_group, permission):
2683 def create(cls, target_user_group, user_group, permission):
2637 n = cls()
2684 n = cls()
2638 n.target_user_group = target_user_group
2685 n.target_user_group = target_user_group
2639 n.user_group = user_group
2686 n.user_group = user_group
2640 n.permission = permission
2687 n.permission = permission
2641 Session().add(n)
2688 Session().add(n)
2642 return n
2689 return n
2643
2690
2644 def __unicode__(self):
2691 def __unicode__(self):
2645 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2692 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2646
2693
2647
2694
2648 class UserGroupToPerm(Base, BaseModel):
2695 class UserGroupToPerm(Base, BaseModel):
2649 __tablename__ = 'users_group_to_perm'
2696 __tablename__ = 'users_group_to_perm'
2650 __table_args__ = (
2697 __table_args__ = (
2651 UniqueConstraint('users_group_id', 'permission_id',),
2698 UniqueConstraint('users_group_id', 'permission_id',),
2652 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2699 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2653 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2700 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2654 )
2701 )
2655 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2702 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2656 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2703 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2657 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2704 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2658
2705
2659 users_group = relationship('UserGroup')
2706 users_group = relationship('UserGroup')
2660 permission = relationship('Permission')
2707 permission = relationship('Permission')
2661
2708
2662
2709
2663 class UserRepoGroupToPerm(Base, BaseModel):
2710 class UserRepoGroupToPerm(Base, BaseModel):
2664 __tablename__ = 'user_repo_group_to_perm'
2711 __tablename__ = 'user_repo_group_to_perm'
2665 __table_args__ = (
2712 __table_args__ = (
2666 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2713 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2667 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2714 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2668 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2715 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2669 )
2716 )
2670
2717
2671 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2718 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2672 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2719 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2673 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2720 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2674 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2721 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2675
2722
2676 user = relationship('User')
2723 user = relationship('User')
2677 group = relationship('RepoGroup')
2724 group = relationship('RepoGroup')
2678 permission = relationship('Permission')
2725 permission = relationship('Permission')
2679
2726
2680 @classmethod
2727 @classmethod
2681 def create(cls, user, repository_group, permission):
2728 def create(cls, user, repository_group, permission):
2682 n = cls()
2729 n = cls()
2683 n.user = user
2730 n.user = user
2684 n.group = repository_group
2731 n.group = repository_group
2685 n.permission = permission
2732 n.permission = permission
2686 Session().add(n)
2733 Session().add(n)
2687 return n
2734 return n
2688
2735
2689
2736
2690 class UserGroupRepoGroupToPerm(Base, BaseModel):
2737 class UserGroupRepoGroupToPerm(Base, BaseModel):
2691 __tablename__ = 'users_group_repo_group_to_perm'
2738 __tablename__ = 'users_group_repo_group_to_perm'
2692 __table_args__ = (
2739 __table_args__ = (
2693 UniqueConstraint('users_group_id', 'group_id'),
2740 UniqueConstraint('users_group_id', 'group_id'),
2694 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2741 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2695 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2742 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2696 )
2743 )
2697
2744
2698 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2745 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2699 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2746 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2700 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2747 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2701 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2748 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2702
2749
2703 users_group = relationship('UserGroup')
2750 users_group = relationship('UserGroup')
2704 permission = relationship('Permission')
2751 permission = relationship('Permission')
2705 group = relationship('RepoGroup')
2752 group = relationship('RepoGroup')
2706
2753
2707 @classmethod
2754 @classmethod
2708 def create(cls, user_group, repository_group, permission):
2755 def create(cls, user_group, repository_group, permission):
2709 n = cls()
2756 n = cls()
2710 n.users_group = user_group
2757 n.users_group = user_group
2711 n.group = repository_group
2758 n.group = repository_group
2712 n.permission = permission
2759 n.permission = permission
2713 Session().add(n)
2760 Session().add(n)
2714 return n
2761 return n
2715
2762
2716 def __unicode__(self):
2763 def __unicode__(self):
2717 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2764 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2718
2765
2719
2766
2720 class Statistics(Base, BaseModel):
2767 class Statistics(Base, BaseModel):
2721 __tablename__ = 'statistics'
2768 __tablename__ = 'statistics'
2722 __table_args__ = (
2769 __table_args__ = (
2723 UniqueConstraint('repository_id'),
2770 UniqueConstraint('repository_id'),
2724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2771 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2725 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2772 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2726 )
2773 )
2727 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2774 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2728 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2775 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2729 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2776 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2730 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2777 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2731 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2778 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2732 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2779 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2733
2780
2734 repository = relationship('Repository', single_parent=True)
2781 repository = relationship('Repository', single_parent=True)
2735
2782
2736
2783
2737 class UserFollowing(Base, BaseModel):
2784 class UserFollowing(Base, BaseModel):
2738 __tablename__ = 'user_followings'
2785 __tablename__ = 'user_followings'
2739 __table_args__ = (
2786 __table_args__ = (
2740 UniqueConstraint('user_id', 'follows_repository_id'),
2787 UniqueConstraint('user_id', 'follows_repository_id'),
2741 UniqueConstraint('user_id', 'follows_user_id'),
2788 UniqueConstraint('user_id', 'follows_user_id'),
2742 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2789 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2743 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2790 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2744 )
2791 )
2745
2792
2746 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2793 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2747 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2794 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2748 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2795 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2749 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2796 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2750 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2797 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2751
2798
2752 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2799 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2753
2800
2754 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2801 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2755 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2802 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2756
2803
2757 @classmethod
2804 @classmethod
2758 def get_repo_followers(cls, repo_id):
2805 def get_repo_followers(cls, repo_id):
2759 return cls.query().filter(cls.follows_repo_id == repo_id)
2806 return cls.query().filter(cls.follows_repo_id == repo_id)
2760
2807
2761
2808
2762 class CacheKey(Base, BaseModel):
2809 class CacheKey(Base, BaseModel):
2763 __tablename__ = 'cache_invalidation'
2810 __tablename__ = 'cache_invalidation'
2764 __table_args__ = (
2811 __table_args__ = (
2765 UniqueConstraint('cache_key'),
2812 UniqueConstraint('cache_key'),
2766 Index('key_idx', 'cache_key'),
2813 Index('key_idx', 'cache_key'),
2767 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2814 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2768 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2815 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2769 )
2816 )
2770 CACHE_TYPE_ATOM = 'ATOM'
2817 CACHE_TYPE_ATOM = 'ATOM'
2771 CACHE_TYPE_RSS = 'RSS'
2818 CACHE_TYPE_RSS = 'RSS'
2772 CACHE_TYPE_README = 'README'
2819 CACHE_TYPE_README = 'README'
2773
2820
2774 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2821 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2775 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2822 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2776 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2823 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2777 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2824 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2778
2825
2779 def __init__(self, cache_key, cache_args=''):
2826 def __init__(self, cache_key, cache_args=''):
2780 self.cache_key = cache_key
2827 self.cache_key = cache_key
2781 self.cache_args = cache_args
2828 self.cache_args = cache_args
2782 self.cache_active = False
2829 self.cache_active = False
2783
2830
2784 def __unicode__(self):
2831 def __unicode__(self):
2785 return u"<%s('%s:%s[%s]')>" % (
2832 return u"<%s('%s:%s[%s]')>" % (
2786 self.__class__.__name__,
2833 self.__class__.__name__,
2787 self.cache_id, self.cache_key, self.cache_active)
2834 self.cache_id, self.cache_key, self.cache_active)
2788
2835
2789 def _cache_key_partition(self):
2836 def _cache_key_partition(self):
2790 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2837 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2791 return prefix, repo_name, suffix
2838 return prefix, repo_name, suffix
2792
2839
2793 def get_prefix(self):
2840 def get_prefix(self):
2794 """
2841 """
2795 Try to extract prefix from existing cache key. The key could consist
2842 Try to extract prefix from existing cache key. The key could consist
2796 of prefix, repo_name, suffix
2843 of prefix, repo_name, suffix
2797 """
2844 """
2798 # this returns prefix, repo_name, suffix
2845 # this returns prefix, repo_name, suffix
2799 return self._cache_key_partition()[0]
2846 return self._cache_key_partition()[0]
2800
2847
2801 def get_suffix(self):
2848 def get_suffix(self):
2802 """
2849 """
2803 get suffix that might have been used in _get_cache_key to
2850 get suffix that might have been used in _get_cache_key to
2804 generate self.cache_key. Only used for informational purposes
2851 generate self.cache_key. Only used for informational purposes
2805 in repo_edit.mako.
2852 in repo_edit.mako.
2806 """
2853 """
2807 # prefix, repo_name, suffix
2854 # prefix, repo_name, suffix
2808 return self._cache_key_partition()[2]
2855 return self._cache_key_partition()[2]
2809
2856
2810 @classmethod
2857 @classmethod
2811 def delete_all_cache(cls):
2858 def delete_all_cache(cls):
2812 """
2859 """
2813 Delete all cache keys from database.
2860 Delete all cache keys from database.
2814 Should only be run when all instances are down and all entries
2861 Should only be run when all instances are down and all entries
2815 thus stale.
2862 thus stale.
2816 """
2863 """
2817 cls.query().delete()
2864 cls.query().delete()
2818 Session().commit()
2865 Session().commit()
2819
2866
2820 @classmethod
2867 @classmethod
2821 def get_cache_key(cls, repo_name, cache_type):
2868 def get_cache_key(cls, repo_name, cache_type):
2822 """
2869 """
2823
2870
2824 Generate a cache key for this process of RhodeCode instance.
2871 Generate a cache key for this process of RhodeCode instance.
2825 Prefix most likely will be process id or maybe explicitly set
2872 Prefix most likely will be process id or maybe explicitly set
2826 instance_id from .ini file.
2873 instance_id from .ini file.
2827 """
2874 """
2828 import rhodecode
2875 import rhodecode
2829 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2876 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2830
2877
2831 repo_as_unicode = safe_unicode(repo_name)
2878 repo_as_unicode = safe_unicode(repo_name)
2832 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2879 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2833 if cache_type else repo_as_unicode
2880 if cache_type else repo_as_unicode
2834
2881
2835 return u'{}{}'.format(prefix, key)
2882 return u'{}{}'.format(prefix, key)
2836
2883
2837 @classmethod
2884 @classmethod
2838 def set_invalidate(cls, repo_name, delete=False):
2885 def set_invalidate(cls, repo_name, delete=False):
2839 """
2886 """
2840 Mark all caches of a repo as invalid in the database.
2887 Mark all caches of a repo as invalid in the database.
2841 """
2888 """
2842
2889
2843 try:
2890 try:
2844 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2891 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2845 if delete:
2892 if delete:
2846 log.debug('cache objects deleted for repo %s',
2893 log.debug('cache objects deleted for repo %s',
2847 safe_str(repo_name))
2894 safe_str(repo_name))
2848 qry.delete()
2895 qry.delete()
2849 else:
2896 else:
2850 log.debug('cache objects marked as invalid for repo %s',
2897 log.debug('cache objects marked as invalid for repo %s',
2851 safe_str(repo_name))
2898 safe_str(repo_name))
2852 qry.update({"cache_active": False})
2899 qry.update({"cache_active": False})
2853
2900
2854 Session().commit()
2901 Session().commit()
2855 except Exception:
2902 except Exception:
2856 log.exception(
2903 log.exception(
2857 'Cache key invalidation failed for repository %s',
2904 'Cache key invalidation failed for repository %s',
2858 safe_str(repo_name))
2905 safe_str(repo_name))
2859 Session().rollback()
2906 Session().rollback()
2860
2907
2861 @classmethod
2908 @classmethod
2862 def get_active_cache(cls, cache_key):
2909 def get_active_cache(cls, cache_key):
2863 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2910 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2864 if inv_obj:
2911 if inv_obj:
2865 return inv_obj
2912 return inv_obj
2866 return None
2913 return None
2867
2914
2868 @classmethod
2915 @classmethod
2869 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2916 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2870 thread_scoped=False):
2917 thread_scoped=False):
2871 """
2918 """
2872 @cache_region('long_term')
2919 @cache_region('long_term')
2873 def _heavy_calculation(cache_key):
2920 def _heavy_calculation(cache_key):
2874 return 'result'
2921 return 'result'
2875
2922
2876 cache_context = CacheKey.repo_context_cache(
2923 cache_context = CacheKey.repo_context_cache(
2877 _heavy_calculation, repo_name, cache_type)
2924 _heavy_calculation, repo_name, cache_type)
2878
2925
2879 with cache_context as context:
2926 with cache_context as context:
2880 context.invalidate()
2927 context.invalidate()
2881 computed = context.compute()
2928 computed = context.compute()
2882
2929
2883 assert computed == 'result'
2930 assert computed == 'result'
2884 """
2931 """
2885 from rhodecode.lib import caches
2932 from rhodecode.lib import caches
2886 return caches.InvalidationContext(
2933 return caches.InvalidationContext(
2887 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2934 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2888
2935
2889
2936
2890 class ChangesetComment(Base, BaseModel):
2937 class ChangesetComment(Base, BaseModel):
2891 __tablename__ = 'changeset_comments'
2938 __tablename__ = 'changeset_comments'
2892 __table_args__ = (
2939 __table_args__ = (
2893 Index('cc_revision_idx', 'revision'),
2940 Index('cc_revision_idx', 'revision'),
2894 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2895 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2896 )
2943 )
2897
2944
2898 COMMENT_OUTDATED = u'comment_outdated'
2945 COMMENT_OUTDATED = u'comment_outdated'
2899 COMMENT_TYPE_NOTE = u'note'
2946 COMMENT_TYPE_NOTE = u'note'
2900 COMMENT_TYPE_TODO = u'todo'
2947 COMMENT_TYPE_TODO = u'todo'
2901 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
2948 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
2902
2949
2903 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2950 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2904 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2951 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2905 revision = Column('revision', String(40), nullable=True)
2952 revision = Column('revision', String(40), nullable=True)
2906 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2953 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2907 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2954 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2908 line_no = Column('line_no', Unicode(10), nullable=True)
2955 line_no = Column('line_no', Unicode(10), nullable=True)
2909 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2956 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2910 f_path = Column('f_path', Unicode(1000), nullable=True)
2957 f_path = Column('f_path', Unicode(1000), nullable=True)
2911 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2958 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2912 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2959 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2913 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2960 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2914 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2961 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2915 renderer = Column('renderer', Unicode(64), nullable=True)
2962 renderer = Column('renderer', Unicode(64), nullable=True)
2916 display_state = Column('display_state', Unicode(128), nullable=True)
2963 display_state = Column('display_state', Unicode(128), nullable=True)
2917
2964
2918 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
2965 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
2919 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
2966 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
2920 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
2967 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
2921 author = relationship('User', lazy='joined')
2968 author = relationship('User', lazy='joined')
2922 repo = relationship('Repository')
2969 repo = relationship('Repository')
2923 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
2970 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
2924 pull_request = relationship('PullRequest', lazy='joined')
2971 pull_request = relationship('PullRequest', lazy='joined')
2925 pull_request_version = relationship('PullRequestVersion')
2972 pull_request_version = relationship('PullRequestVersion')
2926
2973
2927 @classmethod
2974 @classmethod
2928 def get_users(cls, revision=None, pull_request_id=None):
2975 def get_users(cls, revision=None, pull_request_id=None):
2929 """
2976 """
2930 Returns user associated with this ChangesetComment. ie those
2977 Returns user associated with this ChangesetComment. ie those
2931 who actually commented
2978 who actually commented
2932
2979
2933 :param cls:
2980 :param cls:
2934 :param revision:
2981 :param revision:
2935 """
2982 """
2936 q = Session().query(User)\
2983 q = Session().query(User)\
2937 .join(ChangesetComment.author)
2984 .join(ChangesetComment.author)
2938 if revision:
2985 if revision:
2939 q = q.filter(cls.revision == revision)
2986 q = q.filter(cls.revision == revision)
2940 elif pull_request_id:
2987 elif pull_request_id:
2941 q = q.filter(cls.pull_request_id == pull_request_id)
2988 q = q.filter(cls.pull_request_id == pull_request_id)
2942 return q.all()
2989 return q.all()
2943
2990
2944 @classmethod
2991 @classmethod
2945 def get_index_from_version(cls, pr_version, versions):
2992 def get_index_from_version(cls, pr_version, versions):
2946 num_versions = [x.pull_request_version_id for x in versions]
2993 num_versions = [x.pull_request_version_id for x in versions]
2947 try:
2994 try:
2948 return num_versions.index(pr_version) +1
2995 return num_versions.index(pr_version) +1
2949 except (IndexError, ValueError):
2996 except (IndexError, ValueError):
2950 return
2997 return
2951
2998
2952 @property
2999 @property
2953 def outdated(self):
3000 def outdated(self):
2954 return self.display_state == self.COMMENT_OUTDATED
3001 return self.display_state == self.COMMENT_OUTDATED
2955
3002
2956 def outdated_at_version(self, version):
3003 def outdated_at_version(self, version):
2957 """
3004 """
2958 Checks if comment is outdated for given pull request version
3005 Checks if comment is outdated for given pull request version
2959 """
3006 """
2960 return self.outdated and self.pull_request_version_id != version
3007 return self.outdated and self.pull_request_version_id != version
2961
3008
2962 def older_than_version(self, version):
3009 def older_than_version(self, version):
2963 """
3010 """
2964 Checks if comment is made from previous version than given
3011 Checks if comment is made from previous version than given
2965 """
3012 """
2966 if version is None:
3013 if version is None:
2967 return self.pull_request_version_id is not None
3014 return self.pull_request_version_id is not None
2968
3015
2969 return self.pull_request_version_id < version
3016 return self.pull_request_version_id < version
2970
3017
2971 @property
3018 @property
2972 def resolved(self):
3019 def resolved(self):
2973 return self.resolved_by[0] if self.resolved_by else None
3020 return self.resolved_by[0] if self.resolved_by else None
2974
3021
2975 @property
3022 @property
2976 def is_todo(self):
3023 def is_todo(self):
2977 return self.comment_type == self.COMMENT_TYPE_TODO
3024 return self.comment_type == self.COMMENT_TYPE_TODO
2978
3025
2979 def get_index_version(self, versions):
3026 def get_index_version(self, versions):
2980 return self.get_index_from_version(
3027 return self.get_index_from_version(
2981 self.pull_request_version_id, versions)
3028 self.pull_request_version_id, versions)
2982
3029
2983 def render(self, mentions=False):
3030 def render(self, mentions=False):
2984 from rhodecode.lib import helpers as h
3031 from rhodecode.lib import helpers as h
2985 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3032 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2986
3033
2987 def __repr__(self):
3034 def __repr__(self):
2988 if self.comment_id:
3035 if self.comment_id:
2989 return '<DB:Comment #%s>' % self.comment_id
3036 return '<DB:Comment #%s>' % self.comment_id
2990 else:
3037 else:
2991 return '<DB:Comment at %#x>' % id(self)
3038 return '<DB:Comment at %#x>' % id(self)
2992
3039
2993
3040
2994 class ChangesetStatus(Base, BaseModel):
3041 class ChangesetStatus(Base, BaseModel):
2995 __tablename__ = 'changeset_statuses'
3042 __tablename__ = 'changeset_statuses'
2996 __table_args__ = (
3043 __table_args__ = (
2997 Index('cs_revision_idx', 'revision'),
3044 Index('cs_revision_idx', 'revision'),
2998 Index('cs_version_idx', 'version'),
3045 Index('cs_version_idx', 'version'),
2999 UniqueConstraint('repo_id', 'revision', 'version'),
3046 UniqueConstraint('repo_id', 'revision', 'version'),
3000 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3047 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3001 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3048 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3002 )
3049 )
3003 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3050 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3004 STATUS_APPROVED = 'approved'
3051 STATUS_APPROVED = 'approved'
3005 STATUS_REJECTED = 'rejected'
3052 STATUS_REJECTED = 'rejected'
3006 STATUS_UNDER_REVIEW = 'under_review'
3053 STATUS_UNDER_REVIEW = 'under_review'
3007
3054
3008 STATUSES = [
3055 STATUSES = [
3009 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3056 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3010 (STATUS_APPROVED, _("Approved")),
3057 (STATUS_APPROVED, _("Approved")),
3011 (STATUS_REJECTED, _("Rejected")),
3058 (STATUS_REJECTED, _("Rejected")),
3012 (STATUS_UNDER_REVIEW, _("Under Review")),
3059 (STATUS_UNDER_REVIEW, _("Under Review")),
3013 ]
3060 ]
3014
3061
3015 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3062 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3016 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3063 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3017 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3064 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3018 revision = Column('revision', String(40), nullable=False)
3065 revision = Column('revision', String(40), nullable=False)
3019 status = Column('status', String(128), nullable=False, default=DEFAULT)
3066 status = Column('status', String(128), nullable=False, default=DEFAULT)
3020 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3067 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3021 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3068 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3022 version = Column('version', Integer(), nullable=False, default=0)
3069 version = Column('version', Integer(), nullable=False, default=0)
3023 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3070 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3024
3071
3025 author = relationship('User', lazy='joined')
3072 author = relationship('User', lazy='joined')
3026 repo = relationship('Repository')
3073 repo = relationship('Repository')
3027 comment = relationship('ChangesetComment', lazy='joined')
3074 comment = relationship('ChangesetComment', lazy='joined')
3028 pull_request = relationship('PullRequest', lazy='joined')
3075 pull_request = relationship('PullRequest', lazy='joined')
3029
3076
3030 def __unicode__(self):
3077 def __unicode__(self):
3031 return u"<%s('%s[v%s]:%s')>" % (
3078 return u"<%s('%s[v%s]:%s')>" % (
3032 self.__class__.__name__,
3079 self.__class__.__name__,
3033 self.status, self.version, self.author
3080 self.status, self.version, self.author
3034 )
3081 )
3035
3082
3036 @classmethod
3083 @classmethod
3037 def get_status_lbl(cls, value):
3084 def get_status_lbl(cls, value):
3038 return dict(cls.STATUSES).get(value)
3085 return dict(cls.STATUSES).get(value)
3039
3086
3040 @property
3087 @property
3041 def status_lbl(self):
3088 def status_lbl(self):
3042 return ChangesetStatus.get_status_lbl(self.status)
3089 return ChangesetStatus.get_status_lbl(self.status)
3043
3090
3044
3091
3045 class _PullRequestBase(BaseModel):
3092 class _PullRequestBase(BaseModel):
3046 """
3093 """
3047 Common attributes of pull request and version entries.
3094 Common attributes of pull request and version entries.
3048 """
3095 """
3049
3096
3050 # .status values
3097 # .status values
3051 STATUS_NEW = u'new'
3098 STATUS_NEW = u'new'
3052 STATUS_OPEN = u'open'
3099 STATUS_OPEN = u'open'
3053 STATUS_CLOSED = u'closed'
3100 STATUS_CLOSED = u'closed'
3054
3101
3055 title = Column('title', Unicode(255), nullable=True)
3102 title = Column('title', Unicode(255), nullable=True)
3056 description = Column(
3103 description = Column(
3057 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3104 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3058 nullable=True)
3105 nullable=True)
3059 # new/open/closed status of pull request (not approve/reject/etc)
3106 # new/open/closed status of pull request (not approve/reject/etc)
3060 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3107 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3061 created_on = Column(
3108 created_on = Column(
3062 'created_on', DateTime(timezone=False), nullable=False,
3109 'created_on', DateTime(timezone=False), nullable=False,
3063 default=datetime.datetime.now)
3110 default=datetime.datetime.now)
3064 updated_on = Column(
3111 updated_on = Column(
3065 'updated_on', DateTime(timezone=False), nullable=False,
3112 'updated_on', DateTime(timezone=False), nullable=False,
3066 default=datetime.datetime.now)
3113 default=datetime.datetime.now)
3067
3114
3068 @declared_attr
3115 @declared_attr
3069 def user_id(cls):
3116 def user_id(cls):
3070 return Column(
3117 return Column(
3071 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3118 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3072 unique=None)
3119 unique=None)
3073
3120
3074 # 500 revisions max
3121 # 500 revisions max
3075 _revisions = Column(
3122 _revisions = Column(
3076 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3123 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3077
3124
3078 @declared_attr
3125 @declared_attr
3079 def source_repo_id(cls):
3126 def source_repo_id(cls):
3080 # TODO: dan: rename column to source_repo_id
3127 # TODO: dan: rename column to source_repo_id
3081 return Column(
3128 return Column(
3082 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3129 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3083 nullable=False)
3130 nullable=False)
3084
3131
3085 source_ref = Column('org_ref', Unicode(255), nullable=False)
3132 source_ref = Column('org_ref', Unicode(255), nullable=False)
3086
3133
3087 @declared_attr
3134 @declared_attr
3088 def target_repo_id(cls):
3135 def target_repo_id(cls):
3089 # TODO: dan: rename column to target_repo_id
3136 # TODO: dan: rename column to target_repo_id
3090 return Column(
3137 return Column(
3091 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3138 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3092 nullable=False)
3139 nullable=False)
3093
3140
3094 target_ref = Column('other_ref', Unicode(255), nullable=False)
3141 target_ref = Column('other_ref', Unicode(255), nullable=False)
3095 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3142 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3096
3143
3097 # TODO: dan: rename column to last_merge_source_rev
3144 # TODO: dan: rename column to last_merge_source_rev
3098 _last_merge_source_rev = Column(
3145 _last_merge_source_rev = Column(
3099 'last_merge_org_rev', String(40), nullable=True)
3146 'last_merge_org_rev', String(40), nullable=True)
3100 # TODO: dan: rename column to last_merge_target_rev
3147 # TODO: dan: rename column to last_merge_target_rev
3101 _last_merge_target_rev = Column(
3148 _last_merge_target_rev = Column(
3102 'last_merge_other_rev', String(40), nullable=True)
3149 'last_merge_other_rev', String(40), nullable=True)
3103 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3150 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3104 merge_rev = Column('merge_rev', String(40), nullable=True)
3151 merge_rev = Column('merge_rev', String(40), nullable=True)
3105
3152
3106 @hybrid_property
3153 @hybrid_property
3107 def revisions(self):
3154 def revisions(self):
3108 return self._revisions.split(':') if self._revisions else []
3155 return self._revisions.split(':') if self._revisions else []
3109
3156
3110 @revisions.setter
3157 @revisions.setter
3111 def revisions(self, val):
3158 def revisions(self, val):
3112 self._revisions = ':'.join(val)
3159 self._revisions = ':'.join(val)
3113
3160
3114 @declared_attr
3161 @declared_attr
3115 def author(cls):
3162 def author(cls):
3116 return relationship('User', lazy='joined')
3163 return relationship('User', lazy='joined')
3117
3164
3118 @declared_attr
3165 @declared_attr
3119 def source_repo(cls):
3166 def source_repo(cls):
3120 return relationship(
3167 return relationship(
3121 'Repository',
3168 'Repository',
3122 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3169 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3123
3170
3124 @property
3171 @property
3125 def source_ref_parts(self):
3172 def source_ref_parts(self):
3126 return self.unicode_to_reference(self.source_ref)
3173 return self.unicode_to_reference(self.source_ref)
3127
3174
3128 @declared_attr
3175 @declared_attr
3129 def target_repo(cls):
3176 def target_repo(cls):
3130 return relationship(
3177 return relationship(
3131 'Repository',
3178 'Repository',
3132 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3179 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3133
3180
3134 @property
3181 @property
3135 def target_ref_parts(self):
3182 def target_ref_parts(self):
3136 return self.unicode_to_reference(self.target_ref)
3183 return self.unicode_to_reference(self.target_ref)
3137
3184
3138 @property
3185 @property
3139 def shadow_merge_ref(self):
3186 def shadow_merge_ref(self):
3140 return self.unicode_to_reference(self._shadow_merge_ref)
3187 return self.unicode_to_reference(self._shadow_merge_ref)
3141
3188
3142 @shadow_merge_ref.setter
3189 @shadow_merge_ref.setter
3143 def shadow_merge_ref(self, ref):
3190 def shadow_merge_ref(self, ref):
3144 self._shadow_merge_ref = self.reference_to_unicode(ref)
3191 self._shadow_merge_ref = self.reference_to_unicode(ref)
3145
3192
3146 def unicode_to_reference(self, raw):
3193 def unicode_to_reference(self, raw):
3147 """
3194 """
3148 Convert a unicode (or string) to a reference object.
3195 Convert a unicode (or string) to a reference object.
3149 If unicode evaluates to False it returns None.
3196 If unicode evaluates to False it returns None.
3150 """
3197 """
3151 if raw:
3198 if raw:
3152 refs = raw.split(':')
3199 refs = raw.split(':')
3153 return Reference(*refs)
3200 return Reference(*refs)
3154 else:
3201 else:
3155 return None
3202 return None
3156
3203
3157 def reference_to_unicode(self, ref):
3204 def reference_to_unicode(self, ref):
3158 """
3205 """
3159 Convert a reference object to unicode.
3206 Convert a reference object to unicode.
3160 If reference is None it returns None.
3207 If reference is None it returns None.
3161 """
3208 """
3162 if ref:
3209 if ref:
3163 return u':'.join(ref)
3210 return u':'.join(ref)
3164 else:
3211 else:
3165 return None
3212 return None
3166
3213
3167 def get_api_data(self):
3214 def get_api_data(self):
3168 from rhodecode.model.pull_request import PullRequestModel
3215 from rhodecode.model.pull_request import PullRequestModel
3169 pull_request = self
3216 pull_request = self
3170 merge_status = PullRequestModel().merge_status(pull_request)
3217 merge_status = PullRequestModel().merge_status(pull_request)
3171
3218
3172 pull_request_url = url(
3219 pull_request_url = url(
3173 'pullrequest_show', repo_name=self.target_repo.repo_name,
3220 'pullrequest_show', repo_name=self.target_repo.repo_name,
3174 pull_request_id=self.pull_request_id, qualified=True)
3221 pull_request_id=self.pull_request_id, qualified=True)
3175
3222
3176 merge_data = {
3223 merge_data = {
3177 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3224 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3178 'reference': (
3225 'reference': (
3179 pull_request.shadow_merge_ref._asdict()
3226 pull_request.shadow_merge_ref._asdict()
3180 if pull_request.shadow_merge_ref else None),
3227 if pull_request.shadow_merge_ref else None),
3181 }
3228 }
3182
3229
3183 data = {
3230 data = {
3184 'pull_request_id': pull_request.pull_request_id,
3231 'pull_request_id': pull_request.pull_request_id,
3185 'url': pull_request_url,
3232 'url': pull_request_url,
3186 'title': pull_request.title,
3233 'title': pull_request.title,
3187 'description': pull_request.description,
3234 'description': pull_request.description,
3188 'status': pull_request.status,
3235 'status': pull_request.status,
3189 'created_on': pull_request.created_on,
3236 'created_on': pull_request.created_on,
3190 'updated_on': pull_request.updated_on,
3237 'updated_on': pull_request.updated_on,
3191 'commit_ids': pull_request.revisions,
3238 'commit_ids': pull_request.revisions,
3192 'review_status': pull_request.calculated_review_status(),
3239 'review_status': pull_request.calculated_review_status(),
3193 'mergeable': {
3240 'mergeable': {
3194 'status': merge_status[0],
3241 'status': merge_status[0],
3195 'message': unicode(merge_status[1]),
3242 'message': unicode(merge_status[1]),
3196 },
3243 },
3197 'source': {
3244 'source': {
3198 'clone_url': pull_request.source_repo.clone_url(),
3245 'clone_url': pull_request.source_repo.clone_url(),
3199 'repository': pull_request.source_repo.repo_name,
3246 'repository': pull_request.source_repo.repo_name,
3200 'reference': {
3247 'reference': {
3201 'name': pull_request.source_ref_parts.name,
3248 'name': pull_request.source_ref_parts.name,
3202 'type': pull_request.source_ref_parts.type,
3249 'type': pull_request.source_ref_parts.type,
3203 'commit_id': pull_request.source_ref_parts.commit_id,
3250 'commit_id': pull_request.source_ref_parts.commit_id,
3204 },
3251 },
3205 },
3252 },
3206 'target': {
3253 'target': {
3207 'clone_url': pull_request.target_repo.clone_url(),
3254 'clone_url': pull_request.target_repo.clone_url(),
3208 'repository': pull_request.target_repo.repo_name,
3255 'repository': pull_request.target_repo.repo_name,
3209 'reference': {
3256 'reference': {
3210 'name': pull_request.target_ref_parts.name,
3257 'name': pull_request.target_ref_parts.name,
3211 'type': pull_request.target_ref_parts.type,
3258 'type': pull_request.target_ref_parts.type,
3212 'commit_id': pull_request.target_ref_parts.commit_id,
3259 'commit_id': pull_request.target_ref_parts.commit_id,
3213 },
3260 },
3214 },
3261 },
3215 'merge': merge_data,
3262 'merge': merge_data,
3216 'author': pull_request.author.get_api_data(include_secrets=False,
3263 'author': pull_request.author.get_api_data(include_secrets=False,
3217 details='basic'),
3264 details='basic'),
3218 'reviewers': [
3265 'reviewers': [
3219 {
3266 {
3220 'user': reviewer.get_api_data(include_secrets=False,
3267 'user': reviewer.get_api_data(include_secrets=False,
3221 details='basic'),
3268 details='basic'),
3222 'reasons': reasons,
3269 'reasons': reasons,
3223 'review_status': st[0][1].status if st else 'not_reviewed',
3270 'review_status': st[0][1].status if st else 'not_reviewed',
3224 }
3271 }
3225 for reviewer, reasons, st in pull_request.reviewers_statuses()
3272 for reviewer, reasons, st in pull_request.reviewers_statuses()
3226 ]
3273 ]
3227 }
3274 }
3228
3275
3229 return data
3276 return data
3230
3277
3231
3278
3232 class PullRequest(Base, _PullRequestBase):
3279 class PullRequest(Base, _PullRequestBase):
3233 __tablename__ = 'pull_requests'
3280 __tablename__ = 'pull_requests'
3234 __table_args__ = (
3281 __table_args__ = (
3235 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3282 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3236 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3283 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3237 )
3284 )
3238
3285
3239 pull_request_id = Column(
3286 pull_request_id = Column(
3240 'pull_request_id', Integer(), nullable=False, primary_key=True)
3287 'pull_request_id', Integer(), nullable=False, primary_key=True)
3241
3288
3242 def __repr__(self):
3289 def __repr__(self):
3243 if self.pull_request_id:
3290 if self.pull_request_id:
3244 return '<DB:PullRequest #%s>' % self.pull_request_id
3291 return '<DB:PullRequest #%s>' % self.pull_request_id
3245 else:
3292 else:
3246 return '<DB:PullRequest at %#x>' % id(self)
3293 return '<DB:PullRequest at %#x>' % id(self)
3247
3294
3248 reviewers = relationship('PullRequestReviewers',
3295 reviewers = relationship('PullRequestReviewers',
3249 cascade="all, delete, delete-orphan")
3296 cascade="all, delete, delete-orphan")
3250 statuses = relationship('ChangesetStatus')
3297 statuses = relationship('ChangesetStatus')
3251 comments = relationship('ChangesetComment',
3298 comments = relationship('ChangesetComment',
3252 cascade="all, delete, delete-orphan")
3299 cascade="all, delete, delete-orphan")
3253 versions = relationship('PullRequestVersion',
3300 versions = relationship('PullRequestVersion',
3254 cascade="all, delete, delete-orphan",
3301 cascade="all, delete, delete-orphan",
3255 lazy='dynamic')
3302 lazy='dynamic')
3256
3303
3257 @classmethod
3304 @classmethod
3258 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3305 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3259 internal_methods=None):
3306 internal_methods=None):
3260
3307
3261 class PullRequestDisplay(object):
3308 class PullRequestDisplay(object):
3262 """
3309 """
3263 Special object wrapper for showing PullRequest data via Versions
3310 Special object wrapper for showing PullRequest data via Versions
3264 It mimics PR object as close as possible. This is read only object
3311 It mimics PR object as close as possible. This is read only object
3265 just for display
3312 just for display
3266 """
3313 """
3267
3314
3268 def __init__(self, attrs, internal=None):
3315 def __init__(self, attrs, internal=None):
3269 self.attrs = attrs
3316 self.attrs = attrs
3270 # internal have priority over the given ones via attrs
3317 # internal have priority over the given ones via attrs
3271 self.internal = internal or ['versions']
3318 self.internal = internal or ['versions']
3272
3319
3273 def __getattr__(self, item):
3320 def __getattr__(self, item):
3274 if item in self.internal:
3321 if item in self.internal:
3275 return getattr(self, item)
3322 return getattr(self, item)
3276 try:
3323 try:
3277 return self.attrs[item]
3324 return self.attrs[item]
3278 except KeyError:
3325 except KeyError:
3279 raise AttributeError(
3326 raise AttributeError(
3280 '%s object has no attribute %s' % (self, item))
3327 '%s object has no attribute %s' % (self, item))
3281
3328
3282 def __repr__(self):
3329 def __repr__(self):
3283 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3330 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3284
3331
3285 def versions(self):
3332 def versions(self):
3286 return pull_request_obj.versions.order_by(
3333 return pull_request_obj.versions.order_by(
3287 PullRequestVersion.pull_request_version_id).all()
3334 PullRequestVersion.pull_request_version_id).all()
3288
3335
3289 def is_closed(self):
3336 def is_closed(self):
3290 return pull_request_obj.is_closed()
3337 return pull_request_obj.is_closed()
3291
3338
3292 @property
3339 @property
3293 def pull_request_version_id(self):
3340 def pull_request_version_id(self):
3294 return getattr(pull_request_obj, 'pull_request_version_id', None)
3341 return getattr(pull_request_obj, 'pull_request_version_id', None)
3295
3342
3296 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3343 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3297
3344
3298 attrs.author = StrictAttributeDict(
3345 attrs.author = StrictAttributeDict(
3299 pull_request_obj.author.get_api_data())
3346 pull_request_obj.author.get_api_data())
3300 if pull_request_obj.target_repo:
3347 if pull_request_obj.target_repo:
3301 attrs.target_repo = StrictAttributeDict(
3348 attrs.target_repo = StrictAttributeDict(
3302 pull_request_obj.target_repo.get_api_data())
3349 pull_request_obj.target_repo.get_api_data())
3303 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3350 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3304
3351
3305 if pull_request_obj.source_repo:
3352 if pull_request_obj.source_repo:
3306 attrs.source_repo = StrictAttributeDict(
3353 attrs.source_repo = StrictAttributeDict(
3307 pull_request_obj.source_repo.get_api_data())
3354 pull_request_obj.source_repo.get_api_data())
3308 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3355 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3309
3356
3310 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3357 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3311 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3358 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3312 attrs.revisions = pull_request_obj.revisions
3359 attrs.revisions = pull_request_obj.revisions
3313
3360
3314 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3361 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3315
3362
3316 return PullRequestDisplay(attrs, internal=internal_methods)
3363 return PullRequestDisplay(attrs, internal=internal_methods)
3317
3364
3318 def is_closed(self):
3365 def is_closed(self):
3319 return self.status == self.STATUS_CLOSED
3366 return self.status == self.STATUS_CLOSED
3320
3367
3321 def __json__(self):
3368 def __json__(self):
3322 return {
3369 return {
3323 'revisions': self.revisions,
3370 'revisions': self.revisions,
3324 }
3371 }
3325
3372
3326 def calculated_review_status(self):
3373 def calculated_review_status(self):
3327 from rhodecode.model.changeset_status import ChangesetStatusModel
3374 from rhodecode.model.changeset_status import ChangesetStatusModel
3328 return ChangesetStatusModel().calculated_review_status(self)
3375 return ChangesetStatusModel().calculated_review_status(self)
3329
3376
3330 def reviewers_statuses(self):
3377 def reviewers_statuses(self):
3331 from rhodecode.model.changeset_status import ChangesetStatusModel
3378 from rhodecode.model.changeset_status import ChangesetStatusModel
3332 return ChangesetStatusModel().reviewers_statuses(self)
3379 return ChangesetStatusModel().reviewers_statuses(self)
3333
3380
3334 @property
3381 @property
3335 def workspace_id(self):
3382 def workspace_id(self):
3336 from rhodecode.model.pull_request import PullRequestModel
3383 from rhodecode.model.pull_request import PullRequestModel
3337 return PullRequestModel()._workspace_id(self)
3384 return PullRequestModel()._workspace_id(self)
3338
3385
3339 def get_shadow_repo(self):
3386 def get_shadow_repo(self):
3340 workspace_id = self.workspace_id
3387 workspace_id = self.workspace_id
3341 vcs_obj = self.target_repo.scm_instance()
3388 vcs_obj = self.target_repo.scm_instance()
3342 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3389 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3343 workspace_id)
3390 workspace_id)
3344 return vcs_obj._get_shadow_instance(shadow_repository_path)
3391 return vcs_obj._get_shadow_instance(shadow_repository_path)
3345
3392
3346
3393
3347 class PullRequestVersion(Base, _PullRequestBase):
3394 class PullRequestVersion(Base, _PullRequestBase):
3348 __tablename__ = 'pull_request_versions'
3395 __tablename__ = 'pull_request_versions'
3349 __table_args__ = (
3396 __table_args__ = (
3350 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3397 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3351 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3398 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3352 )
3399 )
3353
3400
3354 pull_request_version_id = Column(
3401 pull_request_version_id = Column(
3355 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3402 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3356 pull_request_id = Column(
3403 pull_request_id = Column(
3357 'pull_request_id', Integer(),
3404 'pull_request_id', Integer(),
3358 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3405 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3359 pull_request = relationship('PullRequest')
3406 pull_request = relationship('PullRequest')
3360
3407
3361 def __repr__(self):
3408 def __repr__(self):
3362 if self.pull_request_version_id:
3409 if self.pull_request_version_id:
3363 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3410 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3364 else:
3411 else:
3365 return '<DB:PullRequestVersion at %#x>' % id(self)
3412 return '<DB:PullRequestVersion at %#x>' % id(self)
3366
3413
3367 @property
3414 @property
3368 def reviewers(self):
3415 def reviewers(self):
3369 return self.pull_request.reviewers
3416 return self.pull_request.reviewers
3370
3417
3371 @property
3418 @property
3372 def versions(self):
3419 def versions(self):
3373 return self.pull_request.versions
3420 return self.pull_request.versions
3374
3421
3375 def is_closed(self):
3422 def is_closed(self):
3376 # calculate from original
3423 # calculate from original
3377 return self.pull_request.status == self.STATUS_CLOSED
3424 return self.pull_request.status == self.STATUS_CLOSED
3378
3425
3379 def calculated_review_status(self):
3426 def calculated_review_status(self):
3380 return self.pull_request.calculated_review_status()
3427 return self.pull_request.calculated_review_status()
3381
3428
3382 def reviewers_statuses(self):
3429 def reviewers_statuses(self):
3383 return self.pull_request.reviewers_statuses()
3430 return self.pull_request.reviewers_statuses()
3384
3431
3385
3432
3386 class PullRequestReviewers(Base, BaseModel):
3433 class PullRequestReviewers(Base, BaseModel):
3387 __tablename__ = 'pull_request_reviewers'
3434 __tablename__ = 'pull_request_reviewers'
3388 __table_args__ = (
3435 __table_args__ = (
3389 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3436 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3390 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3437 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3391 )
3438 )
3392
3439
3393 def __init__(self, user=None, pull_request=None, reasons=None):
3440 def __init__(self, user=None, pull_request=None, reasons=None):
3394 self.user = user
3441 self.user = user
3395 self.pull_request = pull_request
3442 self.pull_request = pull_request
3396 self.reasons = reasons or []
3443 self.reasons = reasons or []
3397
3444
3398 @hybrid_property
3445 @hybrid_property
3399 def reasons(self):
3446 def reasons(self):
3400 if not self._reasons:
3447 if not self._reasons:
3401 return []
3448 return []
3402 return self._reasons
3449 return self._reasons
3403
3450
3404 @reasons.setter
3451 @reasons.setter
3405 def reasons(self, val):
3452 def reasons(self, val):
3406 val = val or []
3453 val = val or []
3407 if any(not isinstance(x, basestring) for x in val):
3454 if any(not isinstance(x, basestring) for x in val):
3408 raise Exception('invalid reasons type, must be list of strings')
3455 raise Exception('invalid reasons type, must be list of strings')
3409 self._reasons = val
3456 self._reasons = val
3410
3457
3411 pull_requests_reviewers_id = Column(
3458 pull_requests_reviewers_id = Column(
3412 'pull_requests_reviewers_id', Integer(), nullable=False,
3459 'pull_requests_reviewers_id', Integer(), nullable=False,
3413 primary_key=True)
3460 primary_key=True)
3414 pull_request_id = Column(
3461 pull_request_id = Column(
3415 "pull_request_id", Integer(),
3462 "pull_request_id", Integer(),
3416 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3463 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3417 user_id = Column(
3464 user_id = Column(
3418 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3465 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3419 _reasons = Column(
3466 _reasons = Column(
3420 'reason', MutationList.as_mutable(
3467 'reason', MutationList.as_mutable(
3421 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3468 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3422
3469
3423 user = relationship('User')
3470 user = relationship('User')
3424 pull_request = relationship('PullRequest')
3471 pull_request = relationship('PullRequest')
3425
3472
3426
3473
3427 class Notification(Base, BaseModel):
3474 class Notification(Base, BaseModel):
3428 __tablename__ = 'notifications'
3475 __tablename__ = 'notifications'
3429 __table_args__ = (
3476 __table_args__ = (
3430 Index('notification_type_idx', 'type'),
3477 Index('notification_type_idx', 'type'),
3431 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3478 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3432 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3479 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3433 )
3480 )
3434
3481
3435 TYPE_CHANGESET_COMMENT = u'cs_comment'
3482 TYPE_CHANGESET_COMMENT = u'cs_comment'
3436 TYPE_MESSAGE = u'message'
3483 TYPE_MESSAGE = u'message'
3437 TYPE_MENTION = u'mention'
3484 TYPE_MENTION = u'mention'
3438 TYPE_REGISTRATION = u'registration'
3485 TYPE_REGISTRATION = u'registration'
3439 TYPE_PULL_REQUEST = u'pull_request'
3486 TYPE_PULL_REQUEST = u'pull_request'
3440 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3487 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3441
3488
3442 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3489 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3443 subject = Column('subject', Unicode(512), nullable=True)
3490 subject = Column('subject', Unicode(512), nullable=True)
3444 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3491 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3445 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3492 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3446 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3493 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3447 type_ = Column('type', Unicode(255))
3494 type_ = Column('type', Unicode(255))
3448
3495
3449 created_by_user = relationship('User')
3496 created_by_user = relationship('User')
3450 notifications_to_users = relationship('UserNotification', lazy='joined',
3497 notifications_to_users = relationship('UserNotification', lazy='joined',
3451 cascade="all, delete, delete-orphan")
3498 cascade="all, delete, delete-orphan")
3452
3499
3453 @property
3500 @property
3454 def recipients(self):
3501 def recipients(self):
3455 return [x.user for x in UserNotification.query()\
3502 return [x.user for x in UserNotification.query()\
3456 .filter(UserNotification.notification == self)\
3503 .filter(UserNotification.notification == self)\
3457 .order_by(UserNotification.user_id.asc()).all()]
3504 .order_by(UserNotification.user_id.asc()).all()]
3458
3505
3459 @classmethod
3506 @classmethod
3460 def create(cls, created_by, subject, body, recipients, type_=None):
3507 def create(cls, created_by, subject, body, recipients, type_=None):
3461 if type_ is None:
3508 if type_ is None:
3462 type_ = Notification.TYPE_MESSAGE
3509 type_ = Notification.TYPE_MESSAGE
3463
3510
3464 notification = cls()
3511 notification = cls()
3465 notification.created_by_user = created_by
3512 notification.created_by_user = created_by
3466 notification.subject = subject
3513 notification.subject = subject
3467 notification.body = body
3514 notification.body = body
3468 notification.type_ = type_
3515 notification.type_ = type_
3469 notification.created_on = datetime.datetime.now()
3516 notification.created_on = datetime.datetime.now()
3470
3517
3471 for u in recipients:
3518 for u in recipients:
3472 assoc = UserNotification()
3519 assoc = UserNotification()
3473 assoc.notification = notification
3520 assoc.notification = notification
3474
3521
3475 # if created_by is inside recipients mark his notification
3522 # if created_by is inside recipients mark his notification
3476 # as read
3523 # as read
3477 if u.user_id == created_by.user_id:
3524 if u.user_id == created_by.user_id:
3478 assoc.read = True
3525 assoc.read = True
3479
3526
3480 u.notifications.append(assoc)
3527 u.notifications.append(assoc)
3481 Session().add(notification)
3528 Session().add(notification)
3482
3529
3483 return notification
3530 return notification
3484
3531
3485 @property
3532 @property
3486 def description(self):
3533 def description(self):
3487 from rhodecode.model.notification import NotificationModel
3534 from rhodecode.model.notification import NotificationModel
3488 return NotificationModel().make_description(self)
3535 return NotificationModel().make_description(self)
3489
3536
3490
3537
3491 class UserNotification(Base, BaseModel):
3538 class UserNotification(Base, BaseModel):
3492 __tablename__ = 'user_to_notification'
3539 __tablename__ = 'user_to_notification'
3493 __table_args__ = (
3540 __table_args__ = (
3494 UniqueConstraint('user_id', 'notification_id'),
3541 UniqueConstraint('user_id', 'notification_id'),
3495 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3542 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3496 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3543 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3497 )
3544 )
3498 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3545 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3499 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3546 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3500 read = Column('read', Boolean, default=False)
3547 read = Column('read', Boolean, default=False)
3501 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3548 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3502
3549
3503 user = relationship('User', lazy="joined")
3550 user = relationship('User', lazy="joined")
3504 notification = relationship('Notification', lazy="joined",
3551 notification = relationship('Notification', lazy="joined",
3505 order_by=lambda: Notification.created_on.desc(),)
3552 order_by=lambda: Notification.created_on.desc(),)
3506
3553
3507 def mark_as_read(self):
3554 def mark_as_read(self):
3508 self.read = True
3555 self.read = True
3509 Session().add(self)
3556 Session().add(self)
3510
3557
3511
3558
3512 class Gist(Base, BaseModel):
3559 class Gist(Base, BaseModel):
3513 __tablename__ = 'gists'
3560 __tablename__ = 'gists'
3514 __table_args__ = (
3561 __table_args__ = (
3515 Index('g_gist_access_id_idx', 'gist_access_id'),
3562 Index('g_gist_access_id_idx', 'gist_access_id'),
3516 Index('g_created_on_idx', 'created_on'),
3563 Index('g_created_on_idx', 'created_on'),
3517 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3564 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3518 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3565 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3519 )
3566 )
3520 GIST_PUBLIC = u'public'
3567 GIST_PUBLIC = u'public'
3521 GIST_PRIVATE = u'private'
3568 GIST_PRIVATE = u'private'
3522 DEFAULT_FILENAME = u'gistfile1.txt'
3569 DEFAULT_FILENAME = u'gistfile1.txt'
3523
3570
3524 ACL_LEVEL_PUBLIC = u'acl_public'
3571 ACL_LEVEL_PUBLIC = u'acl_public'
3525 ACL_LEVEL_PRIVATE = u'acl_private'
3572 ACL_LEVEL_PRIVATE = u'acl_private'
3526
3573
3527 gist_id = Column('gist_id', Integer(), primary_key=True)
3574 gist_id = Column('gist_id', Integer(), primary_key=True)
3528 gist_access_id = Column('gist_access_id', Unicode(250))
3575 gist_access_id = Column('gist_access_id', Unicode(250))
3529 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3576 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3530 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3577 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3531 gist_expires = Column('gist_expires', Float(53), nullable=False)
3578 gist_expires = Column('gist_expires', Float(53), nullable=False)
3532 gist_type = Column('gist_type', Unicode(128), nullable=False)
3579 gist_type = Column('gist_type', Unicode(128), nullable=False)
3533 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3580 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3534 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3581 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3535 acl_level = Column('acl_level', Unicode(128), nullable=True)
3582 acl_level = Column('acl_level', Unicode(128), nullable=True)
3536
3583
3537 owner = relationship('User')
3584 owner = relationship('User')
3538
3585
3539 def __repr__(self):
3586 def __repr__(self):
3540 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3587 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3541
3588
3542 @classmethod
3589 @classmethod
3543 def get_or_404(cls, id_):
3590 def get_or_404(cls, id_):
3544 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3591 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3545 if not res:
3592 if not res:
3546 raise HTTPNotFound
3593 raise HTTPNotFound
3547 return res
3594 return res
3548
3595
3549 @classmethod
3596 @classmethod
3550 def get_by_access_id(cls, gist_access_id):
3597 def get_by_access_id(cls, gist_access_id):
3551 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3598 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3552
3599
3553 def gist_url(self):
3600 def gist_url(self):
3554 import rhodecode
3601 import rhodecode
3555 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3602 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3556 if alias_url:
3603 if alias_url:
3557 return alias_url.replace('{gistid}', self.gist_access_id)
3604 return alias_url.replace('{gistid}', self.gist_access_id)
3558
3605
3559 return url('gist', gist_id=self.gist_access_id, qualified=True)
3606 return url('gist', gist_id=self.gist_access_id, qualified=True)
3560
3607
3561 @classmethod
3608 @classmethod
3562 def base_path(cls):
3609 def base_path(cls):
3563 """
3610 """
3564 Returns base path when all gists are stored
3611 Returns base path when all gists are stored
3565
3612
3566 :param cls:
3613 :param cls:
3567 """
3614 """
3568 from rhodecode.model.gist import GIST_STORE_LOC
3615 from rhodecode.model.gist import GIST_STORE_LOC
3569 q = Session().query(RhodeCodeUi)\
3616 q = Session().query(RhodeCodeUi)\
3570 .filter(RhodeCodeUi.ui_key == URL_SEP)
3617 .filter(RhodeCodeUi.ui_key == URL_SEP)
3571 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3618 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3572 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3619 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3573
3620
3574 def get_api_data(self):
3621 def get_api_data(self):
3575 """
3622 """
3576 Common function for generating gist related data for API
3623 Common function for generating gist related data for API
3577 """
3624 """
3578 gist = self
3625 gist = self
3579 data = {
3626 data = {
3580 'gist_id': gist.gist_id,
3627 'gist_id': gist.gist_id,
3581 'type': gist.gist_type,
3628 'type': gist.gist_type,
3582 'access_id': gist.gist_access_id,
3629 'access_id': gist.gist_access_id,
3583 'description': gist.gist_description,
3630 'description': gist.gist_description,
3584 'url': gist.gist_url(),
3631 'url': gist.gist_url(),
3585 'expires': gist.gist_expires,
3632 'expires': gist.gist_expires,
3586 'created_on': gist.created_on,
3633 'created_on': gist.created_on,
3587 'modified_at': gist.modified_at,
3634 'modified_at': gist.modified_at,
3588 'content': None,
3635 'content': None,
3589 'acl_level': gist.acl_level,
3636 'acl_level': gist.acl_level,
3590 }
3637 }
3591 return data
3638 return data
3592
3639
3593 def __json__(self):
3640 def __json__(self):
3594 data = dict(
3641 data = dict(
3595 )
3642 )
3596 data.update(self.get_api_data())
3643 data.update(self.get_api_data())
3597 return data
3644 return data
3598 # SCM functions
3645 # SCM functions
3599
3646
3600 def scm_instance(self, **kwargs):
3647 def scm_instance(self, **kwargs):
3601 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3648 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3602 return get_vcs_instance(
3649 return get_vcs_instance(
3603 repo_path=safe_str(full_repo_path), create=False)
3650 repo_path=safe_str(full_repo_path), create=False)
3604
3651
3605
3652
3606 class ExternalIdentity(Base, BaseModel):
3653 class ExternalIdentity(Base, BaseModel):
3607 __tablename__ = 'external_identities'
3654 __tablename__ = 'external_identities'
3608 __table_args__ = (
3655 __table_args__ = (
3609 Index('local_user_id_idx', 'local_user_id'),
3656 Index('local_user_id_idx', 'local_user_id'),
3610 Index('external_id_idx', 'external_id'),
3657 Index('external_id_idx', 'external_id'),
3611 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3658 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3612 'mysql_charset': 'utf8'})
3659 'mysql_charset': 'utf8'})
3613
3660
3614 external_id = Column('external_id', Unicode(255), default=u'',
3661 external_id = Column('external_id', Unicode(255), default=u'',
3615 primary_key=True)
3662 primary_key=True)
3616 external_username = Column('external_username', Unicode(1024), default=u'')
3663 external_username = Column('external_username', Unicode(1024), default=u'')
3617 local_user_id = Column('local_user_id', Integer(),
3664 local_user_id = Column('local_user_id', Integer(),
3618 ForeignKey('users.user_id'), primary_key=True)
3665 ForeignKey('users.user_id'), primary_key=True)
3619 provider_name = Column('provider_name', Unicode(255), default=u'',
3666 provider_name = Column('provider_name', Unicode(255), default=u'',
3620 primary_key=True)
3667 primary_key=True)
3621 access_token = Column('access_token', String(1024), default=u'')
3668 access_token = Column('access_token', String(1024), default=u'')
3622 alt_token = Column('alt_token', String(1024), default=u'')
3669 alt_token = Column('alt_token', String(1024), default=u'')
3623 token_secret = Column('token_secret', String(1024), default=u'')
3670 token_secret = Column('token_secret', String(1024), default=u'')
3624
3671
3625 @classmethod
3672 @classmethod
3626 def by_external_id_and_provider(cls, external_id, provider_name,
3673 def by_external_id_and_provider(cls, external_id, provider_name,
3627 local_user_id=None):
3674 local_user_id=None):
3628 """
3675 """
3629 Returns ExternalIdentity instance based on search params
3676 Returns ExternalIdentity instance based on search params
3630
3677
3631 :param external_id:
3678 :param external_id:
3632 :param provider_name:
3679 :param provider_name:
3633 :return: ExternalIdentity
3680 :return: ExternalIdentity
3634 """
3681 """
3635 query = cls.query()
3682 query = cls.query()
3636 query = query.filter(cls.external_id == external_id)
3683 query = query.filter(cls.external_id == external_id)
3637 query = query.filter(cls.provider_name == provider_name)
3684 query = query.filter(cls.provider_name == provider_name)
3638 if local_user_id:
3685 if local_user_id:
3639 query = query.filter(cls.local_user_id == local_user_id)
3686 query = query.filter(cls.local_user_id == local_user_id)
3640 return query.first()
3687 return query.first()
3641
3688
3642 @classmethod
3689 @classmethod
3643 def user_by_external_id_and_provider(cls, external_id, provider_name):
3690 def user_by_external_id_and_provider(cls, external_id, provider_name):
3644 """
3691 """
3645 Returns User instance based on search params
3692 Returns User instance based on search params
3646
3693
3647 :param external_id:
3694 :param external_id:
3648 :param provider_name:
3695 :param provider_name:
3649 :return: User
3696 :return: User
3650 """
3697 """
3651 query = User.query()
3698 query = User.query()
3652 query = query.filter(cls.external_id == external_id)
3699 query = query.filter(cls.external_id == external_id)
3653 query = query.filter(cls.provider_name == provider_name)
3700 query = query.filter(cls.provider_name == provider_name)
3654 query = query.filter(User.user_id == cls.local_user_id)
3701 query = query.filter(User.user_id == cls.local_user_id)
3655 return query.first()
3702 return query.first()
3656
3703
3657 @classmethod
3704 @classmethod
3658 def by_local_user_id(cls, local_user_id):
3705 def by_local_user_id(cls, local_user_id):
3659 """
3706 """
3660 Returns all tokens for user
3707 Returns all tokens for user
3661
3708
3662 :param local_user_id:
3709 :param local_user_id:
3663 :return: ExternalIdentity
3710 :return: ExternalIdentity
3664 """
3711 """
3665 query = cls.query()
3712 query = cls.query()
3666 query = query.filter(cls.local_user_id == local_user_id)
3713 query = query.filter(cls.local_user_id == local_user_id)
3667 return query
3714 return query
3668
3715
3669
3716
3670 class Integration(Base, BaseModel):
3717 class Integration(Base, BaseModel):
3671 __tablename__ = 'integrations'
3718 __tablename__ = 'integrations'
3672 __table_args__ = (
3719 __table_args__ = (
3673 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3720 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3674 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3721 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3675 )
3722 )
3676
3723
3677 integration_id = Column('integration_id', Integer(), primary_key=True)
3724 integration_id = Column('integration_id', Integer(), primary_key=True)
3678 integration_type = Column('integration_type', String(255))
3725 integration_type = Column('integration_type', String(255))
3679 enabled = Column('enabled', Boolean(), nullable=False)
3726 enabled = Column('enabled', Boolean(), nullable=False)
3680 name = Column('name', String(255), nullable=False)
3727 name = Column('name', String(255), nullable=False)
3681 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3728 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3682 default=False)
3729 default=False)
3683
3730
3684 settings = Column(
3731 settings = Column(
3685 'settings_json', MutationObj.as_mutable(
3732 'settings_json', MutationObj.as_mutable(
3686 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3733 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3687 repo_id = Column(
3734 repo_id = Column(
3688 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3735 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3689 nullable=True, unique=None, default=None)
3736 nullable=True, unique=None, default=None)
3690 repo = relationship('Repository', lazy='joined')
3737 repo = relationship('Repository', lazy='joined')
3691
3738
3692 repo_group_id = Column(
3739 repo_group_id = Column(
3693 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3740 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3694 nullable=True, unique=None, default=None)
3741 nullable=True, unique=None, default=None)
3695 repo_group = relationship('RepoGroup', lazy='joined')
3742 repo_group = relationship('RepoGroup', lazy='joined')
3696
3743
3697 @property
3744 @property
3698 def scope(self):
3745 def scope(self):
3699 if self.repo:
3746 if self.repo:
3700 return repr(self.repo)
3747 return repr(self.repo)
3701 if self.repo_group:
3748 if self.repo_group:
3702 if self.child_repos_only:
3749 if self.child_repos_only:
3703 return repr(self.repo_group) + ' (child repos only)'
3750 return repr(self.repo_group) + ' (child repos only)'
3704 else:
3751 else:
3705 return repr(self.repo_group) + ' (recursive)'
3752 return repr(self.repo_group) + ' (recursive)'
3706 if self.child_repos_only:
3753 if self.child_repos_only:
3707 return 'root_repos'
3754 return 'root_repos'
3708 return 'global'
3755 return 'global'
3709
3756
3710 def __repr__(self):
3757 def __repr__(self):
3711 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3758 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3712
3759
3713
3760
3714 class RepoReviewRuleUser(Base, BaseModel):
3761 class RepoReviewRuleUser(Base, BaseModel):
3715 __tablename__ = 'repo_review_rules_users'
3762 __tablename__ = 'repo_review_rules_users'
3716 __table_args__ = (
3763 __table_args__ = (
3717 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3764 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3718 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3765 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3719 )
3766 )
3720 repo_review_rule_user_id = Column(
3767 repo_review_rule_user_id = Column(
3721 'repo_review_rule_user_id', Integer(), primary_key=True)
3768 'repo_review_rule_user_id', Integer(), primary_key=True)
3722 repo_review_rule_id = Column("repo_review_rule_id",
3769 repo_review_rule_id = Column("repo_review_rule_id",
3723 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3770 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3724 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3771 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3725 nullable=False)
3772 nullable=False)
3726 user = relationship('User')
3773 user = relationship('User')
3727
3774
3728
3775
3729 class RepoReviewRuleUserGroup(Base, BaseModel):
3776 class RepoReviewRuleUserGroup(Base, BaseModel):
3730 __tablename__ = 'repo_review_rules_users_groups'
3777 __tablename__ = 'repo_review_rules_users_groups'
3731 __table_args__ = (
3778 __table_args__ = (
3732 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3779 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3733 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3780 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3734 )
3781 )
3735 repo_review_rule_users_group_id = Column(
3782 repo_review_rule_users_group_id = Column(
3736 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3783 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3737 repo_review_rule_id = Column("repo_review_rule_id",
3784 repo_review_rule_id = Column("repo_review_rule_id",
3738 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3785 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3739 users_group_id = Column("users_group_id", Integer(),
3786 users_group_id = Column("users_group_id", Integer(),
3740 ForeignKey('users_groups.users_group_id'), nullable=False)
3787 ForeignKey('users_groups.users_group_id'), nullable=False)
3741 users_group = relationship('UserGroup')
3788 users_group = relationship('UserGroup')
3742
3789
3743
3790
3744 class RepoReviewRule(Base, BaseModel):
3791 class RepoReviewRule(Base, BaseModel):
3745 __tablename__ = 'repo_review_rules'
3792 __tablename__ = 'repo_review_rules'
3746 __table_args__ = (
3793 __table_args__ = (
3747 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3794 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3748 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3795 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3749 )
3796 )
3750
3797
3751 repo_review_rule_id = Column(
3798 repo_review_rule_id = Column(
3752 'repo_review_rule_id', Integer(), primary_key=True)
3799 'repo_review_rule_id', Integer(), primary_key=True)
3753 repo_id = Column(
3800 repo_id = Column(
3754 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3801 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3755 repo = relationship('Repository', backref='review_rules')
3802 repo = relationship('Repository', backref='review_rules')
3756
3803
3757 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3804 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3758 default=u'*') # glob
3805 default=u'*') # glob
3759 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3806 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3760 default=u'*') # glob
3807 default=u'*') # glob
3761
3808
3762 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3809 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3763 nullable=False, default=False)
3810 nullable=False, default=False)
3764 rule_users = relationship('RepoReviewRuleUser')
3811 rule_users = relationship('RepoReviewRuleUser')
3765 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3812 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3766
3813
3767 @hybrid_property
3814 @hybrid_property
3768 def branch_pattern(self):
3815 def branch_pattern(self):
3769 return self._branch_pattern or '*'
3816 return self._branch_pattern or '*'
3770
3817
3771 def _validate_glob(self, value):
3818 def _validate_glob(self, value):
3772 re.compile('^' + glob2re(value) + '$')
3819 re.compile('^' + glob2re(value) + '$')
3773
3820
3774 @branch_pattern.setter
3821 @branch_pattern.setter
3775 def branch_pattern(self, value):
3822 def branch_pattern(self, value):
3776 self._validate_glob(value)
3823 self._validate_glob(value)
3777 self._branch_pattern = value or '*'
3824 self._branch_pattern = value or '*'
3778
3825
3779 @hybrid_property
3826 @hybrid_property
3780 def file_pattern(self):
3827 def file_pattern(self):
3781 return self._file_pattern or '*'
3828 return self._file_pattern or '*'
3782
3829
3783 @file_pattern.setter
3830 @file_pattern.setter
3784 def file_pattern(self, value):
3831 def file_pattern(self, value):
3785 self._validate_glob(value)
3832 self._validate_glob(value)
3786 self._file_pattern = value or '*'
3833 self._file_pattern = value or '*'
3787
3834
3788 def matches(self, branch, files_changed):
3835 def matches(self, branch, files_changed):
3789 """
3836 """
3790 Check if this review rule matches a branch/files in a pull request
3837 Check if this review rule matches a branch/files in a pull request
3791
3838
3792 :param branch: branch name for the commit
3839 :param branch: branch name for the commit
3793 :param files_changed: list of file paths changed in the pull request
3840 :param files_changed: list of file paths changed in the pull request
3794 """
3841 """
3795
3842
3796 branch = branch or ''
3843 branch = branch or ''
3797 files_changed = files_changed or []
3844 files_changed = files_changed or []
3798
3845
3799 branch_matches = True
3846 branch_matches = True
3800 if branch:
3847 if branch:
3801 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3848 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3802 branch_matches = bool(branch_regex.search(branch))
3849 branch_matches = bool(branch_regex.search(branch))
3803
3850
3804 files_matches = True
3851 files_matches = True
3805 if self.file_pattern != '*':
3852 if self.file_pattern != '*':
3806 files_matches = False
3853 files_matches = False
3807 file_regex = re.compile(glob2re(self.file_pattern))
3854 file_regex = re.compile(glob2re(self.file_pattern))
3808 for filename in files_changed:
3855 for filename in files_changed:
3809 if file_regex.search(filename):
3856 if file_regex.search(filename):
3810 files_matches = True
3857 files_matches = True
3811 break
3858 break
3812
3859
3813 return branch_matches and files_matches
3860 return branch_matches and files_matches
3814
3861
3815 @property
3862 @property
3816 def review_users(self):
3863 def review_users(self):
3817 """ Returns the users which this rule applies to """
3864 """ Returns the users which this rule applies to """
3818
3865
3819 users = set()
3866 users = set()
3820 users |= set([
3867 users |= set([
3821 rule_user.user for rule_user in self.rule_users
3868 rule_user.user for rule_user in self.rule_users
3822 if rule_user.user.active])
3869 if rule_user.user.active])
3823 users |= set(
3870 users |= set(
3824 member.user
3871 member.user
3825 for rule_user_group in self.rule_user_groups
3872 for rule_user_group in self.rule_user_groups
3826 for member in rule_user_group.users_group.members
3873 for member in rule_user_group.users_group.members
3827 if member.user.active
3874 if member.user.active
3828 )
3875 )
3829 return users
3876 return users
3830
3877
3831 def __repr__(self):
3878 def __repr__(self):
3832 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3879 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3833 self.repo_review_rule_id, self.repo)
3880 self.repo_review_rule_id, self.repo)
3834
3881
3835
3882
3836 class DbMigrateVersion(Base, BaseModel):
3883 class DbMigrateVersion(Base, BaseModel):
3837 __tablename__ = 'db_migrate_version'
3884 __tablename__ = 'db_migrate_version'
3838 __table_args__ = (
3885 __table_args__ = (
3839 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3886 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3840 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3887 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3841 )
3888 )
3842 repository_id = Column('repository_id', String(250), primary_key=True)
3889 repository_id = Column('repository_id', String(250), primary_key=True)
3843 repository_path = Column('repository_path', Text)
3890 repository_path = Column('repository_path', Text)
3844 version = Column('version', Integer)
3891 version = Column('version', Integer)
3845
3892
3846
3893
3847 class DbSession(Base, BaseModel):
3894 class DbSession(Base, BaseModel):
3848 __tablename__ = 'db_session'
3895 __tablename__ = 'db_session'
3849 __table_args__ = (
3896 __table_args__ = (
3850 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3897 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3851 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3898 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3852 )
3899 )
3853
3900
3854 def __repr__(self):
3901 def __repr__(self):
3855 return '<DB:DbSession({})>'.format(self.id)
3902 return '<DB:DbSession({})>'.format(self.id)
3856
3903
3857 id = Column('id', Integer())
3904 id = Column('id', Integer())
3858 namespace = Column('namespace', String(255), primary_key=True)
3905 namespace = Column('namespace', String(255), primary_key=True)
3859 accessed = Column('accessed', DateTime, nullable=False)
3906 accessed = Column('accessed', DateTime, nullable=False)
3860 created = Column('created', DateTime, nullable=False)
3907 created = Column('created', DateTime, nullable=False)
3861 data = Column('data', PickleType, nullable=False)
3908 data = Column('data', PickleType, nullable=False)
@@ -1,54 +1,75 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20 from rhodecode.model.auth_token import AuthTokenModel
21 from rhodecode.model.db import User
21 from rhodecode.model.db import User
22 from rhodecode.tests import *
22 from rhodecode.tests import *
23
23
24
24
25 class TestFeedController(TestController):
25 class TestFeedController(TestController):
26
26
27 def test_rss(self, backend):
27 def test_rss(self, backend):
28 self.log_user()
28 self.log_user()
29 response = self.app.get(url(controller='feed', action='rss',
29 response = self.app.get(url(controller='feed', action='rss',
30 repo_name=backend.repo_name))
30 repo_name=backend.repo_name))
31
31
32 assert response.content_type == "application/rss+xml"
32 assert response.content_type == "application/rss+xml"
33 assert """<rss version="2.0">""" in response
33 assert """<rss version="2.0">""" in response
34
34
35 def test_rss_with_auth_token(self, backend):
35 def test_rss_with_auth_token(self, backend, user_admin):
36 auth_token = User.get_first_super_admin().feed_token
36 auth_token = user_admin.feed_token
37 assert auth_token != ''
37 assert auth_token != ''
38 response = self.app.get(url(controller='feed', action='rss',
38 response = self.app.get(
39 repo_name=backend.repo_name, auth_token=auth_token))
39 url(controller='feed', action='rss',
40 repo_name=backend.repo_name, auth_token=auth_token,
41 status=200))
40
42
41 assert response.content_type == "application/rss+xml"
43 assert response.content_type == "application/rss+xml"
42 assert """<rss version="2.0">""" in response
44 assert """<rss version="2.0">""" in response
43
45
46 def test_rss_with_auth_token_of_wrong_type(self, backend, user_util):
47 user = user_util.create_user()
48 auth_token = AuthTokenModel().create(
49 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_API)
50 auth_token = auth_token.api_key
51
52 self.app.get(
53 url(controller='feed', action='rss',
54 repo_name=backend.repo_name, auth_token=auth_token),
55 status=302)
56
57 auth_token = AuthTokenModel().create(
58 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_FEED)
59 auth_token = auth_token.api_key
60 self.app.get(
61 url(controller='feed', action='rss',
62 repo_name=backend.repo_name, auth_token=auth_token),
63 status=200)
64
44 def test_atom(self, backend):
65 def test_atom(self, backend):
45 self.log_user()
66 self.log_user()
46 response = self.app.get(url(controller='feed', action='atom',
67 response = self.app.get(url(controller='feed', action='atom',
47 repo_name=backend.repo_name))
68 repo_name=backend.repo_name))
48
69
49 assert response.content_type == """application/atom+xml"""
70 assert response.content_type == """application/atom+xml"""
50 assert """<?xml version="1.0" encoding="utf-8"?>""" in response
71 assert """<?xml version="1.0" encoding="utf-8"?>""" in response
51
72
52 tag1 = '<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us">'
73 tag1 = '<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us">'
53 tag2 = '<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom">'
74 tag2 = '<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom">'
54 assert tag1 in response or tag2 in response
75 assert tag1 in response or tag2 in response
@@ -1,588 +1,591 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import urlparse
21 import urlparse
22
22
23 import mock
23 import mock
24 import pytest
24 import pytest
25
25
26 from rhodecode.config.routing import ADMIN_PREFIX
26 from rhodecode.config.routing import ADMIN_PREFIX
27 from rhodecode.tests import (
27 from rhodecode.tests import (
28 TestController, assert_session_flash, clear_all_caches, url,
28 TestController, assert_session_flash, clear_all_caches, url,
29 HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
29 HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
30 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.fixture import Fixture
31 from rhodecode.tests.utils import AssertResponse, get_session_from_response
31 from rhodecode.tests.utils import AssertResponse, get_session_from_response
32 from rhodecode.lib.auth import check_password, generate_auth_token
32 from rhodecode.lib.auth import check_password, generate_auth_token
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.model.auth_token import AuthTokenModel
34 from rhodecode.model.auth_token import AuthTokenModel
35 from rhodecode.model import validators
35 from rhodecode.model import validators
36 from rhodecode.model.db import User, Notification
36 from rhodecode.model.db import User, Notification
37 from rhodecode.model.meta import Session
37 from rhodecode.model.meta import Session
38
38
39 fixture = Fixture()
39 fixture = Fixture()
40
40
41 # Hardcode URLs because we don't have a request object to use
41 # Hardcode URLs because we don't have a request object to use
42 # pyramids URL generation methods.
42 # pyramids URL generation methods.
43 index_url = '/'
43 index_url = '/'
44 login_url = ADMIN_PREFIX + '/login'
44 login_url = ADMIN_PREFIX + '/login'
45 logut_url = ADMIN_PREFIX + '/logout'
45 logut_url = ADMIN_PREFIX + '/logout'
46 register_url = ADMIN_PREFIX + '/register'
46 register_url = ADMIN_PREFIX + '/register'
47 pwd_reset_url = ADMIN_PREFIX + '/password_reset'
47 pwd_reset_url = ADMIN_PREFIX + '/password_reset'
48 pwd_reset_confirm_url = ADMIN_PREFIX + '/password_reset_confirmation'
48 pwd_reset_confirm_url = ADMIN_PREFIX + '/password_reset_confirmation'
49
49
50
50
51 @pytest.mark.usefixtures('app')
51 @pytest.mark.usefixtures('app')
52 class TestLoginController:
52 class TestLoginController:
53 destroy_users = set()
53 destroy_users = set()
54
54
55 @classmethod
55 @classmethod
56 def teardown_class(cls):
56 def teardown_class(cls):
57 fixture.destroy_users(cls.destroy_users)
57 fixture.destroy_users(cls.destroy_users)
58
58
59 def teardown_method(self, method):
59 def teardown_method(self, method):
60 for n in Notification.query().all():
60 for n in Notification.query().all():
61 Session().delete(n)
61 Session().delete(n)
62
62
63 Session().commit()
63 Session().commit()
64 assert Notification.query().all() == []
64 assert Notification.query().all() == []
65
65
66 def test_index(self):
66 def test_index(self):
67 response = self.app.get(login_url)
67 response = self.app.get(login_url)
68 assert response.status == '200 OK'
68 assert response.status == '200 OK'
69 # Test response...
69 # Test response...
70
70
71 def test_login_admin_ok(self):
71 def test_login_admin_ok(self):
72 response = self.app.post(login_url,
72 response = self.app.post(login_url,
73 {'username': 'test_admin',
73 {'username': 'test_admin',
74 'password': 'test12'})
74 'password': 'test12'})
75 assert response.status == '302 Found'
75 assert response.status == '302 Found'
76 session = get_session_from_response(response)
76 session = get_session_from_response(response)
77 username = session['rhodecode_user'].get('username')
77 username = session['rhodecode_user'].get('username')
78 assert username == 'test_admin'
78 assert username == 'test_admin'
79 response = response.follow()
79 response = response.follow()
80 response.mustcontain('/%s' % HG_REPO)
80 response.mustcontain('/%s' % HG_REPO)
81
81
82 def test_login_regular_ok(self):
82 def test_login_regular_ok(self):
83 response = self.app.post(login_url,
83 response = self.app.post(login_url,
84 {'username': 'test_regular',
84 {'username': 'test_regular',
85 'password': 'test12'})
85 'password': 'test12'})
86
86
87 assert response.status == '302 Found'
87 assert response.status == '302 Found'
88 session = get_session_from_response(response)
88 session = get_session_from_response(response)
89 username = session['rhodecode_user'].get('username')
89 username = session['rhodecode_user'].get('username')
90 assert username == 'test_regular'
90 assert username == 'test_regular'
91 response = response.follow()
91 response = response.follow()
92 response.mustcontain('/%s' % HG_REPO)
92 response.mustcontain('/%s' % HG_REPO)
93
93
94 def test_login_ok_came_from(self):
94 def test_login_ok_came_from(self):
95 test_came_from = '/_admin/users?branch=stable'
95 test_came_from = '/_admin/users?branch=stable'
96 _url = '{}?came_from={}'.format(login_url, test_came_from)
96 _url = '{}?came_from={}'.format(login_url, test_came_from)
97 response = self.app.post(
97 response = self.app.post(
98 _url, {'username': 'test_admin', 'password': 'test12'})
98 _url, {'username': 'test_admin', 'password': 'test12'})
99 assert response.status == '302 Found'
99 assert response.status == '302 Found'
100 assert 'branch=stable' in response.location
100 assert 'branch=stable' in response.location
101 response = response.follow()
101 response = response.follow()
102
102
103 assert response.status == '200 OK'
103 assert response.status == '200 OK'
104 response.mustcontain('Users administration')
104 response.mustcontain('Users administration')
105
105
106 def test_redirect_to_login_with_get_args(self):
106 def test_redirect_to_login_with_get_args(self):
107 with fixture.anon_access(False):
107 with fixture.anon_access(False):
108 kwargs = {'branch': 'stable'}
108 kwargs = {'branch': 'stable'}
109 response = self.app.get(
109 response = self.app.get(
110 url('summary_home', repo_name=HG_REPO, **kwargs))
110 url('summary_home', repo_name=HG_REPO, **kwargs))
111 assert response.status == '302 Found'
111 assert response.status == '302 Found'
112 response_query = urlparse.parse_qsl(response.location)
112 response_query = urlparse.parse_qsl(response.location)
113 assert 'branch=stable' in response_query[0][1]
113 assert 'branch=stable' in response_query[0][1]
114
114
115 def test_login_form_with_get_args(self):
115 def test_login_form_with_get_args(self):
116 _url = '{}?came_from=/_admin/users,branch=stable'.format(login_url)
116 _url = '{}?came_from=/_admin/users,branch=stable'.format(login_url)
117 response = self.app.get(_url)
117 response = self.app.get(_url)
118 assert 'branch%3Dstable' in response.form.action
118 assert 'branch%3Dstable' in response.form.action
119
119
120 @pytest.mark.parametrize("url_came_from", [
120 @pytest.mark.parametrize("url_came_from", [
121 'data:text/html,<script>window.alert("xss")</script>',
121 'data:text/html,<script>window.alert("xss")</script>',
122 'mailto:test@rhodecode.org',
122 'mailto:test@rhodecode.org',
123 'file:///etc/passwd',
123 'file:///etc/passwd',
124 'ftp://some.ftp.server',
124 'ftp://some.ftp.server',
125 'http://other.domain',
125 'http://other.domain',
126 '/\r\nX-Forwarded-Host: http://example.org',
126 '/\r\nX-Forwarded-Host: http://example.org',
127 ])
127 ])
128 def test_login_bad_came_froms(self, url_came_from):
128 def test_login_bad_came_froms(self, url_came_from):
129 _url = '{}?came_from={}'.format(login_url, url_came_from)
129 _url = '{}?came_from={}'.format(login_url, url_came_from)
130 response = self.app.post(
130 response = self.app.post(
131 _url,
131 _url,
132 {'username': 'test_admin', 'password': 'test12'})
132 {'username': 'test_admin', 'password': 'test12'})
133 assert response.status == '302 Found'
133 assert response.status == '302 Found'
134 response = response.follow()
134 response = response.follow()
135 assert response.status == '200 OK'
135 assert response.status == '200 OK'
136 assert response.request.path == '/'
136 assert response.request.path == '/'
137
137
138 def test_login_short_password(self):
138 def test_login_short_password(self):
139 response = self.app.post(login_url,
139 response = self.app.post(login_url,
140 {'username': 'test_admin',
140 {'username': 'test_admin',
141 'password': 'as'})
141 'password': 'as'})
142 assert response.status == '200 OK'
142 assert response.status == '200 OK'
143
143
144 response.mustcontain('Enter 3 characters or more')
144 response.mustcontain('Enter 3 characters or more')
145
145
146 def test_login_wrong_non_ascii_password(self, user_regular):
146 def test_login_wrong_non_ascii_password(self, user_regular):
147 response = self.app.post(
147 response = self.app.post(
148 login_url,
148 login_url,
149 {'username': user_regular.username,
149 {'username': user_regular.username,
150 'password': u'invalid-non-asci\xe4'.encode('utf8')})
150 'password': u'invalid-non-asci\xe4'.encode('utf8')})
151
151
152 response.mustcontain('invalid user name')
152 response.mustcontain('invalid user name')
153 response.mustcontain('invalid password')
153 response.mustcontain('invalid password')
154
154
155 def test_login_with_non_ascii_password(self, user_util):
155 def test_login_with_non_ascii_password(self, user_util):
156 password = u'valid-non-ascii\xe4'
156 password = u'valid-non-ascii\xe4'
157 user = user_util.create_user(password=password)
157 user = user_util.create_user(password=password)
158 response = self.app.post(
158 response = self.app.post(
159 login_url,
159 login_url,
160 {'username': user.username,
160 {'username': user.username,
161 'password': password.encode('utf-8')})
161 'password': password.encode('utf-8')})
162 assert response.status_code == 302
162 assert response.status_code == 302
163
163
164 def test_login_wrong_username_password(self):
164 def test_login_wrong_username_password(self):
165 response = self.app.post(login_url,
165 response = self.app.post(login_url,
166 {'username': 'error',
166 {'username': 'error',
167 'password': 'test12'})
167 'password': 'test12'})
168
168
169 response.mustcontain('invalid user name')
169 response.mustcontain('invalid user name')
170 response.mustcontain('invalid password')
170 response.mustcontain('invalid password')
171
171
172 def test_login_admin_ok_password_migration(self, real_crypto_backend):
172 def test_login_admin_ok_password_migration(self, real_crypto_backend):
173 from rhodecode.lib import auth
173 from rhodecode.lib import auth
174
174
175 # create new user, with sha256 password
175 # create new user, with sha256 password
176 temp_user = 'test_admin_sha256'
176 temp_user = 'test_admin_sha256'
177 user = fixture.create_user(temp_user)
177 user = fixture.create_user(temp_user)
178 user.password = auth._RhodeCodeCryptoSha256().hash_create(
178 user.password = auth._RhodeCodeCryptoSha256().hash_create(
179 b'test123')
179 b'test123')
180 Session().add(user)
180 Session().add(user)
181 Session().commit()
181 Session().commit()
182 self.destroy_users.add(temp_user)
182 self.destroy_users.add(temp_user)
183 response = self.app.post(login_url,
183 response = self.app.post(login_url,
184 {'username': temp_user,
184 {'username': temp_user,
185 'password': 'test123'})
185 'password': 'test123'})
186
186
187 assert response.status == '302 Found'
187 assert response.status == '302 Found'
188 session = get_session_from_response(response)
188 session = get_session_from_response(response)
189 username = session['rhodecode_user'].get('username')
189 username = session['rhodecode_user'].get('username')
190 assert username == temp_user
190 assert username == temp_user
191 response = response.follow()
191 response = response.follow()
192 response.mustcontain('/%s' % HG_REPO)
192 response.mustcontain('/%s' % HG_REPO)
193
193
194 # new password should be bcrypted, after log-in and transfer
194 # new password should be bcrypted, after log-in and transfer
195 user = User.get_by_username(temp_user)
195 user = User.get_by_username(temp_user)
196 assert user.password.startswith('$')
196 assert user.password.startswith('$')
197
197
198 # REGISTRATIONS
198 # REGISTRATIONS
199 def test_register(self):
199 def test_register(self):
200 response = self.app.get(register_url)
200 response = self.app.get(register_url)
201 response.mustcontain('Create an Account')
201 response.mustcontain('Create an Account')
202
202
203 def test_register_err_same_username(self):
203 def test_register_err_same_username(self):
204 uname = 'test_admin'
204 uname = 'test_admin'
205 response = self.app.post(
205 response = self.app.post(
206 register_url,
206 register_url,
207 {
207 {
208 'username': uname,
208 'username': uname,
209 'password': 'test12',
209 'password': 'test12',
210 'password_confirmation': 'test12',
210 'password_confirmation': 'test12',
211 'email': 'goodmail@domain.com',
211 'email': 'goodmail@domain.com',
212 'firstname': 'test',
212 'firstname': 'test',
213 'lastname': 'test'
213 'lastname': 'test'
214 }
214 }
215 )
215 )
216
216
217 assertr = AssertResponse(response)
217 assertr = AssertResponse(response)
218 msg = validators.ValidUsername()._messages['username_exists']
218 msg = validators.ValidUsername()._messages['username_exists']
219 msg = msg % {'username': uname}
219 msg = msg % {'username': uname}
220 assertr.element_contains('#username+.error-message', msg)
220 assertr.element_contains('#username+.error-message', msg)
221
221
222 def test_register_err_same_email(self):
222 def test_register_err_same_email(self):
223 response = self.app.post(
223 response = self.app.post(
224 register_url,
224 register_url,
225 {
225 {
226 'username': 'test_admin_0',
226 'username': 'test_admin_0',
227 'password': 'test12',
227 'password': 'test12',
228 'password_confirmation': 'test12',
228 'password_confirmation': 'test12',
229 'email': 'test_admin@mail.com',
229 'email': 'test_admin@mail.com',
230 'firstname': 'test',
230 'firstname': 'test',
231 'lastname': 'test'
231 'lastname': 'test'
232 }
232 }
233 )
233 )
234
234
235 assertr = AssertResponse(response)
235 assertr = AssertResponse(response)
236 msg = validators.UniqSystemEmail()()._messages['email_taken']
236 msg = validators.UniqSystemEmail()()._messages['email_taken']
237 assertr.element_contains('#email+.error-message', msg)
237 assertr.element_contains('#email+.error-message', msg)
238
238
239 def test_register_err_same_email_case_sensitive(self):
239 def test_register_err_same_email_case_sensitive(self):
240 response = self.app.post(
240 response = self.app.post(
241 register_url,
241 register_url,
242 {
242 {
243 'username': 'test_admin_1',
243 'username': 'test_admin_1',
244 'password': 'test12',
244 'password': 'test12',
245 'password_confirmation': 'test12',
245 'password_confirmation': 'test12',
246 'email': 'TesT_Admin@mail.COM',
246 'email': 'TesT_Admin@mail.COM',
247 'firstname': 'test',
247 'firstname': 'test',
248 'lastname': 'test'
248 'lastname': 'test'
249 }
249 }
250 )
250 )
251 assertr = AssertResponse(response)
251 assertr = AssertResponse(response)
252 msg = validators.UniqSystemEmail()()._messages['email_taken']
252 msg = validators.UniqSystemEmail()()._messages['email_taken']
253 assertr.element_contains('#email+.error-message', msg)
253 assertr.element_contains('#email+.error-message', msg)
254
254
255 def test_register_err_wrong_data(self):
255 def test_register_err_wrong_data(self):
256 response = self.app.post(
256 response = self.app.post(
257 register_url,
257 register_url,
258 {
258 {
259 'username': 'xs',
259 'username': 'xs',
260 'password': 'test',
260 'password': 'test',
261 'password_confirmation': 'test',
261 'password_confirmation': 'test',
262 'email': 'goodmailm',
262 'email': 'goodmailm',
263 'firstname': 'test',
263 'firstname': 'test',
264 'lastname': 'test'
264 'lastname': 'test'
265 }
265 }
266 )
266 )
267 assert response.status == '200 OK'
267 assert response.status == '200 OK'
268 response.mustcontain('An email address must contain a single @')
268 response.mustcontain('An email address must contain a single @')
269 response.mustcontain('Enter a value 6 characters long or more')
269 response.mustcontain('Enter a value 6 characters long or more')
270
270
271 def test_register_err_username(self):
271 def test_register_err_username(self):
272 response = self.app.post(
272 response = self.app.post(
273 register_url,
273 register_url,
274 {
274 {
275 'username': 'error user',
275 'username': 'error user',
276 'password': 'test12',
276 'password': 'test12',
277 'password_confirmation': 'test12',
277 'password_confirmation': 'test12',
278 'email': 'goodmailm',
278 'email': 'goodmailm',
279 'firstname': 'test',
279 'firstname': 'test',
280 'lastname': 'test'
280 'lastname': 'test'
281 }
281 }
282 )
282 )
283
283
284 response.mustcontain('An email address must contain a single @')
284 response.mustcontain('An email address must contain a single @')
285 response.mustcontain(
285 response.mustcontain(
286 'Username may only contain '
286 'Username may only contain '
287 'alphanumeric characters underscores, '
287 'alphanumeric characters underscores, '
288 'periods or dashes and must begin with '
288 'periods or dashes and must begin with '
289 'alphanumeric character')
289 'alphanumeric character')
290
290
291 def test_register_err_case_sensitive(self):
291 def test_register_err_case_sensitive(self):
292 usr = 'Test_Admin'
292 usr = 'Test_Admin'
293 response = self.app.post(
293 response = self.app.post(
294 register_url,
294 register_url,
295 {
295 {
296 'username': usr,
296 'username': usr,
297 'password': 'test12',
297 'password': 'test12',
298 'password_confirmation': 'test12',
298 'password_confirmation': 'test12',
299 'email': 'goodmailm',
299 'email': 'goodmailm',
300 'firstname': 'test',
300 'firstname': 'test',
301 'lastname': 'test'
301 'lastname': 'test'
302 }
302 }
303 )
303 )
304
304
305 assertr = AssertResponse(response)
305 assertr = AssertResponse(response)
306 msg = validators.ValidUsername()._messages['username_exists']
306 msg = validators.ValidUsername()._messages['username_exists']
307 msg = msg % {'username': usr}
307 msg = msg % {'username': usr}
308 assertr.element_contains('#username+.error-message', msg)
308 assertr.element_contains('#username+.error-message', msg)
309
309
310 def test_register_special_chars(self):
310 def test_register_special_chars(self):
311 response = self.app.post(
311 response = self.app.post(
312 register_url,
312 register_url,
313 {
313 {
314 'username': 'xxxaxn',
314 'username': 'xxxaxn',
315 'password': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
315 'password': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
316 'password_confirmation': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
316 'password_confirmation': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
317 'email': 'goodmailm@test.plx',
317 'email': 'goodmailm@test.plx',
318 'firstname': 'test',
318 'firstname': 'test',
319 'lastname': 'test'
319 'lastname': 'test'
320 }
320 }
321 )
321 )
322
322
323 msg = validators.ValidPassword()._messages['invalid_password']
323 msg = validators.ValidPassword()._messages['invalid_password']
324 response.mustcontain(msg)
324 response.mustcontain(msg)
325
325
326 def test_register_password_mismatch(self):
326 def test_register_password_mismatch(self):
327 response = self.app.post(
327 response = self.app.post(
328 register_url,
328 register_url,
329 {
329 {
330 'username': 'xs',
330 'username': 'xs',
331 'password': '123qwe',
331 'password': '123qwe',
332 'password_confirmation': 'qwe123',
332 'password_confirmation': 'qwe123',
333 'email': 'goodmailm@test.plxa',
333 'email': 'goodmailm@test.plxa',
334 'firstname': 'test',
334 'firstname': 'test',
335 'lastname': 'test'
335 'lastname': 'test'
336 }
336 }
337 )
337 )
338 msg = validators.ValidPasswordsMatch()._messages['password_mismatch']
338 msg = validators.ValidPasswordsMatch()._messages['password_mismatch']
339 response.mustcontain(msg)
339 response.mustcontain(msg)
340
340
341 def test_register_ok(self):
341 def test_register_ok(self):
342 username = 'test_regular4'
342 username = 'test_regular4'
343 password = 'qweqwe'
343 password = 'qweqwe'
344 email = 'marcin@test.com'
344 email = 'marcin@test.com'
345 name = 'testname'
345 name = 'testname'
346 lastname = 'testlastname'
346 lastname = 'testlastname'
347
347
348 response = self.app.post(
348 response = self.app.post(
349 register_url,
349 register_url,
350 {
350 {
351 'username': username,
351 'username': username,
352 'password': password,
352 'password': password,
353 'password_confirmation': password,
353 'password_confirmation': password,
354 'email': email,
354 'email': email,
355 'firstname': name,
355 'firstname': name,
356 'lastname': lastname,
356 'lastname': lastname,
357 'admin': True
357 'admin': True
358 }
358 }
359 ) # This should be overriden
359 ) # This should be overriden
360 assert response.status == '302 Found'
360 assert response.status == '302 Found'
361 assert_session_flash(
361 assert_session_flash(
362 response, 'You have successfully registered with RhodeCode')
362 response, 'You have successfully registered with RhodeCode')
363
363
364 ret = Session().query(User).filter(
364 ret = Session().query(User).filter(
365 User.username == 'test_regular4').one()
365 User.username == 'test_regular4').one()
366 assert ret.username == username
366 assert ret.username == username
367 assert check_password(password, ret.password)
367 assert check_password(password, ret.password)
368 assert ret.email == email
368 assert ret.email == email
369 assert ret.name == name
369 assert ret.name == name
370 assert ret.lastname == lastname
370 assert ret.lastname == lastname
371 assert ret.api_key is not None
371 assert ret.api_key is not None
372 assert not ret.admin
372 assert not ret.admin
373
373
374 def test_forgot_password_wrong_mail(self):
374 def test_forgot_password_wrong_mail(self):
375 bad_email = 'marcin@wrongmail.org'
375 bad_email = 'marcin@wrongmail.org'
376 response = self.app.post(
376 response = self.app.post(
377 pwd_reset_url,
377 pwd_reset_url,
378 {'email': bad_email, }
378 {'email': bad_email, }
379 )
379 )
380
380
381 msg = validators.ValidSystemEmail()._messages['non_existing_email']
381 msg = validators.ValidSystemEmail()._messages['non_existing_email']
382 msg = h.html_escape(msg % {'email': bad_email})
382 msg = h.html_escape(msg % {'email': bad_email})
383 response.mustcontain()
383 response.mustcontain()
384
384
385 def test_forgot_password(self):
385 def test_forgot_password(self):
386 response = self.app.get(pwd_reset_url)
386 response = self.app.get(pwd_reset_url)
387 assert response.status == '200 OK'
387 assert response.status == '200 OK'
388
388
389 username = 'test_password_reset_1'
389 username = 'test_password_reset_1'
390 password = 'qweqwe'
390 password = 'qweqwe'
391 email = 'marcin@python-works.com'
391 email = 'marcin@python-works.com'
392 name = 'passwd'
392 name = 'passwd'
393 lastname = 'reset'
393 lastname = 'reset'
394
394
395 new = User()
395 new = User()
396 new.username = username
396 new.username = username
397 new.password = password
397 new.password = password
398 new.email = email
398 new.email = email
399 new.name = name
399 new.name = name
400 new.lastname = lastname
400 new.lastname = lastname
401 new.api_key = generate_auth_token(username)
401 new.api_key = generate_auth_token(username)
402 Session().add(new)
402 Session().add(new)
403 Session().commit()
403 Session().commit()
404
404
405 response = self.app.post(pwd_reset_url,
405 response = self.app.post(pwd_reset_url,
406 {'email': email, })
406 {'email': email, })
407
407
408 assert_session_flash(
408 assert_session_flash(
409 response, 'Your password reset link was sent')
409 response, 'Your password reset link was sent')
410
410
411 response = response.follow()
411 response = response.follow()
412
412
413 # BAD KEY
413 # BAD KEY
414
414
415 key = "bad"
415 key = "bad"
416 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key)
416 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key)
417 response = self.app.get(confirm_url)
417 response = self.app.get(confirm_url)
418 assert response.status == '302 Found'
418 assert response.status == '302 Found'
419 assert response.location.endswith(pwd_reset_url)
419 assert response.location.endswith(pwd_reset_url)
420
420
421 # GOOD KEY
421 # GOOD KEY
422
422
423 key = User.get_by_username(username).api_key
423 key = User.get_by_username(username).api_key
424 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key)
424 confirm_url = '{}?key={}'.format(pwd_reset_confirm_url, key)
425 response = self.app.get(confirm_url)
425 response = self.app.get(confirm_url)
426 assert response.status == '302 Found'
426 assert response.status == '302 Found'
427 assert response.location.endswith(login_url)
427 assert response.location.endswith(login_url)
428
428
429 assert_session_flash(
429 assert_session_flash(
430 response,
430 response,
431 'Your password reset was successful, '
431 'Your password reset was successful, '
432 'a new password has been sent to your email')
432 'a new password has been sent to your email')
433
433
434 response = response.follow()
434 response = response.follow()
435
435
436 def _get_api_whitelist(self, values=None):
436 def _get_api_whitelist(self, values=None):
437 config = {'api_access_controllers_whitelist': values or []}
437 config = {'api_access_controllers_whitelist': values or []}
438 return config
438 return config
439
439
440 @pytest.mark.parametrize("test_name, auth_token", [
440 @pytest.mark.parametrize("test_name, auth_token", [
441 ('none', None),
441 ('none', None),
442 ('empty_string', ''),
442 ('empty_string', ''),
443 ('fake_number', '123456'),
443 ('fake_number', '123456'),
444 ('proper_auth_token', None)
444 ('proper_auth_token', None)
445 ])
445 ])
446 def test_access_not_whitelisted_page_via_auth_token(self, test_name,
446 def test_access_not_whitelisted_page_via_auth_token(
447 auth_token):
447 self, test_name, auth_token, user_admin):
448
448 whitelist = self._get_api_whitelist([])
449 whitelist = self._get_api_whitelist([])
449 with mock.patch.dict('rhodecode.CONFIG', whitelist):
450 with mock.patch.dict('rhodecode.CONFIG', whitelist):
450 assert [] == whitelist['api_access_controllers_whitelist']
451 assert [] == whitelist['api_access_controllers_whitelist']
451 if test_name == 'proper_auth_token':
452 if test_name == 'proper_auth_token':
452 # use builtin if api_key is None
453 # use builtin if api_key is None
453 auth_token = User.get_first_super_admin().api_key
454 auth_token = user_admin.api_key
454
455
455 with fixture.anon_access(False):
456 with fixture.anon_access(False):
456 self.app.get(url(controller='changeset',
457 self.app.get(url(controller='changeset',
457 action='changeset_raw',
458 action='changeset_raw',
458 repo_name=HG_REPO, revision='tip',
459 repo_name=HG_REPO, revision='tip',
459 api_key=auth_token),
460 api_key=auth_token),
460 status=302)
461 status=302)
461
462
462 @pytest.mark.parametrize("test_name, auth_token, code", [
463 @pytest.mark.parametrize("test_name, auth_token, code", [
463 ('none', None, 302),
464 ('none', None, 302),
464 ('empty_string', '', 302),
465 ('empty_string', '', 302),
465 ('fake_number', '123456', 302),
466 ('fake_number', '123456', 302),
466 ('proper_auth_token', None, 200)
467 ('proper_auth_token', None, 200)
467 ])
468 ])
468 def test_access_whitelisted_page_via_auth_token(self, test_name,
469 def test_access_whitelisted_page_via_auth_token(
469 auth_token, code):
470 self, test_name, auth_token, code, user_admin):
470 whitelist = self._get_api_whitelist(
471
471 ['ChangesetController:changeset_raw'])
472 whitelist_entry = ['ChangesetController:changeset_raw']
473 whitelist = self._get_api_whitelist(whitelist_entry)
474
472 with mock.patch.dict('rhodecode.CONFIG', whitelist):
475 with mock.patch.dict('rhodecode.CONFIG', whitelist):
473 assert ['ChangesetController:changeset_raw'] == \
476 assert whitelist_entry == whitelist['api_access_controllers_whitelist']
474 whitelist['api_access_controllers_whitelist']
477
475 if test_name == 'proper_auth_token':
478 if test_name == 'proper_auth_token':
476 auth_token = User.get_first_super_admin().api_key
479 auth_token = user_admin.api_key
477
480
478 with fixture.anon_access(False):
481 with fixture.anon_access(False):
479 self.app.get(url(controller='changeset',
482 self.app.get(url(controller='changeset',
480 action='changeset_raw',
483 action='changeset_raw',
481 repo_name=HG_REPO, revision='tip',
484 repo_name=HG_REPO, revision='tip',
482 api_key=auth_token),
485 api_key=auth_token),
483 status=code)
486 status=code)
484
487
485 def test_access_page_via_extra_auth_token(self):
488 def test_access_page_via_extra_auth_token(self):
486 whitelist = self._get_api_whitelist(
489 whitelist = self._get_api_whitelist(
487 ['ChangesetController:changeset_raw'])
490 ['ChangesetController:changeset_raw'])
488 with mock.patch.dict('rhodecode.CONFIG', whitelist):
491 with mock.patch.dict('rhodecode.CONFIG', whitelist):
489 assert ['ChangesetController:changeset_raw'] == \
492 assert ['ChangesetController:changeset_raw'] == \
490 whitelist['api_access_controllers_whitelist']
493 whitelist['api_access_controllers_whitelist']
491
494
492 new_auth_token = AuthTokenModel().create(
495 new_auth_token = AuthTokenModel().create(
493 TEST_USER_ADMIN_LOGIN, 'test')
496 TEST_USER_ADMIN_LOGIN, 'test')
494 Session().commit()
497 Session().commit()
495 with fixture.anon_access(False):
498 with fixture.anon_access(False):
496 self.app.get(url(controller='changeset',
499 self.app.get(url(controller='changeset',
497 action='changeset_raw',
500 action='changeset_raw',
498 repo_name=HG_REPO, revision='tip',
501 repo_name=HG_REPO, revision='tip',
499 api_key=new_auth_token.api_key),
502 api_key=new_auth_token.api_key),
500 status=200)
503 status=200)
501
504
502 def test_access_page_via_expired_auth_token(self):
505 def test_access_page_via_expired_auth_token(self):
503 whitelist = self._get_api_whitelist(
506 whitelist = self._get_api_whitelist(
504 ['ChangesetController:changeset_raw'])
507 ['ChangesetController:changeset_raw'])
505 with mock.patch.dict('rhodecode.CONFIG', whitelist):
508 with mock.patch.dict('rhodecode.CONFIG', whitelist):
506 assert ['ChangesetController:changeset_raw'] == \
509 assert ['ChangesetController:changeset_raw'] == \
507 whitelist['api_access_controllers_whitelist']
510 whitelist['api_access_controllers_whitelist']
508
511
509 new_auth_token = AuthTokenModel().create(
512 new_auth_token = AuthTokenModel().create(
510 TEST_USER_ADMIN_LOGIN, 'test')
513 TEST_USER_ADMIN_LOGIN, 'test')
511 Session().commit()
514 Session().commit()
512 # patch the api key and make it expired
515 # patch the api key and make it expired
513 new_auth_token.expires = 0
516 new_auth_token.expires = 0
514 Session().add(new_auth_token)
517 Session().add(new_auth_token)
515 Session().commit()
518 Session().commit()
516 with fixture.anon_access(False):
519 with fixture.anon_access(False):
517 self.app.get(url(controller='changeset',
520 self.app.get(url(controller='changeset',
518 action='changeset_raw',
521 action='changeset_raw',
519 repo_name=HG_REPO, revision='tip',
522 repo_name=HG_REPO, revision='tip',
520 api_key=new_auth_token.api_key),
523 api_key=new_auth_token.api_key),
521 status=302)
524 status=302)
522
525
523
526
524 class TestPasswordReset(TestController):
527 class TestPasswordReset(TestController):
525
528
526 @pytest.mark.parametrize(
529 @pytest.mark.parametrize(
527 'pwd_reset_setting, show_link, show_reset', [
530 'pwd_reset_setting, show_link, show_reset', [
528 ('hg.password_reset.enabled', True, True),
531 ('hg.password_reset.enabled', True, True),
529 ('hg.password_reset.hidden', False, True),
532 ('hg.password_reset.hidden', False, True),
530 ('hg.password_reset.disabled', False, False),
533 ('hg.password_reset.disabled', False, False),
531 ])
534 ])
532 def test_password_reset_settings(
535 def test_password_reset_settings(
533 self, pwd_reset_setting, show_link, show_reset):
536 self, pwd_reset_setting, show_link, show_reset):
534 clear_all_caches()
537 clear_all_caches()
535 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
538 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
536 params = {
539 params = {
537 'csrf_token': self.csrf_token,
540 'csrf_token': self.csrf_token,
538 'anonymous': 'True',
541 'anonymous': 'True',
539 'default_register': 'hg.register.auto_activate',
542 'default_register': 'hg.register.auto_activate',
540 'default_register_message': '',
543 'default_register_message': '',
541 'default_password_reset': pwd_reset_setting,
544 'default_password_reset': pwd_reset_setting,
542 'default_extern_activate': 'hg.extern_activate.auto',
545 'default_extern_activate': 'hg.extern_activate.auto',
543 }
546 }
544 resp = self.app.post(url('admin_permissions_application'), params=params)
547 resp = self.app.post(url('admin_permissions_application'), params=params)
545 self.logout_user()
548 self.logout_user()
546
549
547 login_page = self.app.get(login_url)
550 login_page = self.app.get(login_url)
548 asr_login = AssertResponse(login_page)
551 asr_login = AssertResponse(login_page)
549 index_page = self.app.get(index_url)
552 index_page = self.app.get(index_url)
550 asr_index = AssertResponse(index_page)
553 asr_index = AssertResponse(index_page)
551
554
552 if show_link:
555 if show_link:
553 asr_login.one_element_exists('a.pwd_reset')
556 asr_login.one_element_exists('a.pwd_reset')
554 asr_index.one_element_exists('a.pwd_reset')
557 asr_index.one_element_exists('a.pwd_reset')
555 else:
558 else:
556 asr_login.no_element_exists('a.pwd_reset')
559 asr_login.no_element_exists('a.pwd_reset')
557 asr_index.no_element_exists('a.pwd_reset')
560 asr_index.no_element_exists('a.pwd_reset')
558
561
559 pwdreset_page = self.app.get(pwd_reset_url)
562 pwdreset_page = self.app.get(pwd_reset_url)
560
563
561 asr_reset = AssertResponse(pwdreset_page)
564 asr_reset = AssertResponse(pwdreset_page)
562 if show_reset:
565 if show_reset:
563 assert 'Send password reset email' in pwdreset_page
566 assert 'Send password reset email' in pwdreset_page
564 asr_reset.one_element_exists('#email')
567 asr_reset.one_element_exists('#email')
565 asr_reset.one_element_exists('#send')
568 asr_reset.one_element_exists('#send')
566 else:
569 else:
567 assert 'Password reset is disabled.' in pwdreset_page
570 assert 'Password reset is disabled.' in pwdreset_page
568 asr_reset.no_element_exists('#email')
571 asr_reset.no_element_exists('#email')
569 asr_reset.no_element_exists('#send')
572 asr_reset.no_element_exists('#send')
570
573
571 def test_password_form_disabled(self):
574 def test_password_form_disabled(self):
572 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
575 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
573 params = {
576 params = {
574 'csrf_token': self.csrf_token,
577 'csrf_token': self.csrf_token,
575 'anonymous': 'True',
578 'anonymous': 'True',
576 'default_register': 'hg.register.auto_activate',
579 'default_register': 'hg.register.auto_activate',
577 'default_register_message': '',
580 'default_register_message': '',
578 'default_password_reset': 'hg.password_reset.disabled',
581 'default_password_reset': 'hg.password_reset.disabled',
579 'default_extern_activate': 'hg.extern_activate.auto',
582 'default_extern_activate': 'hg.extern_activate.auto',
580 }
583 }
581 self.app.post(url('admin_permissions_application'), params=params)
584 self.app.post(url('admin_permissions_application'), params=params)
582 self.logout_user()
585 self.logout_user()
583
586
584 pwdreset_page = self.app.post(
587 pwdreset_page = self.app.post(
585 pwd_reset_url,
588 pwd_reset_url,
586 {'email': 'lisa@rhodecode.com',}
589 {'email': 'lisa@rhodecode.com',}
587 )
590 )
588 assert 'Password reset is disabled.' in pwdreset_page
591 assert 'Password reset is disabled.' in pwdreset_page
@@ -1,582 +1,608 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 from hashlib import sha1
22 from hashlib import sha1
23
23
24 import pytest
24 import pytest
25 from mock import patch
25 from mock import patch
26
26
27 from rhodecode.lib import auth
27 from rhodecode.lib import auth
28 from rhodecode.lib.utils2 import md5
28 from rhodecode.lib.utils2 import md5
29 from rhodecode.model.auth_token import AuthTokenModel
29 from rhodecode.model.db import User
30 from rhodecode.model.db import User
30 from rhodecode.model.repo import RepoModel
31 from rhodecode.model.repo import RepoModel
31 from rhodecode.model.user import UserModel
32 from rhodecode.model.user import UserModel
32 from rhodecode.model.user_group import UserGroupModel
33 from rhodecode.model.user_group import UserGroupModel
33
34
34
35
35 def test_perm_origin_dict():
36 def test_perm_origin_dict():
36 pod = auth.PermOriginDict()
37 pod = auth.PermOriginDict()
37 pod['thing'] = 'read', 'default'
38 pod['thing'] = 'read', 'default'
38 assert pod['thing'] == 'read'
39 assert pod['thing'] == 'read'
39
40
40 assert pod.perm_origin_stack == {
41 assert pod.perm_origin_stack == {
41 'thing': [('read', 'default')]}
42 'thing': [('read', 'default')]}
42
43
43 pod['thing'] = 'write', 'admin'
44 pod['thing'] = 'write', 'admin'
44 assert pod['thing'] == 'write'
45 assert pod['thing'] == 'write'
45
46
46 assert pod.perm_origin_stack == {
47 assert pod.perm_origin_stack == {
47 'thing': [('read', 'default'), ('write', 'admin')]}
48 'thing': [('read', 'default'), ('write', 'admin')]}
48
49
49 pod['other'] = 'write', 'default'
50 pod['other'] = 'write', 'default'
50
51
51 assert pod.perm_origin_stack == {
52 assert pod.perm_origin_stack == {
52 'other': [('write', 'default')],
53 'other': [('write', 'default')],
53 'thing': [('read', 'default'), ('write', 'admin')]}
54 'thing': [('read', 'default'), ('write', 'admin')]}
54
55
55 pod['other'] = 'none', 'override'
56 pod['other'] = 'none', 'override'
56
57
57 assert pod.perm_origin_stack == {
58 assert pod.perm_origin_stack == {
58 'other': [('write', 'default'), ('none', 'override')],
59 'other': [('write', 'default'), ('none', 'override')],
59 'thing': [('read', 'default'), ('write', 'admin')]}
60 'thing': [('read', 'default'), ('write', 'admin')]}
60
61
61 with pytest.raises(ValueError):
62 with pytest.raises(ValueError):
62 pod['thing'] = 'read'
63 pod['thing'] = 'read'
63
64
64
65
65 def test_cached_perms_data(user_regular, backend_random):
66 def test_cached_perms_data(user_regular, backend_random):
66 permissions = get_permissions(user_regular)
67 permissions = get_permissions(user_regular)
67 repo_name = backend_random.repo.repo_name
68 repo_name = backend_random.repo.repo_name
68 expected_global_permissions = {
69 expected_global_permissions = {
69 'repository.read', 'group.read', 'usergroup.read'}
70 'repository.read', 'group.read', 'usergroup.read'}
70 assert expected_global_permissions.issubset(permissions['global'])
71 assert expected_global_permissions.issubset(permissions['global'])
71 assert permissions['repositories'][repo_name] == 'repository.read'
72 assert permissions['repositories'][repo_name] == 'repository.read'
72
73
73
74
74 def test_cached_perms_data_with_admin_user(user_regular, backend_random):
75 def test_cached_perms_data_with_admin_user(user_regular, backend_random):
75 permissions = get_permissions(user_regular, user_is_admin=True)
76 permissions = get_permissions(user_regular, user_is_admin=True)
76 repo_name = backend_random.repo.repo_name
77 repo_name = backend_random.repo.repo_name
77 assert 'hg.admin' in permissions['global']
78 assert 'hg.admin' in permissions['global']
78 assert permissions['repositories'][repo_name] == 'repository.admin'
79 assert permissions['repositories'][repo_name] == 'repository.admin'
79
80
80
81
81 def test_cached_perms_data_user_group_global_permissions(user_util):
82 def test_cached_perms_data_user_group_global_permissions(user_util):
82 user, user_group = user_util.create_user_with_group()
83 user, user_group = user_util.create_user_with_group()
83 user_group.inherit_default_permissions = False
84 user_group.inherit_default_permissions = False
84
85
85 granted_permission = 'repository.write'
86 granted_permission = 'repository.write'
86 UserGroupModel().grant_perm(user_group, granted_permission)
87 UserGroupModel().grant_perm(user_group, granted_permission)
87
88
88 permissions = get_permissions(user)
89 permissions = get_permissions(user)
89 assert granted_permission in permissions['global']
90 assert granted_permission in permissions['global']
90
91
91
92
92 @pytest.mark.xfail(reason="Not implemented, see TODO note")
93 @pytest.mark.xfail(reason="Not implemented, see TODO note")
93 def test_cached_perms_data_user_group_global_permissions_(user_util):
94 def test_cached_perms_data_user_group_global_permissions_(user_util):
94 user, user_group = user_util.create_user_with_group()
95 user, user_group = user_util.create_user_with_group()
95
96
96 granted_permission = 'repository.write'
97 granted_permission = 'repository.write'
97 UserGroupModel().grant_perm(user_group, granted_permission)
98 UserGroupModel().grant_perm(user_group, granted_permission)
98
99
99 permissions = get_permissions(user)
100 permissions = get_permissions(user)
100 assert granted_permission in permissions['global']
101 assert granted_permission in permissions['global']
101
102
102
103
103 def test_cached_perms_data_user_global_permissions(user_util):
104 def test_cached_perms_data_user_global_permissions(user_util):
104 user = user_util.create_user()
105 user = user_util.create_user()
105 UserModel().grant_perm(user, 'repository.none')
106 UserModel().grant_perm(user, 'repository.none')
106
107
107 permissions = get_permissions(user, user_inherit_default_permissions=True)
108 permissions = get_permissions(user, user_inherit_default_permissions=True)
108 assert 'repository.read' in permissions['global']
109 assert 'repository.read' in permissions['global']
109
110
110
111
111 def test_cached_perms_data_repository_permissions_on_private_repository(
112 def test_cached_perms_data_repository_permissions_on_private_repository(
112 backend_random, user_util):
113 backend_random, user_util):
113 user, user_group = user_util.create_user_with_group()
114 user, user_group = user_util.create_user_with_group()
114
115
115 repo = backend_random.create_repo()
116 repo = backend_random.create_repo()
116 repo.private = True
117 repo.private = True
117
118
118 granted_permission = 'repository.write'
119 granted_permission = 'repository.write'
119 RepoModel().grant_user_group_permission(
120 RepoModel().grant_user_group_permission(
120 repo, user_group.users_group_name, granted_permission)
121 repo, user_group.users_group_name, granted_permission)
121
122
122 permissions = get_permissions(user)
123 permissions = get_permissions(user)
123 assert permissions['repositories'][repo.repo_name] == granted_permission
124 assert permissions['repositories'][repo.repo_name] == granted_permission
124
125
125
126
126 def test_cached_perms_data_repository_permissions_for_owner(
127 def test_cached_perms_data_repository_permissions_for_owner(
127 backend_random, user_util):
128 backend_random, user_util):
128 user = user_util.create_user()
129 user = user_util.create_user()
129
130
130 repo = backend_random.create_repo()
131 repo = backend_random.create_repo()
131 repo.user_id = user.user_id
132 repo.user_id = user.user_id
132
133
133 permissions = get_permissions(user)
134 permissions = get_permissions(user)
134 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
135 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
135
136
136 # TODO: johbo: Make cleanup in UserUtility smarter, then remove this hack
137 # TODO: johbo: Make cleanup in UserUtility smarter, then remove this hack
137 repo.user_id = User.get_default_user().user_id
138 repo.user_id = User.get_default_user().user_id
138
139
139
140
140 def test_cached_perms_data_repository_permissions_not_inheriting_defaults(
141 def test_cached_perms_data_repository_permissions_not_inheriting_defaults(
141 backend_random, user_util):
142 backend_random, user_util):
142 user = user_util.create_user()
143 user = user_util.create_user()
143 repo = backend_random.create_repo()
144 repo = backend_random.create_repo()
144
145
145 # Don't inherit default object permissions
146 # Don't inherit default object permissions
146 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
147 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
147
148
148 permissions = get_permissions(user)
149 permissions = get_permissions(user)
149 assert permissions['repositories'][repo.repo_name] == 'repository.none'
150 assert permissions['repositories'][repo.repo_name] == 'repository.none'
150
151
151
152
152 def test_cached_perms_data_default_permissions_on_repository_group(user_util):
153 def test_cached_perms_data_default_permissions_on_repository_group(user_util):
153 # Have a repository group with default permissions set
154 # Have a repository group with default permissions set
154 repo_group = user_util.create_repo_group()
155 repo_group = user_util.create_repo_group()
155 default_user = User.get_default_user()
156 default_user = User.get_default_user()
156 user_util.grant_user_permission_to_repo_group(
157 user_util.grant_user_permission_to_repo_group(
157 repo_group, default_user, 'repository.write')
158 repo_group, default_user, 'repository.write')
158 user = user_util.create_user()
159 user = user_util.create_user()
159
160
160 permissions = get_permissions(user)
161 permissions = get_permissions(user)
161 assert permissions['repositories_groups'][repo_group.group_name] == \
162 assert permissions['repositories_groups'][repo_group.group_name] == \
162 'repository.write'
163 'repository.write'
163
164
164
165
165 def test_cached_perms_data_default_permissions_on_repository_group_owner(
166 def test_cached_perms_data_default_permissions_on_repository_group_owner(
166 user_util):
167 user_util):
167 # Have a repository group
168 # Have a repository group
168 repo_group = user_util.create_repo_group()
169 repo_group = user_util.create_repo_group()
169 default_user = User.get_default_user()
170 default_user = User.get_default_user()
170
171
171 # Add a permission for the default user to hit the code path
172 # Add a permission for the default user to hit the code path
172 user_util.grant_user_permission_to_repo_group(
173 user_util.grant_user_permission_to_repo_group(
173 repo_group, default_user, 'repository.write')
174 repo_group, default_user, 'repository.write')
174
175
175 # Have an owner of the group
176 # Have an owner of the group
176 user = user_util.create_user()
177 user = user_util.create_user()
177 repo_group.user_id = user.user_id
178 repo_group.user_id = user.user_id
178
179
179 permissions = get_permissions(user)
180 permissions = get_permissions(user)
180 assert permissions['repositories_groups'][repo_group.group_name] == \
181 assert permissions['repositories_groups'][repo_group.group_name] == \
181 'group.admin'
182 'group.admin'
182
183
183
184
184 def test_cached_perms_data_default_permissions_on_repository_group_no_inherit(
185 def test_cached_perms_data_default_permissions_on_repository_group_no_inherit(
185 user_util):
186 user_util):
186 # Have a repository group
187 # Have a repository group
187 repo_group = user_util.create_repo_group()
188 repo_group = user_util.create_repo_group()
188 default_user = User.get_default_user()
189 default_user = User.get_default_user()
189
190
190 # Add a permission for the default user to hit the code path
191 # Add a permission for the default user to hit the code path
191 user_util.grant_user_permission_to_repo_group(
192 user_util.grant_user_permission_to_repo_group(
192 repo_group, default_user, 'repository.write')
193 repo_group, default_user, 'repository.write')
193
194
194 # Don't inherit default object permissions
195 # Don't inherit default object permissions
195 user = user_util.create_user()
196 user = user_util.create_user()
196 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
197 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
197
198
198 permissions = get_permissions(user)
199 permissions = get_permissions(user)
199 assert permissions['repositories_groups'][repo_group.group_name] == \
200 assert permissions['repositories_groups'][repo_group.group_name] == \
200 'group.none'
201 'group.none'
201
202
202
203
203 def test_cached_perms_data_repository_permissions_from_user_group(
204 def test_cached_perms_data_repository_permissions_from_user_group(
204 user_util, backend_random):
205 user_util, backend_random):
205 user, user_group = user_util.create_user_with_group()
206 user, user_group = user_util.create_user_with_group()
206
207
207 # Needs a second user group to make sure that we select the right
208 # Needs a second user group to make sure that we select the right
208 # permissions.
209 # permissions.
209 user_group2 = user_util.create_user_group()
210 user_group2 = user_util.create_user_group()
210 UserGroupModel().add_user_to_group(user_group2, user)
211 UserGroupModel().add_user_to_group(user_group2, user)
211
212
212 repo = backend_random.create_repo()
213 repo = backend_random.create_repo()
213
214
214 RepoModel().grant_user_group_permission(
215 RepoModel().grant_user_group_permission(
215 repo, user_group.users_group_name, 'repository.read')
216 repo, user_group.users_group_name, 'repository.read')
216 RepoModel().grant_user_group_permission(
217 RepoModel().grant_user_group_permission(
217 repo, user_group2.users_group_name, 'repository.write')
218 repo, user_group2.users_group_name, 'repository.write')
218
219
219 permissions = get_permissions(user)
220 permissions = get_permissions(user)
220 assert permissions['repositories'][repo.repo_name] == 'repository.write'
221 assert permissions['repositories'][repo.repo_name] == 'repository.write'
221
222
222
223
223 def test_cached_perms_data_repository_permissions_from_user_group_owner(
224 def test_cached_perms_data_repository_permissions_from_user_group_owner(
224 user_util, backend_random):
225 user_util, backend_random):
225 user, user_group = user_util.create_user_with_group()
226 user, user_group = user_util.create_user_with_group()
226
227
227 repo = backend_random.create_repo()
228 repo = backend_random.create_repo()
228 repo.user_id = user.user_id
229 repo.user_id = user.user_id
229
230
230 RepoModel().grant_user_group_permission(
231 RepoModel().grant_user_group_permission(
231 repo, user_group.users_group_name, 'repository.write')
232 repo, user_group.users_group_name, 'repository.write')
232
233
233 permissions = get_permissions(user)
234 permissions = get_permissions(user)
234 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
235 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
235
236
236
237
237 def test_cached_perms_data_user_repository_permissions(
238 def test_cached_perms_data_user_repository_permissions(
238 user_util, backend_random):
239 user_util, backend_random):
239 user = user_util.create_user()
240 user = user_util.create_user()
240 repo = backend_random.create_repo()
241 repo = backend_random.create_repo()
241 granted_permission = 'repository.write'
242 granted_permission = 'repository.write'
242 RepoModel().grant_user_permission(repo, user, granted_permission)
243 RepoModel().grant_user_permission(repo, user, granted_permission)
243
244
244 permissions = get_permissions(user)
245 permissions = get_permissions(user)
245 assert permissions['repositories'][repo.repo_name] == granted_permission
246 assert permissions['repositories'][repo.repo_name] == granted_permission
246
247
247
248
248 def test_cached_perms_data_user_repository_permissions_explicit(
249 def test_cached_perms_data_user_repository_permissions_explicit(
249 user_util, backend_random):
250 user_util, backend_random):
250 user = user_util.create_user()
251 user = user_util.create_user()
251 repo = backend_random.create_repo()
252 repo = backend_random.create_repo()
252 granted_permission = 'repository.none'
253 granted_permission = 'repository.none'
253 RepoModel().grant_user_permission(repo, user, granted_permission)
254 RepoModel().grant_user_permission(repo, user, granted_permission)
254
255
255 permissions = get_permissions(user, explicit=True)
256 permissions = get_permissions(user, explicit=True)
256 assert permissions['repositories'][repo.repo_name] == granted_permission
257 assert permissions['repositories'][repo.repo_name] == granted_permission
257
258
258
259
259 def test_cached_perms_data_user_repository_permissions_owner(
260 def test_cached_perms_data_user_repository_permissions_owner(
260 user_util, backend_random):
261 user_util, backend_random):
261 user = user_util.create_user()
262 user = user_util.create_user()
262 repo = backend_random.create_repo()
263 repo = backend_random.create_repo()
263 repo.user_id = user.user_id
264 repo.user_id = user.user_id
264 RepoModel().grant_user_permission(repo, user, 'repository.write')
265 RepoModel().grant_user_permission(repo, user, 'repository.write')
265
266
266 permissions = get_permissions(user)
267 permissions = get_permissions(user)
267 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
268 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
268
269
269
270
270 def test_cached_perms_data_repository_groups_permissions_inherited(
271 def test_cached_perms_data_repository_groups_permissions_inherited(
271 user_util, backend_random):
272 user_util, backend_random):
272 user, user_group = user_util.create_user_with_group()
273 user, user_group = user_util.create_user_with_group()
273
274
274 # Needs a second group to hit the last condition
275 # Needs a second group to hit the last condition
275 user_group2 = user_util.create_user_group()
276 user_group2 = user_util.create_user_group()
276 UserGroupModel().add_user_to_group(user_group2, user)
277 UserGroupModel().add_user_to_group(user_group2, user)
277
278
278 repo_group = user_util.create_repo_group()
279 repo_group = user_util.create_repo_group()
279
280
280 user_util.grant_user_group_permission_to_repo_group(
281 user_util.grant_user_group_permission_to_repo_group(
281 repo_group, user_group, 'group.read')
282 repo_group, user_group, 'group.read')
282 user_util.grant_user_group_permission_to_repo_group(
283 user_util.grant_user_group_permission_to_repo_group(
283 repo_group, user_group2, 'group.write')
284 repo_group, user_group2, 'group.write')
284
285
285 permissions = get_permissions(user)
286 permissions = get_permissions(user)
286 assert permissions['repositories_groups'][repo_group.group_name] == \
287 assert permissions['repositories_groups'][repo_group.group_name] == \
287 'group.write'
288 'group.write'
288
289
289
290
290 def test_cached_perms_data_repository_groups_permissions_inherited_owner(
291 def test_cached_perms_data_repository_groups_permissions_inherited_owner(
291 user_util, backend_random):
292 user_util, backend_random):
292 user, user_group = user_util.create_user_with_group()
293 user, user_group = user_util.create_user_with_group()
293 repo_group = user_util.create_repo_group()
294 repo_group = user_util.create_repo_group()
294 repo_group.user_id = user.user_id
295 repo_group.user_id = user.user_id
295
296
296 granted_permission = 'group.write'
297 granted_permission = 'group.write'
297 user_util.grant_user_group_permission_to_repo_group(
298 user_util.grant_user_group_permission_to_repo_group(
298 repo_group, user_group, granted_permission)
299 repo_group, user_group, granted_permission)
299
300
300 permissions = get_permissions(user)
301 permissions = get_permissions(user)
301 assert permissions['repositories_groups'][repo_group.group_name] == \
302 assert permissions['repositories_groups'][repo_group.group_name] == \
302 'group.admin'
303 'group.admin'
303
304
304
305
305 def test_cached_perms_data_repository_groups_permissions(
306 def test_cached_perms_data_repository_groups_permissions(
306 user_util, backend_random):
307 user_util, backend_random):
307 user = user_util.create_user()
308 user = user_util.create_user()
308
309
309 repo_group = user_util.create_repo_group()
310 repo_group = user_util.create_repo_group()
310
311
311 granted_permission = 'group.write'
312 granted_permission = 'group.write'
312 user_util.grant_user_permission_to_repo_group(
313 user_util.grant_user_permission_to_repo_group(
313 repo_group, user, granted_permission)
314 repo_group, user, granted_permission)
314
315
315 permissions = get_permissions(user)
316 permissions = get_permissions(user)
316 assert permissions['repositories_groups'][repo_group.group_name] == \
317 assert permissions['repositories_groups'][repo_group.group_name] == \
317 'group.write'
318 'group.write'
318
319
319
320
320 def test_cached_perms_data_repository_groups_permissions_explicit(
321 def test_cached_perms_data_repository_groups_permissions_explicit(
321 user_util, backend_random):
322 user_util, backend_random):
322 user = user_util.create_user()
323 user = user_util.create_user()
323
324
324 repo_group = user_util.create_repo_group()
325 repo_group = user_util.create_repo_group()
325
326
326 granted_permission = 'group.none'
327 granted_permission = 'group.none'
327 user_util.grant_user_permission_to_repo_group(
328 user_util.grant_user_permission_to_repo_group(
328 repo_group, user, granted_permission)
329 repo_group, user, granted_permission)
329
330
330 permissions = get_permissions(user, explicit=True)
331 permissions = get_permissions(user, explicit=True)
331 assert permissions['repositories_groups'][repo_group.group_name] == \
332 assert permissions['repositories_groups'][repo_group.group_name] == \
332 'group.none'
333 'group.none'
333
334
334
335
335 def test_cached_perms_data_repository_groups_permissions_owner(
336 def test_cached_perms_data_repository_groups_permissions_owner(
336 user_util, backend_random):
337 user_util, backend_random):
337 user = user_util.create_user()
338 user = user_util.create_user()
338
339
339 repo_group = user_util.create_repo_group()
340 repo_group = user_util.create_repo_group()
340 repo_group.user_id = user.user_id
341 repo_group.user_id = user.user_id
341
342
342 granted_permission = 'group.write'
343 granted_permission = 'group.write'
343 user_util.grant_user_permission_to_repo_group(
344 user_util.grant_user_permission_to_repo_group(
344 repo_group, user, granted_permission)
345 repo_group, user, granted_permission)
345
346
346 permissions = get_permissions(user)
347 permissions = get_permissions(user)
347 assert permissions['repositories_groups'][repo_group.group_name] == \
348 assert permissions['repositories_groups'][repo_group.group_name] == \
348 'group.admin'
349 'group.admin'
349
350
350
351
351 def test_cached_perms_data_user_group_permissions_inherited(
352 def test_cached_perms_data_user_group_permissions_inherited(
352 user_util, backend_random):
353 user_util, backend_random):
353 user, user_group = user_util.create_user_with_group()
354 user, user_group = user_util.create_user_with_group()
354 user_group2 = user_util.create_user_group()
355 user_group2 = user_util.create_user_group()
355 UserGroupModel().add_user_to_group(user_group2, user)
356 UserGroupModel().add_user_to_group(user_group2, user)
356
357
357 target_user_group = user_util.create_user_group()
358 target_user_group = user_util.create_user_group()
358
359
359 user_util.grant_user_group_permission_to_user_group(
360 user_util.grant_user_group_permission_to_user_group(
360 target_user_group, user_group, 'usergroup.read')
361 target_user_group, user_group, 'usergroup.read')
361 user_util.grant_user_group_permission_to_user_group(
362 user_util.grant_user_group_permission_to_user_group(
362 target_user_group, user_group2, 'usergroup.write')
363 target_user_group, user_group2, 'usergroup.write')
363
364
364 permissions = get_permissions(user)
365 permissions = get_permissions(user)
365 assert permissions['user_groups'][target_user_group.users_group_name] == \
366 assert permissions['user_groups'][target_user_group.users_group_name] == \
366 'usergroup.write'
367 'usergroup.write'
367
368
368
369
369 def test_cached_perms_data_user_group_permissions(
370 def test_cached_perms_data_user_group_permissions(
370 user_util, backend_random):
371 user_util, backend_random):
371 user = user_util.create_user()
372 user = user_util.create_user()
372 user_group = user_util.create_user_group()
373 user_group = user_util.create_user_group()
373 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.write')
374 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.write')
374
375
375 permissions = get_permissions(user)
376 permissions = get_permissions(user)
376 assert permissions['user_groups'][user_group.users_group_name] == \
377 assert permissions['user_groups'][user_group.users_group_name] == \
377 'usergroup.write'
378 'usergroup.write'
378
379
379
380
380 def test_cached_perms_data_user_group_permissions_explicit(
381 def test_cached_perms_data_user_group_permissions_explicit(
381 user_util, backend_random):
382 user_util, backend_random):
382 user = user_util.create_user()
383 user = user_util.create_user()
383 user_group = user_util.create_user_group()
384 user_group = user_util.create_user_group()
384 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.none')
385 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.none')
385
386
386 permissions = get_permissions(user, explicit=True)
387 permissions = get_permissions(user, explicit=True)
387 assert permissions['user_groups'][user_group.users_group_name] == \
388 assert permissions['user_groups'][user_group.users_group_name] == \
388 'usergroup.none'
389 'usergroup.none'
389
390
390
391
391 def test_cached_perms_data_user_group_permissions_not_inheriting_defaults(
392 def test_cached_perms_data_user_group_permissions_not_inheriting_defaults(
392 user_util, backend_random):
393 user_util, backend_random):
393 user = user_util.create_user()
394 user = user_util.create_user()
394 user_group = user_util.create_user_group()
395 user_group = user_util.create_user_group()
395
396
396 # Don't inherit default object permissions
397 # Don't inherit default object permissions
397 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
398 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
398
399
399 permissions = get_permissions(user)
400 permissions = get_permissions(user)
400 assert permissions['user_groups'][user_group.users_group_name] == \
401 assert permissions['user_groups'][user_group.users_group_name] == \
401 'usergroup.none'
402 'usergroup.none'
402
403
403
404
404 def test_permission_calculator_admin_permissions(
405 def test_permission_calculator_admin_permissions(
405 user_util, backend_random):
406 user_util, backend_random):
406 user = user_util.create_user()
407 user = user_util.create_user()
407 user_group = user_util.create_user_group()
408 user_group = user_util.create_user_group()
408 repo = backend_random.repo
409 repo = backend_random.repo
409 repo_group = user_util.create_repo_group()
410 repo_group = user_util.create_repo_group()
410
411
411 calculator = auth.PermissionCalculator(
412 calculator = auth.PermissionCalculator(
412 user.user_id, {}, False, False, True, 'higherwin')
413 user.user_id, {}, False, False, True, 'higherwin')
413 permissions = calculator._admin_permissions()
414 permissions = calculator._admin_permissions()
414
415
415 assert permissions['repositories_groups'][repo_group.group_name] == \
416 assert permissions['repositories_groups'][repo_group.group_name] == \
416 'group.admin'
417 'group.admin'
417 assert permissions['user_groups'][user_group.users_group_name] == \
418 assert permissions['user_groups'][user_group.users_group_name] == \
418 'usergroup.admin'
419 'usergroup.admin'
419 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
420 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
420 assert 'hg.admin' in permissions['global']
421 assert 'hg.admin' in permissions['global']
421
422
422
423
423 def test_permission_calculator_repository_permissions_robustness_from_group(
424 def test_permission_calculator_repository_permissions_robustness_from_group(
424 user_util, backend_random):
425 user_util, backend_random):
425 user, user_group = user_util.create_user_with_group()
426 user, user_group = user_util.create_user_with_group()
426
427
427 RepoModel().grant_user_group_permission(
428 RepoModel().grant_user_group_permission(
428 backend_random.repo, user_group.users_group_name, 'repository.write')
429 backend_random.repo, user_group.users_group_name, 'repository.write')
429
430
430 calculator = auth.PermissionCalculator(
431 calculator = auth.PermissionCalculator(
431 user.user_id, {}, False, False, False, 'higherwin')
432 user.user_id, {}, False, False, False, 'higherwin')
432 calculator._calculate_repository_permissions()
433 calculator._calculate_repository_permissions()
433
434
434
435
435 def test_permission_calculator_repository_permissions_robustness_from_user(
436 def test_permission_calculator_repository_permissions_robustness_from_user(
436 user_util, backend_random):
437 user_util, backend_random):
437 user = user_util.create_user()
438 user = user_util.create_user()
438
439
439 RepoModel().grant_user_permission(
440 RepoModel().grant_user_permission(
440 backend_random.repo, user, 'repository.write')
441 backend_random.repo, user, 'repository.write')
441
442
442 calculator = auth.PermissionCalculator(
443 calculator = auth.PermissionCalculator(
443 user.user_id, {}, False, False, False, 'higherwin')
444 user.user_id, {}, False, False, False, 'higherwin')
444 calculator._calculate_repository_permissions()
445 calculator._calculate_repository_permissions()
445
446
446
447
447 def test_permission_calculator_repo_group_permissions_robustness_from_group(
448 def test_permission_calculator_repo_group_permissions_robustness_from_group(
448 user_util, backend_random):
449 user_util, backend_random):
449 user, user_group = user_util.create_user_with_group()
450 user, user_group = user_util.create_user_with_group()
450 repo_group = user_util.create_repo_group()
451 repo_group = user_util.create_repo_group()
451
452
452 user_util.grant_user_group_permission_to_repo_group(
453 user_util.grant_user_group_permission_to_repo_group(
453 repo_group, user_group, 'group.write')
454 repo_group, user_group, 'group.write')
454
455
455 calculator = auth.PermissionCalculator(
456 calculator = auth.PermissionCalculator(
456 user.user_id, {}, False, False, False, 'higherwin')
457 user.user_id, {}, False, False, False, 'higherwin')
457 calculator._calculate_repository_group_permissions()
458 calculator._calculate_repository_group_permissions()
458
459
459
460
460 def test_permission_calculator_repo_group_permissions_robustness_from_user(
461 def test_permission_calculator_repo_group_permissions_robustness_from_user(
461 user_util, backend_random):
462 user_util, backend_random):
462 user = user_util.create_user()
463 user = user_util.create_user()
463 repo_group = user_util.create_repo_group()
464 repo_group = user_util.create_repo_group()
464
465
465 user_util.grant_user_permission_to_repo_group(
466 user_util.grant_user_permission_to_repo_group(
466 repo_group, user, 'group.write')
467 repo_group, user, 'group.write')
467
468
468 calculator = auth.PermissionCalculator(
469 calculator = auth.PermissionCalculator(
469 user.user_id, {}, False, False, False, 'higherwin')
470 user.user_id, {}, False, False, False, 'higherwin')
470 calculator._calculate_repository_group_permissions()
471 calculator._calculate_repository_group_permissions()
471
472
472
473
473 def test_permission_calculator_user_group_permissions_robustness_from_group(
474 def test_permission_calculator_user_group_permissions_robustness_from_group(
474 user_util, backend_random):
475 user_util, backend_random):
475 user, user_group = user_util.create_user_with_group()
476 user, user_group = user_util.create_user_with_group()
476 target_user_group = user_util.create_user_group()
477 target_user_group = user_util.create_user_group()
477
478
478 user_util.grant_user_group_permission_to_user_group(
479 user_util.grant_user_group_permission_to_user_group(
479 target_user_group, user_group, 'usergroup.write')
480 target_user_group, user_group, 'usergroup.write')
480
481
481 calculator = auth.PermissionCalculator(
482 calculator = auth.PermissionCalculator(
482 user.user_id, {}, False, False, False, 'higherwin')
483 user.user_id, {}, False, False, False, 'higherwin')
483 calculator._calculate_user_group_permissions()
484 calculator._calculate_user_group_permissions()
484
485
485
486
486 def test_permission_calculator_user_group_permissions_robustness_from_user(
487 def test_permission_calculator_user_group_permissions_robustness_from_user(
487 user_util, backend_random):
488 user_util, backend_random):
488 user = user_util.create_user()
489 user = user_util.create_user()
489 target_user_group = user_util.create_user_group()
490 target_user_group = user_util.create_user_group()
490
491
491 user_util.grant_user_permission_to_user_group(
492 user_util.grant_user_permission_to_user_group(
492 target_user_group, user, 'usergroup.write')
493 target_user_group, user, 'usergroup.write')
493
494
494 calculator = auth.PermissionCalculator(
495 calculator = auth.PermissionCalculator(
495 user.user_id, {}, False, False, False, 'higherwin')
496 user.user_id, {}, False, False, False, 'higherwin')
496 calculator._calculate_user_group_permissions()
497 calculator._calculate_user_group_permissions()
497
498
498
499
499 @pytest.mark.parametrize("algo, new_permission, old_permission, expected", [
500 @pytest.mark.parametrize("algo, new_permission, old_permission, expected", [
500 ('higherwin', 'repository.none', 'repository.none', 'repository.none'),
501 ('higherwin', 'repository.none', 'repository.none', 'repository.none'),
501 ('higherwin', 'repository.read', 'repository.none', 'repository.read'),
502 ('higherwin', 'repository.read', 'repository.none', 'repository.read'),
502 ('lowerwin', 'repository.write', 'repository.write', 'repository.write'),
503 ('lowerwin', 'repository.write', 'repository.write', 'repository.write'),
503 ('lowerwin', 'repository.read', 'repository.write', 'repository.read'),
504 ('lowerwin', 'repository.read', 'repository.write', 'repository.read'),
504 ])
505 ])
505 def test_permission_calculator_choose_permission(
506 def test_permission_calculator_choose_permission(
506 user_regular, algo, new_permission, old_permission, expected):
507 user_regular, algo, new_permission, old_permission, expected):
507 calculator = auth.PermissionCalculator(
508 calculator = auth.PermissionCalculator(
508 user_regular.user_id, {}, False, False, False, algo)
509 user_regular.user_id, {}, False, False, False, algo)
509 result = calculator._choose_permission(new_permission, old_permission)
510 result = calculator._choose_permission(new_permission, old_permission)
510 assert result == expected
511 assert result == expected
511
512
512
513
513 def test_permission_calculator_choose_permission_raises_on_wrong_algo(
514 def test_permission_calculator_choose_permission_raises_on_wrong_algo(
514 user_regular):
515 user_regular):
515 calculator = auth.PermissionCalculator(
516 calculator = auth.PermissionCalculator(
516 user_regular.user_id, {}, False, False, False, 'invalid')
517 user_regular.user_id, {}, False, False, False, 'invalid')
517 result = calculator._choose_permission(
518 result = calculator._choose_permission(
518 'repository.read', 'repository.read')
519 'repository.read', 'repository.read')
519 # TODO: johbo: This documents the existing behavior. Think of an
520 # TODO: johbo: This documents the existing behavior. Think of an
520 # improvement.
521 # improvement.
521 assert result is None
522 assert result is None
522
523
523
524
524 def test_auth_user_get_cookie_store_for_normal_user(user_util):
525 def test_auth_user_get_cookie_store_for_normal_user(user_util):
525 user = user_util.create_user()
526 user = user_util.create_user()
526 auth_user = auth.AuthUser(user_id=user.user_id)
527 auth_user = auth.AuthUser(user_id=user.user_id)
527 expected_data = {
528 expected_data = {
528 'username': user.username,
529 'username': user.username,
529 'user_id': user.user_id,
530 'user_id': user.user_id,
530 'password': md5(user.password),
531 'password': md5(user.password),
531 'is_authenticated': False
532 'is_authenticated': False
532 }
533 }
533 assert auth_user.get_cookie_store() == expected_data
534 assert auth_user.get_cookie_store() == expected_data
534
535
535
536
536 def test_auth_user_get_cookie_store_for_default_user():
537 def test_auth_user_get_cookie_store_for_default_user():
537 default_user = User.get_default_user()
538 default_user = User.get_default_user()
538 auth_user = auth.AuthUser()
539 auth_user = auth.AuthUser()
539 expected_data = {
540 expected_data = {
540 'username': User.DEFAULT_USER,
541 'username': User.DEFAULT_USER,
541 'user_id': default_user.user_id,
542 'user_id': default_user.user_id,
542 'password': md5(default_user.password),
543 'password': md5(default_user.password),
543 'is_authenticated': True
544 'is_authenticated': True
544 }
545 }
545 assert auth_user.get_cookie_store() == expected_data
546 assert auth_user.get_cookie_store() == expected_data
546
547
547
548
548 def get_permissions(user, **kwargs):
549 def get_permissions(user, **kwargs):
549 """
550 """
550 Utility filling in useful defaults into the call to `_cached_perms_data`.
551 Utility filling in useful defaults into the call to `_cached_perms_data`.
551
552
552 Fill in `**kwargs` if specific values are needed for a test.
553 Fill in `**kwargs` if specific values are needed for a test.
553 """
554 """
554 call_args = {
555 call_args = {
555 'user_id': user.user_id,
556 'user_id': user.user_id,
556 'scope': {},
557 'scope': {},
557 'user_is_admin': False,
558 'user_is_admin': False,
558 'user_inherit_default_permissions': False,
559 'user_inherit_default_permissions': False,
559 'explicit': False,
560 'explicit': False,
560 'algo': 'higherwin',
561 'algo': 'higherwin',
561 }
562 }
562 call_args.update(kwargs)
563 call_args.update(kwargs)
563 permissions = auth._cached_perms_data(**call_args)
564 permissions = auth._cached_perms_data(**call_args)
564 return permissions
565 return permissions
565
566
566
567
567 class TestGenerateAuthToken(object):
568 class TestGenerateAuthToken(object):
568 def test_salt_is_used_when_specified(self):
569 def test_salt_is_used_when_specified(self):
569 salt = 'abcde'
570 salt = 'abcde'
570 user_name = 'test_user'
571 user_name = 'test_user'
571 result = auth.generate_auth_token(user_name, salt)
572 result = auth.generate_auth_token(user_name, salt)
572 expected_result = sha1(user_name + salt).hexdigest()
573 expected_result = sha1(user_name + salt).hexdigest()
573 assert result == expected_result
574 assert result == expected_result
574
575
575 def test_salt_is_geneated_when_not_specified(self):
576 def test_salt_is_geneated_when_not_specified(self):
576 user_name = 'test_user'
577 user_name = 'test_user'
577 random_salt = os.urandom(16)
578 random_salt = os.urandom(16)
578 with patch.object(auth, 'os') as os_mock:
579 with patch.object(auth, 'os') as os_mock:
579 os_mock.urandom.return_value = random_salt
580 os_mock.urandom.return_value = random_salt
580 result = auth.generate_auth_token(user_name)
581 result = auth.generate_auth_token(user_name)
581 expected_result = sha1(user_name + random_salt).hexdigest()
582 expected_result = sha1(user_name + random_salt).hexdigest()
582 assert result == expected_result
583 assert result == expected_result
584
585
586 @pytest.mark.parametrize("test_token, test_roles, auth_result, expected_tokens", [
587 ('', None, False,
588 []),
589 ('wrongtoken', None, False,
590 []),
591 ('abracadabra_vcs', [AuthTokenModel.cls.ROLE_API], False,
592 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1)]),
593 ('abracadabra_api', [AuthTokenModel.cls.ROLE_API], True,
594 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1)]),
595 ('abracadabra_api', [AuthTokenModel.cls.ROLE_API], True,
596 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1),
597 ('abracadabra_http', AuthTokenModel.cls.ROLE_HTTP, -1)]),
598 ])
599 def test_auth_by_token(test_token, test_roles, auth_result, expected_tokens,
600 user_util):
601 user = user_util.create_user()
602 user_id = user.user_id
603 for token, role, expires in expected_tokens:
604 new_token = AuthTokenModel().create(user_id, 'test-token', expires, role)
605 new_token.api_key = token # inject known name for testing...
606
607 assert auth_result == user.authenticate_by_token(
608 test_token, roles=test_roles, include_builtin_token=True)
General Comments 0
You need to be logged in to leave comments. Login now