##// 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

The requested changes are too big and content was truncated. Show full diff

@@ -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 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -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