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