##// END OF EJS Templates
caches: fixed auth plugin usage of cached settings....
super-admin -
r4836:0d20aaca default
parent child Browse files
Show More
@@ -1,822 +1,818 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 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 modules
23 23 """
24 24 import socket
25 25 import string
26 26 import colander
27 27 import copy
28 28 import logging
29 29 import time
30 30 import traceback
31 31 import warnings
32 32 import functools
33 33
34 34 from pyramid.threadlocal import get_current_registry
35 35
36 36 from rhodecode.authentication.interface import IAuthnPluginRegistry
37 37 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
38 38 from rhodecode.lib import rc_cache
39 39 from rhodecode.lib.statsd_client import StatsdClient
40 40 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
41 41 from rhodecode.lib.utils2 import safe_int, safe_str
42 42 from rhodecode.lib.exceptions import (LdapConnectionError, LdapUsernameError, LdapPasswordError)
43 43 from rhodecode.model.db import User
44 44 from rhodecode.model.meta import Session
45 45 from rhodecode.model.settings import SettingsModel
46 46 from rhodecode.model.user import UserModel
47 47 from rhodecode.model.user_group import UserGroupModel
48 48
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52 # auth types that authenticate() function can receive
53 53 VCS_TYPE = 'vcs'
54 54 HTTP_TYPE = 'http'
55 55
56 56 external_auth_session_key = 'rhodecode.external_auth'
57 57
58 58
59 59 class hybrid_property(object):
60 60 """
61 61 a property decorator that works both for instance and class
62 62 """
63 63 def __init__(self, fget, fset=None, fdel=None, expr=None):
64 64 self.fget = fget
65 65 self.fset = fset
66 66 self.fdel = fdel
67 67 self.expr = expr or fget
68 68 functools.update_wrapper(self, fget)
69 69
70 70 def __get__(self, instance, owner):
71 71 if instance is None:
72 72 return self.expr(owner)
73 73 else:
74 74 return self.fget(instance)
75 75
76 76 def __set__(self, instance, value):
77 77 self.fset(instance, value)
78 78
79 79 def __delete__(self, instance):
80 80 self.fdel(instance)
81 81
82 82
83 83 class LazyFormencode(object):
84 84 def __init__(self, formencode_obj, *args, **kwargs):
85 85 self.formencode_obj = formencode_obj
86 86 self.args = args
87 87 self.kwargs = kwargs
88 88
89 89 def __call__(self, *args, **kwargs):
90 90 from inspect import isfunction
91 91 formencode_obj = self.formencode_obj
92 92 if isfunction(formencode_obj):
93 93 # case we wrap validators into functions
94 94 formencode_obj = self.formencode_obj(*args, **kwargs)
95 95 return formencode_obj(*self.args, **self.kwargs)
96 96
97 97
98 98 class RhodeCodeAuthPluginBase(object):
99 99 # UID is used to register plugin to the registry
100 100 uid = None
101 101
102 102 # cache the authentication request for N amount of seconds. Some kind
103 103 # of authentication methods are very heavy and it's very efficient to cache
104 104 # the result of a call. If it's set to None (default) cache is off
105 105 AUTH_CACHE_TTL = None
106 106 AUTH_CACHE = {}
107 107
108 108 auth_func_attrs = {
109 109 "username": "unique username",
110 110 "firstname": "first name",
111 111 "lastname": "last name",
112 112 "email": "email address",
113 113 "groups": '["list", "of", "groups"]',
114 114 "user_group_sync":
115 115 'True|False defines if returned user groups should be synced',
116 116 "extern_name": "name in external source of record",
117 117 "extern_type": "type of external source of record",
118 118 "admin": 'True|False defines if user should be RhodeCode super admin',
119 119 "active":
120 120 'True|False defines active state of user internally for RhodeCode',
121 121 "active_from_extern":
122 122 "True|False|None, active state from the external auth, "
123 123 "None means use definition from RhodeCode extern_type active value"
124 124
125 125 }
126 126 # set on authenticate() method and via set_auth_type func.
127 127 auth_type = None
128 128
129 129 # set on authenticate() method and via set_calling_scope_repo, this is a
130 130 # calling scope repository when doing authentication most likely on VCS
131 131 # operations
132 132 acl_repo_name = None
133 133
134 134 # List of setting names to store encrypted. Plugins may override this list
135 135 # to store settings encrypted.
136 136 _settings_encrypted = []
137 137
138 138 # Mapping of python to DB settings model types. Plugins may override or
139 139 # extend this mapping.
140 140 _settings_type_map = {
141 141 colander.String: 'unicode',
142 142 colander.Integer: 'int',
143 143 colander.Boolean: 'bool',
144 144 colander.List: 'list',
145 145 }
146 146
147 147 # list of keys in settings that are unsafe to be logged, should be passwords
148 148 # or other crucial credentials
149 149 _settings_unsafe_keys = []
150 150
151 151 def __init__(self, plugin_id):
152 152 self._plugin_id = plugin_id
153 self._settings = {}
154 153
155 154 def __str__(self):
156 155 return self.get_id()
157 156
158 157 def _get_setting_full_name(self, name):
159 158 """
160 159 Return the full setting name used for storing values in the database.
161 160 """
162 161 # TODO: johbo: Using the name here is problematic. It would be good to
163 162 # introduce either new models in the database to hold Plugin and
164 163 # PluginSetting or to use the plugin id here.
165 164 return 'auth_{}_{}'.format(self.name, name)
166 165
167 166 def _get_setting_type(self, name):
168 167 """
169 168 Return the type of a setting. This type is defined by the SettingsModel
170 169 and determines how the setting is stored in DB. Optionally the suffix
171 170 `.encrypted` is appended to instruct SettingsModel to store it
172 171 encrypted.
173 172 """
174 173 schema_node = self.get_settings_schema().get(name)
175 174 db_type = self._settings_type_map.get(
176 175 type(schema_node.typ), 'unicode')
177 176 if name in self._settings_encrypted:
178 177 db_type = '{}.encrypted'.format(db_type)
179 178 return db_type
180 179
181 180 @classmethod
182 181 def docs(cls):
183 182 """
184 183 Defines documentation url which helps with plugin setup
185 184 """
186 185 return ''
187 186
188 187 @classmethod
189 188 def icon(cls):
190 189 """
191 190 Defines ICON in SVG format for authentication method
192 191 """
193 192 return ''
194 193
195 194 def is_enabled(self):
196 195 """
197 196 Returns true if this plugin is enabled. An enabled plugin can be
198 197 configured in the admin interface but it is not consulted during
199 198 authentication.
200 199 """
201 200 auth_plugins = SettingsModel().get_auth_plugins()
202 201 return self.get_id() in auth_plugins
203 202
204 203 def is_active(self, plugin_cached_settings=None):
205 204 """
206 205 Returns true if the plugin is activated. An activated plugin is
207 206 consulted during authentication, assumed it is also enabled.
208 207 """
209 208 return self.get_setting_by_name(
210 209 'enabled', plugin_cached_settings=plugin_cached_settings)
211 210
212 211 def get_id(self):
213 212 """
214 213 Returns the plugin id.
215 214 """
216 215 return self._plugin_id
217 216
218 217 def get_display_name(self, load_from_settings=False):
219 218 """
220 219 Returns a translation string for displaying purposes.
221 220 if load_from_settings is set, plugin settings can override the display name
222 221 """
223 222 raise NotImplementedError('Not implemented in base class')
224 223
225 224 def get_settings_schema(self):
226 225 """
227 226 Returns a colander schema, representing the plugin settings.
228 227 """
229 228 return AuthnPluginSettingsSchemaBase()
230 229
231 230 def _propagate_settings(self, raw_settings):
232 231 settings = {}
233 232 for node in self.get_settings_schema():
234 233 settings[node.name] = self.get_setting_by_name(
235 234 node.name, plugin_cached_settings=raw_settings)
236 235 return settings
237 236
238 237 def get_settings(self, use_cache=True):
239 238 """
240 239 Returns the plugin settings as dictionary.
241 240 """
242 if self._settings != {} and use_cache:
243 return self._settings
244 241
245 raw_settings = SettingsModel().get_all_settings()
242 raw_settings = SettingsModel().get_all_settings(cache=use_cache)
246 243 settings = self._propagate_settings(raw_settings)
247 244
248 self._settings = settings
249 return self._settings
245 return settings
250 246
251 247 def get_setting_by_name(self, name, default=None, plugin_cached_settings=None):
252 248 """
253 249 Returns a plugin setting by name.
254 250 """
255 251 full_name = 'rhodecode_{}'.format(self._get_setting_full_name(name))
256 252 if plugin_cached_settings:
257 253 plugin_settings = plugin_cached_settings
258 254 else:
259 255 plugin_settings = SettingsModel().get_all_settings()
260 256
261 257 if full_name in plugin_settings:
262 258 return plugin_settings[full_name]
263 259 else:
264 260 return default
265 261
266 262 def create_or_update_setting(self, name, value):
267 263 """
268 264 Create or update a setting for this plugin in the persistent storage.
269 265 """
270 266 full_name = self._get_setting_full_name(name)
271 267 type_ = self._get_setting_type(name)
272 268 db_setting = SettingsModel().create_or_update_setting(
273 269 full_name, value, type_)
274 270 return db_setting.app_settings_value
275 271
276 272 def log_safe_settings(self, settings):
277 273 """
278 274 returns a log safe representation of settings, without any secrets
279 275 """
280 276 settings_copy = copy.deepcopy(settings)
281 277 for k in self._settings_unsafe_keys:
282 278 if k in settings_copy:
283 279 del settings_copy[k]
284 280 return settings_copy
285 281
286 282 @hybrid_property
287 283 def name(self):
288 284 """
289 285 Returns the name of this authentication plugin.
290 286
291 287 :returns: string
292 288 """
293 289 raise NotImplementedError("Not implemented in base class")
294 290
295 291 def get_url_slug(self):
296 292 """
297 293 Returns a slug which should be used when constructing URLs which refer
298 294 to this plugin. By default it returns the plugin name. If the name is
299 295 not suitable for using it in an URL the plugin should override this
300 296 method.
301 297 """
302 298 return self.name
303 299
304 300 @property
305 301 def is_headers_auth(self):
306 302 """
307 303 Returns True if this authentication plugin uses HTTP headers as
308 304 authentication method.
309 305 """
310 306 return False
311 307
312 308 @hybrid_property
313 309 def is_container_auth(self):
314 310 """
315 311 Deprecated method that indicates if this authentication plugin uses
316 312 HTTP headers as authentication method.
317 313 """
318 314 warnings.warn(
319 315 'Use is_headers_auth instead.', category=DeprecationWarning)
320 316 return self.is_headers_auth
321 317
322 318 @hybrid_property
323 319 def allows_creating_users(self):
324 320 """
325 321 Defines if Plugin allows users to be created on-the-fly when
326 322 authentication is called. Controls how external plugins should behave
327 323 in terms if they are allowed to create new users, or not. Base plugins
328 324 should not be allowed to, but External ones should be !
329 325
330 326 :return: bool
331 327 """
332 328 return False
333 329
334 330 def set_auth_type(self, auth_type):
335 331 self.auth_type = auth_type
336 332
337 333 def set_calling_scope_repo(self, acl_repo_name):
338 334 self.acl_repo_name = acl_repo_name
339 335
340 336 def allows_authentication_from(
341 337 self, user, allows_non_existing_user=True,
342 338 allowed_auth_plugins=None, allowed_auth_sources=None):
343 339 """
344 340 Checks if this authentication module should accept a request for
345 341 the current user.
346 342
347 343 :param user: user object fetched using plugin's get_user() method.
348 344 :param allows_non_existing_user: if True, don't allow the
349 345 user to be empty, meaning not existing in our database
350 346 :param allowed_auth_plugins: if provided, users extern_type will be
351 347 checked against a list of provided extern types, which are plugin
352 348 auth_names in the end
353 349 :param allowed_auth_sources: authentication type allowed,
354 350 `http` or `vcs` default is both.
355 351 defines if plugin will accept only http authentication vcs
356 352 authentication(git/hg) or both
357 353 :returns: boolean
358 354 """
359 355 if not user and not allows_non_existing_user:
360 356 log.debug('User is empty but plugin does not allow empty users,'
361 357 'not allowed to authenticate')
362 358 return False
363 359
364 360 expected_auth_plugins = allowed_auth_plugins or [self.name]
365 361 if user and (user.extern_type and
366 362 user.extern_type not in expected_auth_plugins):
367 363 log.debug(
368 364 'User `%s` is bound to `%s` auth type. Plugin allows only '
369 365 '%s, skipping', user, user.extern_type, expected_auth_plugins)
370 366
371 367 return False
372 368
373 369 # by default accept both
374 370 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
375 371 if self.auth_type not in expected_auth_from:
376 372 log.debug('Current auth source is %s but plugin only allows %s',
377 373 self.auth_type, expected_auth_from)
378 374 return False
379 375
380 376 return True
381 377
382 378 def get_user(self, username=None, **kwargs):
383 379 """
384 380 Helper method for user fetching in plugins, by default it's using
385 381 simple fetch by username, but this method can be custimized in plugins
386 382 eg. headers auth plugin to fetch user by environ params
387 383
388 384 :param username: username if given to fetch from database
389 385 :param kwargs: extra arguments needed for user fetching.
390 386 """
391 387 user = None
392 388 log.debug(
393 389 'Trying to fetch user `%s` from RhodeCode database', username)
394 390 if username:
395 391 user = User.get_by_username(username)
396 392 if not user:
397 393 log.debug('User not found, fallback to fetch user in '
398 394 'case insensitive mode')
399 395 user = User.get_by_username(username, case_insensitive=True)
400 396 else:
401 397 log.debug('provided username:`%s` is empty skipping...', username)
402 398 if not user:
403 399 log.debug('User `%s` not found in database', username)
404 400 else:
405 401 log.debug('Got DB user:%s', user)
406 402 return user
407 403
408 404 def user_activation_state(self):
409 405 """
410 406 Defines user activation state when creating new users
411 407
412 408 :returns: boolean
413 409 """
414 410 raise NotImplementedError("Not implemented in base class")
415 411
416 412 def auth(self, userobj, username, passwd, settings, **kwargs):
417 413 """
418 414 Given a user object (which may be null), username, a plaintext
419 415 password, and a settings object (containing all the keys needed as
420 416 listed in settings()), authenticate this user's login attempt.
421 417
422 418 Return None on failure. On success, return a dictionary of the form:
423 419
424 420 see: RhodeCodeAuthPluginBase.auth_func_attrs
425 421 This is later validated for correctness
426 422 """
427 423 raise NotImplementedError("not implemented in base class")
428 424
429 425 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
430 426 """
431 427 Wrapper to call self.auth() that validates call on it
432 428
433 429 :param userobj: userobj
434 430 :param username: username
435 431 :param passwd: plaintext password
436 432 :param settings: plugin settings
437 433 """
438 434 auth = self.auth(userobj, username, passwd, settings, **kwargs)
439 435 if auth:
440 436 auth['_plugin'] = self.name
441 437 auth['_ttl_cache'] = self.get_ttl_cache(settings)
442 438 # check if hash should be migrated ?
443 439 new_hash = auth.get('_hash_migrate')
444 440 if new_hash:
445 441 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
446 442 if 'user_group_sync' not in auth:
447 443 auth['user_group_sync'] = False
448 444 return self._validate_auth_return(auth)
449 445 return auth
450 446
451 447 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
452 448 new_hash_cypher = _RhodeCodeCryptoBCrypt()
453 449 # extra checks, so make sure new hash is correct.
454 450 password_encoded = safe_str(password)
455 451 if new_hash and new_hash_cypher.hash_check(
456 452 password_encoded, new_hash):
457 453 cur_user = User.get_by_username(username)
458 454 cur_user.password = new_hash
459 455 Session().add(cur_user)
460 456 Session().flush()
461 457 log.info('Migrated user %s hash to bcrypt', cur_user)
462 458
463 459 def _validate_auth_return(self, ret):
464 460 if not isinstance(ret, dict):
465 461 raise Exception('returned value from auth must be a dict')
466 462 for k in self.auth_func_attrs:
467 463 if k not in ret:
468 464 raise Exception('Missing %s attribute from returned data' % k)
469 465 return ret
470 466
471 467 def get_ttl_cache(self, settings=None):
472 468 plugin_settings = settings or self.get_settings()
473 469 # we set default to 30, we make a compromise here,
474 470 # performance > security, mostly due to LDAP/SVN, majority
475 471 # of users pick cache_ttl to be enabled
476 472 from rhodecode.authentication import plugin_default_auth_ttl
477 473 cache_ttl = plugin_default_auth_ttl
478 474
479 475 if isinstance(self.AUTH_CACHE_TTL, (int, long)):
480 476 # plugin cache set inside is more important than the settings value
481 477 cache_ttl = self.AUTH_CACHE_TTL
482 478 elif plugin_settings.get('cache_ttl'):
483 479 cache_ttl = safe_int(plugin_settings.get('cache_ttl'), 0)
484 480
485 481 plugin_cache_active = bool(cache_ttl and cache_ttl > 0)
486 482 return plugin_cache_active, cache_ttl
487 483
488 484
489 485 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
490 486
491 487 @hybrid_property
492 488 def allows_creating_users(self):
493 489 return True
494 490
495 491 def use_fake_password(self):
496 492 """
497 493 Return a boolean that indicates whether or not we should set the user's
498 494 password to a random value when it is authenticated by this plugin.
499 495 If your plugin provides authentication, then you will generally
500 496 want this.
501 497
502 498 :returns: boolean
503 499 """
504 500 raise NotImplementedError("Not implemented in base class")
505 501
506 502 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
507 503 # at this point _authenticate calls plugin's `auth()` function
508 504 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
509 505 userobj, username, passwd, settings, **kwargs)
510 506
511 507 if auth:
512 508 # maybe plugin will clean the username ?
513 509 # we should use the return value
514 510 username = auth['username']
515 511
516 512 # if external source tells us that user is not active, we should
517 513 # skip rest of the process. This can prevent from creating users in
518 514 # RhodeCode when using external authentication, but if it's
519 515 # inactive user we shouldn't create that user anyway
520 516 if auth['active_from_extern'] is False:
521 517 log.warning(
522 518 "User %s authenticated against %s, but is inactive",
523 519 username, self.__module__)
524 520 return None
525 521
526 522 cur_user = User.get_by_username(username, case_insensitive=True)
527 523 is_user_existing = cur_user is not None
528 524
529 525 if is_user_existing:
530 526 log.debug('Syncing user `%s` from '
531 527 '`%s` plugin', username, self.name)
532 528 else:
533 529 log.debug('Creating non existing user `%s` from '
534 530 '`%s` plugin', username, self.name)
535 531
536 532 if self.allows_creating_users:
537 533 log.debug('Plugin `%s` allows to '
538 534 'create new users', self.name)
539 535 else:
540 536 log.debug('Plugin `%s` does not allow to '
541 537 'create new users', self.name)
542 538
543 539 user_parameters = {
544 540 'username': username,
545 541 'email': auth["email"],
546 542 'firstname': auth["firstname"],
547 543 'lastname': auth["lastname"],
548 544 'active': auth["active"],
549 545 'admin': auth["admin"],
550 546 'extern_name': auth["extern_name"],
551 547 'extern_type': self.name,
552 548 'plugin': self,
553 549 'allow_to_create_user': self.allows_creating_users,
554 550 }
555 551
556 552 if not is_user_existing:
557 553 if self.use_fake_password():
558 554 # Randomize the PW because we don't need it, but don't want
559 555 # them blank either
560 556 passwd = PasswordGenerator().gen_password(length=16)
561 557 user_parameters['password'] = passwd
562 558 else:
563 559 # Since the password is required by create_or_update method of
564 560 # UserModel, we need to set it explicitly.
565 561 # The create_or_update method is smart and recognises the
566 562 # password hashes as well.
567 563 user_parameters['password'] = cur_user.password
568 564
569 565 # we either create or update users, we also pass the flag
570 566 # that controls if this method can actually do that.
571 567 # raises NotAllowedToCreateUserError if it cannot, and we try to.
572 568 user = UserModel().create_or_update(**user_parameters)
573 569 Session().flush()
574 570 # enforce user is just in given groups, all of them has to be ones
575 571 # created from plugins. We store this info in _group_data JSON
576 572 # field
577 573
578 574 if auth['user_group_sync']:
579 575 try:
580 576 groups = auth['groups'] or []
581 577 log.debug(
582 578 'Performing user_group sync based on set `%s` '
583 579 'returned by `%s` plugin', groups, self.name)
584 580 UserGroupModel().enforce_groups(user, groups, self.name)
585 581 except Exception:
586 582 # for any reason group syncing fails, we should
587 583 # proceed with login
588 584 log.error(traceback.format_exc())
589 585
590 586 Session().commit()
591 587 return auth
592 588
593 589
594 590 class AuthLdapBase(object):
595 591
596 592 @classmethod
597 593 def _build_servers(cls, ldap_server_type, ldap_server, port, use_resolver=True):
598 594
599 595 def host_resolver(host, port, full_resolve=True):
600 596 """
601 597 Main work for this function is to prevent ldap connection issues,
602 598 and detect them early using a "greenified" sockets
603 599 """
604 600 host = host.strip()
605 601 if not full_resolve:
606 602 return '{}:{}'.format(host, port)
607 603
608 604 log.debug('LDAP: Resolving IP for LDAP host `%s`', host)
609 605 try:
610 606 ip = socket.gethostbyname(host)
611 607 log.debug('LDAP: Got LDAP host `%s` ip %s', host, ip)
612 608 except Exception:
613 609 raise LdapConnectionError('Failed to resolve host: `{}`'.format(host))
614 610
615 611 log.debug('LDAP: Checking if IP %s is accessible', ip)
616 612 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
617 613 try:
618 614 s.connect((ip, int(port)))
619 615 s.shutdown(socket.SHUT_RD)
620 616 log.debug('LDAP: connection to %s successful', ip)
621 617 except Exception:
622 618 raise LdapConnectionError(
623 619 'Failed to connect to host: `{}:{}`'.format(host, port))
624 620
625 621 return '{}:{}'.format(host, port)
626 622
627 623 if len(ldap_server) == 1:
628 624 # in case of single server use resolver to detect potential
629 625 # connection issues
630 626 full_resolve = True
631 627 else:
632 628 full_resolve = False
633 629
634 630 return ', '.join(
635 631 ["{}://{}".format(
636 632 ldap_server_type,
637 633 host_resolver(host, port, full_resolve=use_resolver and full_resolve))
638 634 for host in ldap_server])
639 635
640 636 @classmethod
641 637 def _get_server_list(cls, servers):
642 638 return map(string.strip, servers.split(','))
643 639
644 640 @classmethod
645 641 def get_uid(cls, username, server_addresses):
646 642 uid = username
647 643 for server_addr in server_addresses:
648 644 uid = chop_at(username, "@%s" % server_addr)
649 645 return uid
650 646
651 647 @classmethod
652 648 def validate_username(cls, username):
653 649 if "," in username:
654 650 raise LdapUsernameError(
655 651 "invalid character `,` in username: `{}`".format(username))
656 652
657 653 @classmethod
658 654 def validate_password(cls, username, password):
659 655 if not password:
660 656 msg = "Authenticating user %s with blank password not allowed"
661 657 log.warning(msg, username)
662 658 raise LdapPasswordError(msg)
663 659
664 660
665 661 def loadplugin(plugin_id):
666 662 """
667 663 Loads and returns an instantiated authentication plugin.
668 664 Returns the RhodeCodeAuthPluginBase subclass on success,
669 665 or None on failure.
670 666 """
671 667 # TODO: Disusing pyramids thread locals to retrieve the registry.
672 668 authn_registry = get_authn_registry()
673 669 plugin = authn_registry.get_plugin(plugin_id)
674 670 if plugin is None:
675 671 log.error('Authentication plugin not found: "%s"', plugin_id)
676 672 return plugin
677 673
678 674
679 675 def get_authn_registry(registry=None):
680 676 registry = registry or get_current_registry()
681 677 authn_registry = registry.queryUtility(IAuthnPluginRegistry)
682 678 return authn_registry
683 679
684 680
685 681 def authenticate(username, password, environ=None, auth_type=None,
686 682 skip_missing=False, registry=None, acl_repo_name=None):
687 683 """
688 684 Authentication function used for access control,
689 685 It tries to authenticate based on enabled authentication modules.
690 686
691 687 :param username: username can be empty for headers auth
692 688 :param password: password can be empty for headers auth
693 689 :param environ: environ headers passed for headers auth
694 690 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
695 691 :param skip_missing: ignores plugins that are in db but not in environment
696 692 :returns: None if auth failed, plugin_user dict if auth is correct
697 693 """
698 694 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
699 695 raise ValueError('auth type must be on of http, vcs got "%s" instead'
700 696 % auth_type)
701 697 headers_only = environ and not (username and password)
702 698
703 699 authn_registry = get_authn_registry(registry)
704 700
705 701 plugins_to_check = authn_registry.get_plugins_for_authentication()
706 702 log.debug('Starting ordered authentication chain using %s plugins',
707 703 [x.name for x in plugins_to_check])
708 704 for plugin in plugins_to_check:
709 705 plugin.set_auth_type(auth_type)
710 706 plugin.set_calling_scope_repo(acl_repo_name)
711 707
712 708 if headers_only and not plugin.is_headers_auth:
713 709 log.debug('Auth type is for headers only and plugin `%s` is not '
714 710 'headers plugin, skipping...', plugin.get_id())
715 711 continue
716 712
717 713 log.debug('Trying authentication using ** %s **', plugin.get_id())
718 714
719 715 # load plugin settings from RhodeCode database
720 716 plugin_settings = plugin.get_settings()
721 717 plugin_sanitized_settings = plugin.log_safe_settings(plugin_settings)
722 718 log.debug('Plugin `%s` settings:%s', plugin.get_id(), plugin_sanitized_settings)
723 719
724 720 # use plugin's method of user extraction.
725 721 user = plugin.get_user(username, environ=environ,
726 722 settings=plugin_settings)
727 723 display_user = user.username if user else username
728 724 log.debug(
729 725 'Plugin %s extracted user is `%s`', plugin.get_id(), display_user)
730 726
731 727 if not plugin.allows_authentication_from(user):
732 728 log.debug('Plugin %s does not accept user `%s` for authentication',
733 729 plugin.get_id(), display_user)
734 730 continue
735 731 else:
736 732 log.debug('Plugin %s accepted user `%s` for authentication',
737 733 plugin.get_id(), display_user)
738 734
739 735 log.info('Authenticating user `%s` using %s plugin',
740 736 display_user, plugin.get_id())
741 737
742 738 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(plugin_settings)
743 739
744 740 log.debug('AUTH_CACHE_TTL for plugin `%s` active: %s (TTL: %s)',
745 741 plugin.get_id(), plugin_cache_active, cache_ttl)
746 742
747 743 user_id = user.user_id if user else 'no-user'
748 744 # don't cache for empty users
749 745 plugin_cache_active = plugin_cache_active and user_id
750 746 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
751 747 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
752 748
753 749 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
754 750 expiration_time=cache_ttl,
755 751 condition=plugin_cache_active)
756 752 def compute_auth(
757 753 cache_name, plugin_name, username, password):
758 754
759 755 # _authenticate is a wrapper for .auth() method of plugin.
760 756 # it checks if .auth() sends proper data.
761 757 # For RhodeCodeExternalAuthPlugin it also maps users to
762 758 # Database and maps the attributes returned from .auth()
763 759 # to RhodeCode database. If this function returns data
764 760 # then auth is correct.
765 761 log.debug('Running plugin `%s` _authenticate method '
766 762 'using username and password', plugin.get_id())
767 763 return plugin._authenticate(
768 764 user, username, password, plugin_settings,
769 765 environ=environ or {})
770 766
771 767 start = time.time()
772 768 # for environ based auth, password can be empty, but then the validation is
773 769 # on the server that fills in the env data needed for authentication
774 770 plugin_user = compute_auth('auth', plugin.name, username, (password or ''))
775 771
776 772 auth_time = time.time() - start
777 773 log.debug('Authentication for plugin `%s` completed in %.4fs, '
778 774 'expiration time of fetched cache %.1fs.',
779 775 plugin.get_id(), auth_time, cache_ttl,
780 776 extra={"plugin": plugin.get_id(), "time": auth_time})
781 777
782 778 log.debug('PLUGIN USER DATA: %s', plugin_user)
783 779
784 780 statsd = StatsdClient.statsd
785 781
786 782 if plugin_user:
787 783 log.debug('Plugin returned proper authentication data')
788 784 if statsd:
789 785 elapsed_time_ms = round(1000.0 * auth_time) # use ms only
790 786 statsd.incr('rhodecode_login_success_total')
791 787 statsd.timing("rhodecode_login_timing.histogram", elapsed_time_ms,
792 788 tags=["plugin:{}".format(plugin.get_id())],
793 789 use_decimals=False
794 790 )
795 791 return plugin_user
796 792
797 793 # we failed to Auth because .auth() method didn't return proper user
798 794 log.debug("User `%s` failed to authenticate against %s",
799 795 display_user, plugin.get_id())
800 796 if statsd:
801 797 statsd.incr('rhodecode_login_fail_total')
802 798
803 799 # case when we failed to authenticate against all defined plugins
804 800 return None
805 801
806 802
807 803 def chop_at(s, sub, inclusive=False):
808 804 """Truncate string ``s`` at the first occurrence of ``sub``.
809 805
810 806 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
811 807
812 808 >>> chop_at("plutocratic brats", "rat")
813 809 'plutoc'
814 810 >>> chop_at("plutocratic brats", "rat", True)
815 811 'plutocrat'
816 812 """
817 813 pos = s.find(sub)
818 814 if pos == -1:
819 815 return s
820 816 if inclusive:
821 817 return s[:pos+len(sub)]
822 818 return s[:pos]
@@ -1,107 +1,107 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2020 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 logging
22 22
23 23 from pyramid.exceptions import ConfigurationError
24 24 from zope.interface import implementer
25 25
26 26 from rhodecode.authentication.interface import IAuthnPluginRegistry
27 27 from rhodecode.lib.utils2 import safe_str
28 28 from rhodecode.model.settings import SettingsModel
29 29
30 30 log = logging.getLogger(__name__)
31 31
32 32
33 33 @implementer(IAuthnPluginRegistry)
34 34 class AuthenticationPluginRegistry(object):
35 35
36 36 # INI settings key to set a fallback authentication plugin.
37 37 fallback_plugin_key = 'rhodecode.auth_plugin_fallback'
38 38
39 39 def __init__(self, settings):
40 40 self._plugins = {}
41 41 self._plugins_for_auth = None
42 42 self._fallback_plugin = settings.get(self.fallback_plugin_key, None)
43 43
44 44 def add_authn_plugin(self, config, plugin):
45 45 plugin_id = plugin.get_id()
46 46 if plugin_id in self._plugins.keys():
47 47 raise ConfigurationError(
48 48 'Cannot register authentication plugin twice: "%s"', plugin_id)
49 49 else:
50 50 log.debug('Register authentication plugin: "%s"', plugin_id)
51 51 self._plugins[plugin_id] = plugin
52 52
53 53 def get_plugins(self):
54 54 def sort_key(plugin):
55 55 return str.lower(safe_str(plugin.get_display_name()))
56 56
57 57 return sorted(self._plugins.values(), key=sort_key)
58 58
59 59 def get_plugin(self, plugin_id):
60 60 return self._plugins.get(plugin_id, None)
61 61
62 62 def get_plugin_by_uid(self, plugin_uid):
63 63 for plugin in self._plugins.values():
64 64 if plugin.uid == plugin_uid:
65 65 return plugin
66 66
67 67 def invalidate_plugins_for_auth(self):
68 68 log.debug('Invalidating cached plugins for authentication')
69 69 self._plugins_for_auth = None
70 70
71 71 def get_plugins_for_authentication(self):
72 72 """
73 73 Returns a list of plugins which should be consulted when authenticating
74 74 a user. It only returns plugins which are enabled and active.
75 75 Additionally it includes the fallback plugin from the INI file, if
76 76 `rhodecode.auth_plugin_fallback` is set to a plugin ID.
77 77 """
78 78 if self._plugins_for_auth is not None:
79 79 return self._plugins_for_auth
80 80
81 81 plugins = []
82 82
83 83 # Add all enabled and active plugins to the list. We iterate over the
84 84 # auth_plugins setting from DB because it also represents the ordering.
85 85 enabled_plugins = SettingsModel().get_auth_plugins()
86 raw_settings = SettingsModel().get_all_settings()
86 raw_settings = SettingsModel().get_all_settings(cache=True)
87 87 for plugin_id in enabled_plugins:
88 88 plugin = self.get_plugin(plugin_id)
89 89 if plugin is not None and plugin.is_active(
90 90 plugin_cached_settings=raw_settings):
91 91
92 92 # inject settings into plugin, we can re-use the DB fetched settings here
93 93 plugin._settings = plugin._propagate_settings(raw_settings)
94 94 plugins.append(plugin)
95 95
96 96 # Add the fallback plugin from ini file.
97 97 if self._fallback_plugin:
98 98 log.warn(
99 99 'Using fallback authentication plugin from INI file: "%s"',
100 100 self._fallback_plugin)
101 101 plugin = self.get_plugin(self._fallback_plugin)
102 102 if plugin is not None and plugin not in plugins:
103 103 plugin._settings = plugin._propagate_settings(raw_settings)
104 104 plugins.append(plugin)
105 105
106 106 self._plugins_for_auth = plugins
107 107 return self._plugins_for_auth
@@ -1,417 +1,419 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 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 Helpers for fixture generation
23 23 """
24 24
25 25 import os
26 26 import time
27 27 import tempfile
28 28 import shutil
29 29
30 30 import configobj
31 31
32 32 from rhodecode.model.settings import SettingsModel
33 33 from rhodecode.tests import *
34 34 from rhodecode.model.db import Repository, User, RepoGroup, UserGroup, Gist, UserEmailMap
35 35 from rhodecode.model.meta import Session
36 36 from rhodecode.model.repo import RepoModel
37 37 from rhodecode.model.user import UserModel
38 38 from rhodecode.model.repo_group import RepoGroupModel
39 39 from rhodecode.model.user_group import UserGroupModel
40 40 from rhodecode.model.gist import GistModel
41 41 from rhodecode.model.auth_token import AuthTokenModel
42 42 from rhodecode.authentication.plugins.auth_rhodecode import \
43 43 RhodeCodeAuthPlugin
44 44
45 45 dn = os.path.dirname
46 46 FIXTURES = os.path.join(dn(dn(os.path.abspath(__file__))), 'tests', 'fixtures')
47 47
48 48
49 49 def error_function(*args, **kwargs):
50 50 raise Exception('Total Crash !')
51 51
52 52
53 53 class TestINI(object):
54 54 """
55 55 Allows to create a new test.ini file as a copy of existing one with edited
56 56 data. Example usage::
57 57
58 58 with TestINI('test.ini', [{'section':{'key':val'}]) as new_test_ini_path:
59 59 print('paster server %s' % new_test_ini)
60 60 """
61 61
62 62 def __init__(self, ini_file_path, ini_params, new_file_prefix='DEFAULT',
63 63 destroy=True, dir=None):
64 64 self.ini_file_path = ini_file_path
65 65 self.ini_params = ini_params
66 66 self.new_path = None
67 67 self.new_path_prefix = new_file_prefix
68 68 self._destroy = destroy
69 69 self._dir = dir
70 70
71 71 def __enter__(self):
72 72 return self.create()
73 73
74 74 def __exit__(self, exc_type, exc_val, exc_tb):
75 75 self.destroy()
76 76
77 77 def create(self):
78 78 config = configobj.ConfigObj(
79 79 self.ini_file_path, file_error=True, write_empty_values=True)
80 80
81 81 for data in self.ini_params:
82 82 section, ini_params = data.items()[0]
83 83 for key, val in ini_params.items():
84 84 config[section][key] = val
85 85 with tempfile.NamedTemporaryFile(
86 86 prefix=self.new_path_prefix, suffix='.ini', dir=self._dir,
87 87 delete=False) as new_ini_file:
88 88 config.write(new_ini_file)
89 89 self.new_path = new_ini_file.name
90 90
91 91 return self.new_path
92 92
93 93 def destroy(self):
94 94 if self._destroy:
95 95 os.remove(self.new_path)
96 96
97 97
98 98 class Fixture(object):
99 99
100 100 def anon_access(self, status):
101 101 """
102 102 Context process for disabling anonymous access. use like:
103 103 fixture = Fixture()
104 104 with fixture.anon_access(False):
105 105 #tests
106 106
107 107 after this block anon access will be set to `not status`
108 108 """
109 109
110 110 class context(object):
111 111 def __enter__(self):
112 112 anon = User.get_default_user()
113 113 anon.active = status
114 114 Session().add(anon)
115 115 Session().commit()
116 116 time.sleep(1.5) # must sleep for cache (1s to expire)
117 117
118 118 def __exit__(self, exc_type, exc_val, exc_tb):
119 119 anon = User.get_default_user()
120 120 anon.active = not status
121 121 Session().add(anon)
122 122 Session().commit()
123 123
124 124 return context()
125 125
126 126 def auth_restriction(self, registry, auth_restriction):
127 127 """
128 128 Context process for changing the builtin rhodecode plugin auth restrictions.
129 129 Use like:
130 130 fixture = Fixture()
131 131 with fixture.auth_restriction('super_admin'):
132 132 #tests
133 133
134 134 after this block auth restriction will be taken off
135 135 """
136 136
137 137 class context(object):
138 def _get_pluing(self):
138 def _get_plugin(self):
139 139 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(RhodeCodeAuthPlugin.uid)
140 140 plugin = RhodeCodeAuthPlugin(plugin_id)
141 141 return plugin
142 142
143 143 def __enter__(self):
144 plugin = self._get_pluing()
144
145 plugin = self._get_plugin()
145 146 plugin.create_or_update_setting('auth_restriction', auth_restriction)
146 147 Session().commit()
147 148 SettingsModel().invalidate_settings_cache()
148 149
149 150 def __exit__(self, exc_type, exc_val, exc_tb):
150 plugin = self._get_pluing()
151
152 plugin = self._get_plugin()
151 153 plugin.create_or_update_setting(
152 154 'auth_restriction', RhodeCodeAuthPlugin.AUTH_RESTRICTION_NONE)
153 155 Session().commit()
154 156 SettingsModel().invalidate_settings_cache()
155 157
156 158 return context()
157 159
158 160 def scope_restriction(self, registry, scope_restriction):
159 161 """
160 162 Context process for changing the builtin rhodecode plugin scope restrictions.
161 163 Use like:
162 164 fixture = Fixture()
163 165 with fixture.scope_restriction('scope_http'):
164 166 #tests
165 167
166 168 after this block scope restriction will be taken off
167 169 """
168 170
169 171 class context(object):
170 def _get_pluing(self):
172 def _get_plugin(self):
171 173 plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(RhodeCodeAuthPlugin.uid)
172 174 plugin = RhodeCodeAuthPlugin(plugin_id)
173 175 return plugin
174 176
175 177 def __enter__(self):
176 plugin = self._get_pluing()
178 plugin = self._get_plugin()
177 179 plugin.create_or_update_setting('scope_restriction', scope_restriction)
178 180 Session().commit()
179 181 SettingsModel().invalidate_settings_cache()
180 182
181 183 def __exit__(self, exc_type, exc_val, exc_tb):
182 plugin = self._get_pluing()
184 plugin = self._get_plugin()
183 185 plugin.create_or_update_setting(
184 186 'scope_restriction', RhodeCodeAuthPlugin.AUTH_RESTRICTION_SCOPE_ALL)
185 187 Session().commit()
186 188 SettingsModel().invalidate_settings_cache()
187 189
188 190 return context()
189 191
190 192 def _get_repo_create_params(self, **custom):
191 193 defs = {
192 194 'repo_name': None,
193 195 'repo_type': 'hg',
194 196 'clone_uri': '',
195 197 'push_uri': '',
196 198 'repo_group': '-1',
197 199 'repo_description': 'DESC',
198 200 'repo_private': False,
199 201 'repo_landing_rev': 'rev:tip',
200 202 'repo_copy_permissions': False,
201 203 'repo_state': Repository.STATE_CREATED,
202 204 }
203 205 defs.update(custom)
204 206 if 'repo_name_full' not in custom:
205 207 defs.update({'repo_name_full': defs['repo_name']})
206 208
207 209 # fix the repo name if passed as repo_name_full
208 210 if defs['repo_name']:
209 211 defs['repo_name'] = defs['repo_name'].split('/')[-1]
210 212
211 213 return defs
212 214
213 215 def _get_group_create_params(self, **custom):
214 216 defs = {
215 217 'group_name': None,
216 218 'group_description': 'DESC',
217 219 'perm_updates': [],
218 220 'perm_additions': [],
219 221 'perm_deletions': [],
220 222 'group_parent_id': -1,
221 223 'enable_locking': False,
222 224 'recursive': False,
223 225 }
224 226 defs.update(custom)
225 227
226 228 return defs
227 229
228 230 def _get_user_create_params(self, name, **custom):
229 231 defs = {
230 232 'username': name,
231 233 'password': 'qweqwe',
232 234 'email': '%s+test@rhodecode.org' % name,
233 235 'firstname': 'TestUser',
234 236 'lastname': 'Test',
235 237 'description': 'test description',
236 238 'active': True,
237 239 'admin': False,
238 240 'extern_type': 'rhodecode',
239 241 'extern_name': None,
240 242 }
241 243 defs.update(custom)
242 244
243 245 return defs
244 246
245 247 def _get_user_group_create_params(self, name, **custom):
246 248 defs = {
247 249 'users_group_name': name,
248 250 'user_group_description': 'DESC',
249 251 'users_group_active': True,
250 252 'user_group_data': {},
251 253 }
252 254 defs.update(custom)
253 255
254 256 return defs
255 257
256 258 def create_repo(self, name, **kwargs):
257 259 repo_group = kwargs.get('repo_group')
258 260 if isinstance(repo_group, RepoGroup):
259 261 kwargs['repo_group'] = repo_group.group_id
260 262 name = name.split(Repository.NAME_SEP)[-1]
261 263 name = Repository.NAME_SEP.join((repo_group.group_name, name))
262 264
263 265 if 'skip_if_exists' in kwargs:
264 266 del kwargs['skip_if_exists']
265 267 r = Repository.get_by_repo_name(name)
266 268 if r:
267 269 return r
268 270
269 271 form_data = self._get_repo_create_params(repo_name=name, **kwargs)
270 272 cur_user = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
271 273 RepoModel().create(form_data, cur_user)
272 274 Session().commit()
273 275 repo = Repository.get_by_repo_name(name)
274 276 assert repo
275 277 return repo
276 278
277 279 def create_fork(self, repo_to_fork, fork_name, **kwargs):
278 280 repo_to_fork = Repository.get_by_repo_name(repo_to_fork)
279 281
280 282 form_data = self._get_repo_create_params(repo_name=fork_name,
281 283 fork_parent_id=repo_to_fork.repo_id,
282 284 repo_type=repo_to_fork.repo_type,
283 285 **kwargs)
284 286 #TODO: fix it !!
285 287 form_data['description'] = form_data['repo_description']
286 288 form_data['private'] = form_data['repo_private']
287 289 form_data['landing_rev'] = form_data['repo_landing_rev']
288 290
289 291 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
290 292 RepoModel().create_fork(form_data, cur_user=owner)
291 293 Session().commit()
292 294 r = Repository.get_by_repo_name(fork_name)
293 295 assert r
294 296 return r
295 297
296 298 def destroy_repo(self, repo_name, **kwargs):
297 299 RepoModel().delete(repo_name, pull_requests='delete', **kwargs)
298 300 Session().commit()
299 301
300 302 def destroy_repo_on_filesystem(self, repo_name):
301 303 rm_path = os.path.join(RepoModel().repos_path, repo_name)
302 304 if os.path.isdir(rm_path):
303 305 shutil.rmtree(rm_path)
304 306
305 307 def create_repo_group(self, name, **kwargs):
306 308 if 'skip_if_exists' in kwargs:
307 309 del kwargs['skip_if_exists']
308 310 gr = RepoGroup.get_by_group_name(group_name=name)
309 311 if gr:
310 312 return gr
311 313 form_data = self._get_group_create_params(group_name=name, **kwargs)
312 314 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
313 315 gr = RepoGroupModel().create(
314 316 group_name=form_data['group_name'],
315 317 group_description=form_data['group_name'],
316 318 owner=owner)
317 319 Session().commit()
318 320 gr = RepoGroup.get_by_group_name(gr.group_name)
319 321 return gr
320 322
321 323 def destroy_repo_group(self, repogroupid):
322 324 RepoGroupModel().delete(repogroupid)
323 325 Session().commit()
324 326
325 327 def create_user(self, name, **kwargs):
326 328 if 'skip_if_exists' in kwargs:
327 329 del kwargs['skip_if_exists']
328 330 user = User.get_by_username(name)
329 331 if user:
330 332 return user
331 333 form_data = self._get_user_create_params(name, **kwargs)
332 334 user = UserModel().create(form_data)
333 335
334 336 # create token for user
335 337 AuthTokenModel().create(
336 338 user=user, description=u'TEST_USER_TOKEN')
337 339
338 340 Session().commit()
339 341 user = User.get_by_username(user.username)
340 342 return user
341 343
342 344 def destroy_user(self, userid):
343 345 UserModel().delete(userid)
344 346 Session().commit()
345 347
346 348 def create_additional_user_email(self, user, email):
347 349 uem = UserEmailMap()
348 350 uem.user = user
349 351 uem.email = email
350 352 Session().add(uem)
351 353 return uem
352 354
353 355 def destroy_users(self, userid_iter):
354 356 for user_id in userid_iter:
355 357 if User.get_by_username(user_id):
356 358 UserModel().delete(user_id)
357 359 Session().commit()
358 360
359 361 def create_user_group(self, name, **kwargs):
360 362 if 'skip_if_exists' in kwargs:
361 363 del kwargs['skip_if_exists']
362 364 gr = UserGroup.get_by_group_name(group_name=name)
363 365 if gr:
364 366 return gr
365 367 # map active flag to the real attribute. For API consistency of fixtures
366 368 if 'active' in kwargs:
367 369 kwargs['users_group_active'] = kwargs['active']
368 370 del kwargs['active']
369 371 form_data = self._get_user_group_create_params(name, **kwargs)
370 372 owner = kwargs.get('cur_user', TEST_USER_ADMIN_LOGIN)
371 373 user_group = UserGroupModel().create(
372 374 name=form_data['users_group_name'],
373 375 description=form_data['user_group_description'],
374 376 owner=owner, active=form_data['users_group_active'],
375 377 group_data=form_data['user_group_data'])
376 378 Session().commit()
377 379 user_group = UserGroup.get_by_group_name(user_group.users_group_name)
378 380 return user_group
379 381
380 382 def destroy_user_group(self, usergroupid):
381 383 UserGroupModel().delete(user_group=usergroupid, force=True)
382 384 Session().commit()
383 385
384 386 def create_gist(self, **kwargs):
385 387 form_data = {
386 388 'description': 'new-gist',
387 389 'owner': TEST_USER_ADMIN_LOGIN,
388 390 'gist_type': GistModel.cls.GIST_PUBLIC,
389 391 'lifetime': -1,
390 392 'acl_level': Gist.ACL_LEVEL_PUBLIC,
391 393 'gist_mapping': {'filename1.txt': {'content': 'hello world'},}
392 394 }
393 395 form_data.update(kwargs)
394 396 gist = GistModel().create(
395 397 description=form_data['description'], owner=form_data['owner'],
396 398 gist_mapping=form_data['gist_mapping'], gist_type=form_data['gist_type'],
397 399 lifetime=form_data['lifetime'], gist_acl_level=form_data['acl_level']
398 400 )
399 401 Session().commit()
400 402 return gist
401 403
402 404 def destroy_gists(self, gistid=None):
403 405 for g in GistModel.cls.get_all():
404 406 if gistid:
405 407 if gistid == g.gist_access_id:
406 408 GistModel().delete(g)
407 409 else:
408 410 GistModel().delete(g)
409 411 Session().commit()
410 412
411 413 def load_resource(self, resource_name, strip=False):
412 414 with open(os.path.join(FIXTURES, resource_name)) as f:
413 415 source = f.read()
414 416 if strip:
415 417 source = source.strip()
416 418
417 419 return source
@@ -1,658 +1,717 b''
1 1
2 2 ; #########################################
3 3 ; RHODECODE COMMUNITY EDITION CONFIGURATION
4 4 ; #########################################
5 5
6 6 [DEFAULT]
7 7 ; Debug flag sets all loggers to debug, and enables request tracking
8 8 debug = true
9 9
10 10 ; ########################################################################
11 11 ; EMAIL CONFIGURATION
12 12 ; These settings will be used by the RhodeCode mailing system
13 13 ; ########################################################################
14 14
15 15 ; prefix all emails subjects with given prefix, helps filtering out emails
16 16 #email_prefix = [RhodeCode]
17 17
18 18 ; email FROM address all mails will be sent
19 19 #app_email_from = rhodecode-noreply@localhost
20 20
21 21 #smtp_server = mail.server.com
22 22 #smtp_username =
23 23 #smtp_password =
24 24 #smtp_port =
25 25 #smtp_use_tls = false
26 26 #smtp_use_ssl = true
27 27
28 28 [server:main]
29 29 ; COMMON HOST/IP CONFIG
30 30 host = 0.0.0.0
31 31 port = 5000
32 32
33 33
34 34 ; ###########################
35 35 ; GUNICORN APPLICATION SERVER
36 36 ; ###########################
37 37
38 38 ; run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
39 39
40 40 ; Module to use, this setting shouldn't be changed
41 41 use = egg:gunicorn#main
42 42
43 ## Sets the number of process workers. You must set `instance_id = *`
44 ## when this option is set to more than one worker, recommended
45 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
46 ## The `instance_id = *` must be set in the [app:main] section below
43 ; Sets the number of process workers. More workers means more concurrent connections
44 ; RhodeCode can handle at the same time. Each additional worker also it increases
45 ; memory usage as each has it's own set of caches.
46 ; Recommended value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers, but no more
47 ; than 8-10 unless for really big deployments .e.g 700-1000 users.
48 ; `instance_id = *` must be set in the [app:main] section below (which is the default)
49 ; when using more than 1 worker.
47 50 #workers = 2
48 ## number of threads for each of the worker, must be set to 1 for gevent
49 ## generally recommened to be at 1
50 #threads = 1
51 ## process name
51
52 ; Gunicorn access log level
53 #loglevel = info
54
55 ; Process name visible in process list
52 56 #proc_name = rhodecode
53 ## type of worker class, one of sync, gevent
54 ## recommended for bigger setup is using of of other than sync one
55 #worker_class = sync
56 ## The maximum number of simultaneous clients. Valid only for Gevent
57
58 ; Type of worker class, one of `sync`, `gevent`
59 ; Recommended type is `gevent`
60 #worker_class = gevent
61
62 ; The maximum number of simultaneous clients per worker. Valid only for gevent
57 63 #worker_connections = 10
58 ## max number of requests that worker will handle before being gracefully
59 ## restarted, could prevent memory leaks
64
65 ; Max number of requests that worker will handle before being gracefully restarted.
66 ; Prevents memory leaks, jitter adds variability so not all workers are restarted at once.
60 67 #max_requests = 1000
61 68 #max_requests_jitter = 30
62 ## amount of time a worker can spend with handling a request before it
63 ## gets killed and restarted. Set to 6hrs
69
70 ; Amount of time a worker can spend with handling a request before it
71 ; gets killed and restarted. By default set to 21600 (6hrs)
72 ; Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
64 73 #timeout = 21600
65 74
66 ## prefix middleware for RhodeCode.
67 ## recommended when using proxy setup.
68 ## allows to set RhodeCode under a prefix in server.
69 ## eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
70 ## And set your prefix like: `prefix = /custom_prefix`
71 ## be sure to also set beaker.session.cookie_path = /custom_prefix if you need
72 ## to make your cookies only work on prefix url
75 ; The maximum size of HTTP request line in bytes.
76 ; 0 for unlimited
77 #limit_request_line = 0
78
79
80 ; Prefix middleware for RhodeCode.
81 ; recommended when using proxy setup.
82 ; allows to set RhodeCode under a prefix in server.
83 ; eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
84 ; And set your prefix like: `prefix = /custom_prefix`
85 ; be sure to also set beaker.session.cookie_path = /custom_prefix if you need
86 ; to make your cookies only work on prefix url
73 87 [filter:proxy-prefix]
74 88 use = egg:PasteDeploy#prefix
75 89 prefix = /
76 90
77 91 [app:main]
92 ; The %(here)s variable will be replaced with the absolute path of parent directory
93 ; of this file
94 ; Each option in the app:main can be override by an environmental variable
95 ;
96 ;To override an option:
97 ;
98 ;RC_<KeyName>
99 ;Everything should be uppercase, . and - should be replaced by _.
100 ;For example, if you have these configuration settings:
101 ;rc_cache.repo_object.backend = foo
102 ;can be overridden by
103 ;export RC_CACHE_REPO_OBJECT_BACKEND=foo
104
78 105 is_test = True
79 106 use = egg:rhodecode-enterprise-ce
80 107
81 108 ; enable proxy prefix middleware, defined above
82 109 #filter-with = proxy-prefix
83 110
84 111
85 112 ## RHODECODE PLUGINS ##
86 113 rhodecode.includes = rhodecode.api
87 114
88 115 # api prefix url
89 116 rhodecode.api.url = /_admin/api
90 117
91 118
92 119 ## END RHODECODE PLUGINS ##
93 120
94 121 ## encryption key used to encrypt social plugin tokens,
95 122 ## remote_urls with credentials etc, if not set it defaults to
96 123 ## `beaker.session.secret`
97 124 #rhodecode.encrypted_values.secret =
98 125
99 126 ; decryption strict mode (enabled by default). It controls if decryption raises
100 127 ; `SignatureVerificationError` in case of wrong key, or damaged encryption data.
101 128 #rhodecode.encrypted_values.strict = false
102 129
103 130 ; Pick algorithm for encryption. Either fernet (more secure) or aes (default)
104 131 ; fernet is safer, and we strongly recommend switching to it.
105 132 ; Due to backward compatibility aes is used as default.
106 133 #rhodecode.encrypted_values.algorithm = fernet
107 134
108 135 ; Return gzipped responses from RhodeCode (static files/application)
109 136 gzip_responses = false
110 137
111 138 ; Auto-generate javascript routes file on startup
112 139 generate_js_files = false
113 140
114 141 ; System global default language.
115 142 ; All available languages: en (default), be, de, es, fr, it, ja, pl, pt, ru, zh
116 143 lang = en
117 144
118 ## perform a full repository scan on each server start, this should be
119 ## set to false after first startup, to allow faster server restarts.
145 ; Perform a full repository scan and import on each server start.
146 ; Settings this to true could lead to very long startup time.
120 147 startup.import_repos = true
121 148
122 149 ; Uncomment and set this path to use archive download cache.
123 150 ; Once enabled, generated archives will be cached at this location
124 151 ; and served from the cache during subsequent requests for the same archive of
125 152 ; the repository.
126 153 #archive_cache_dir = /tmp/tarballcache
127 154
128 155 ; URL at which the application is running. This is used for Bootstrapping
129 156 ; requests in context when no web request is available. Used in ishell, or
130 157 ; SSH calls. Set this for events to receive proper url for SSH calls.
131 158 app.base_url = http://rhodecode.local
132 159
133 160 ; Unique application ID. Should be a random unique string for security.
134 161 app_instance_uuid = rc-production
135 162
136 163 ## cut off limit for large diffs (size in bytes)
137 164 cut_off_limit_diff = 1024000
138 165 cut_off_limit_file = 256000
139 166
140 167 ; Use cached version of vcs repositories everywhere. Recommended to be `true`
141 168 vcs_full_cache = false
142 169
143 170 ; Force https in RhodeCode, fixes https redirects, assumes it's always https.
144 171 ; Normally this is controlled by proper flags sent from http server such as Nginx or Apache
145 172 force_https = false
146 173
147 174 ; use Strict-Transport-Security headers
148 175 use_htsts = false
149 176
150 177 ; Set to true if your repos are exposed using the dumb protocol
151 178 git_update_server_info = false
152 179
153 180 ; RSS/ATOM feed options
154 181 rss_cut_off_limit = 256000
155 182 rss_items_per_page = 10
156 183 rss_include_diff = false
157 184
158 185 ; gist URL alias, used to create nicer urls for gist. This should be an
159 186 ; url that does rewrites to _admin/gists/{gistid}.
160 187 ; example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
161 188 ; RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
162 189 gist_alias_url =
163 190
164 ## List of views (using glob pattern syntax) that AUTH TOKENS could be
165 ## used for access.
166 ## Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
167 ## came from the the logged in user who own this authentication token.
168 ## Additionally @TOKEN syntaxt can be used to bound the view to specific
169 ## authentication token. Such view would be only accessible when used together
170 ## with this authentication token
171 ##
172 ## list of all views can be found under `/_admin/permissions/auth_token_access`
173 ## The list should be "," separated and on a single line.
174 ##
175 ## Most common views to enable:
191 ; List of views (using glob pattern syntax) that AUTH TOKENS could be
192 ; used for access.
193 ; Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
194 ; came from the the logged in user who own this authentication token.
195 ; Additionally @TOKEN syntax can be used to bound the view to specific
196 ; authentication token. Such view would be only accessible when used together
197 ; with this authentication token
198 ; list of all views can be found under `/_admin/permissions/auth_token_access`
199 ; The list should be "," separated and on a single line.
200 ; Most common views to enable:
201
176 202 # RepoCommitsView:repo_commit_download
177 203 # RepoCommitsView:repo_commit_patch
178 204 # RepoCommitsView:repo_commit_raw
179 205 # RepoCommitsView:repo_commit_raw@TOKEN
180 206 # RepoFilesView:repo_files_diff
181 207 # RepoFilesView:repo_archivefile
182 208 # RepoFilesView:repo_file_raw
183 209 # GistView:*
184 210 api_access_controllers_whitelist =
185 211
186 212 ; Default encoding used to convert from and to unicode
187 213 ; can be also a comma separated list of encoding in case of mixed encodings
188 214 default_encoding = UTF-8
189 215
190 216 ; instance-id prefix
191 217 ; a prefix key for this instance used for cache invalidation when running
192 218 ; multiple instances of RhodeCode, make sure it's globally unique for
193 219 ; all running RhodeCode instances. Leave empty if you don't use it
194 220 instance_id =
195 221
196 222 ; Fallback authentication plugin. Set this to a plugin ID to force the usage
197 223 ; of an authentication plugin also if it is disabled by it's settings.
198 224 ; This could be useful if you are unable to log in to the system due to broken
199 225 ; authentication settings. Then you can enable e.g. the internal RhodeCode auth
200 226 ; module to log in again and fix the settings.
201 227 ; Available builtin plugin IDs (hash is part of the ID):
202 228 ; egg:rhodecode-enterprise-ce#rhodecode
203 229 ; egg:rhodecode-enterprise-ce#pam
204 230 ; egg:rhodecode-enterprise-ce#ldap
205 231 ; egg:rhodecode-enterprise-ce#jasig_cas
206 232 ; egg:rhodecode-enterprise-ce#headers
207 233 ; egg:rhodecode-enterprise-ce#crowd
208 234
209 235 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
210 236
211 237 ; Flag to control loading of legacy plugins in py:/path format
212 238 auth_plugin.import_legacy_plugins = true
213 239
214 240 ; alternative return HTTP header for failed authentication. Default HTTP
215 241 ; response is 401 HTTPUnauthorized. Currently HG clients have troubles with
216 242 ; handling that causing a series of failed authentication calls.
217 243 ; Set this variable to 403 to return HTTPForbidden, or any other HTTP code
218 244 ; This will be served instead of default 401 on bad authentication
219 245 auth_ret_code =
220 246
221 ## use special detection method when serving auth_ret_code, instead of serving
222 ## ret_code directly, use 401 initially (Which triggers credentials prompt)
223 ## and then serve auth_ret_code to clients
247 ; use special detection method when serving auth_ret_code, instead of serving
248 ; ret_code directly, use 401 initially (Which triggers credentials prompt)
249 ; and then serve auth_ret_code to clients
224 250 auth_ret_code_detection = false
225 251
226 ## locking return code. When repository is locked return this HTTP code. 2XX
227 ## codes don't break the transactions while 4XX codes do
252 ; locking return code. When repository is locked return this HTTP code. 2XX
253 ; codes don't break the transactions while 4XX codes do
228 254 lock_ret_code = 423
229 255
230 ## allows to change the repository location in settings page
256 ; allows to change the repository location in settings page
231 257 allow_repo_location_change = true
232 258
233 ## allows to setup custom hooks in settings page
259 ; allows to setup custom hooks in settings page
234 260 allow_custom_hooks_settings = true
235 261
236 262 ## generated license token, goto license page in RhodeCode settings to obtain
237 263 ## new token
238 264 license_token = abra-cada-bra1-rce3
239 265
240 266 ## supervisor connection uri, for managing supervisor and logs.
241 267 supervisor.uri =
242 268 ## supervisord group name/id we only want this RC instance to handle
243 269 supervisor.group_id = dev
244 270
245 271 ## Display extended labs settings
246 272 labs_settings_active = true
247 273
274 ; Custom exception store path, defaults to TMPDIR
275 ; This is used to store exception from RhodeCode in shared directory
276 #exception_tracker.store_path =
277
278 ; Send email with exception details when it happens
279 #exception_tracker.send_email = false
280
281 ; Comma separated list of recipients for exception emails,
282 ; e.g admin@rhodecode.com,devops@rhodecode.com
283 ; Can be left empty, then emails will be sent to ALL super-admins
284 #exception_tracker.send_email_recipients =
285
286 ; optional prefix to Add to email Subject
287 #exception_tracker.email_prefix = [RHODECODE ERROR]
288
289 ; File store configuration. This is used to store and serve uploaded files
290 file_store.enabled = true
291
292 ; Storage backend, available options are: local
293 file_store.backend = local
294
295 ; path to store the uploaded binaries
296 file_store.storage_path = %(here)s/data/file_store
297
298
248 299 ; #############
249 300 ; CELERY CONFIG
250 301 ; #############
251 302
252 303 ; manually run celery: /path/to/celery worker -E --beat --app rhodecode.lib.celerylib.loader --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler --loglevel DEBUG --ini /path/to/rhodecode.ini
253 304
254 305 use_celery = false
255 306
256 307 ; path to store schedule database
257 308 #celerybeat-schedule.path =
258 309
259 310 ; connection url to the message broker (default redis)
260 311 celery.broker_url = redis://localhost:6379/8
261 312
262 313 ; rabbitmq example
263 314 #celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
264 315
265 316 ; maximum tasks to execute before worker restart
266 317 celery.max_tasks_per_child = 100
267 318
268 319 ; tasks will never be sent to the queue, but executed locally instead.
269 320 celery.task_always_eager = false
270 321
271 322 ; #############
272 323 ; DOGPILE CACHE
273 324 ; #############
274 325
275 326 ; Default cache dir for caches. Putting this into a ramdisk can boost performance.
276 327 ; eg. /tmpfs/data_ramdisk, however this directory might require large amount of space
277 328 cache_dir = %(here)s/data
278 329
279 330 ## locking and default file storage for Beaker. Putting this into a ramdisk
280 331 ## can boost performance, eg. %(here)s/data_ramdisk/cache/beaker_data
281 332 beaker.cache.data_dir = %(here)s/rc/data/cache/beaker_data
282 333 beaker.cache.lock_dir = %(here)s/rc/data/cache/beaker_lock
283 334
284 335 beaker.cache.regions = long_term
285 336
286 337 beaker.cache.long_term.type = memory
287 338 beaker.cache.long_term.expire = 36000
288 339 beaker.cache.long_term.key_length = 256
289 340
290 341
291 342 #####################################
292 343 ### DOGPILE CACHE ####
293 344 #####################################
294 345
295 346 ## permission tree cache settings
296 347 rc_cache.cache_perms.backend = dogpile.cache.rc.file_namespace
297 348 rc_cache.cache_perms.expiration_time = 0
298 349 rc_cache.cache_perms.arguments.filename = /tmp/rc_cache_1
299 350
300 351
301 352 ## cache settings for SQL queries
302 353 rc_cache.sql_cache_short.backend = dogpile.cache.rc.memory_lru
303 354 rc_cache.sql_cache_short.expiration_time = 0
304 355
305 356
306 357 ; ##############
307 358 ; BEAKER SESSION
308 359 ; ##############
309 360
310 361 ; beaker.session.type is type of storage options for the logged users sessions. Current allowed
311 362 ; types are file, ext:redis, ext:database, ext:memcached, and memory (default if not specified).
312 363 ; Fastest ones are Redis and ext:database
313 364 beaker.session.type = file
314 365 beaker.session.data_dir = %(here)s/rc/data/sessions/data
315 366
316 367 ; Redis based sessions
317 368 #beaker.session.type = ext:redis
318 369 #beaker.session.url = redis://127.0.0.1:6379/2
319 370
320 371 ; DB based session, fast, and allows easy management over logged in users
321 372 #beaker.session.type = ext:database
322 373 #beaker.session.table_name = db_session
323 374 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
324 375 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
325 376 #beaker.session.sa.pool_recycle = 3600
326 377 #beaker.session.sa.echo = false
327 378
328 379 beaker.session.key = rhodecode
329 380 beaker.session.secret = test-rc-uytcxaz
330 381 beaker.session.lock_dir = %(here)s/rc/data/sessions/lock
331 382
332 383 ; Secure encrypted cookie. Requires AES and AES python libraries
333 384 ; you must disable beaker.session.secret to use this
334 385 #beaker.session.encrypt_key = key_for_encryption
335 386 #beaker.session.validate_key = validation_key
336 387
337 388 ; Sets session as invalid (also logging out user) if it haven not been
338 389 ; accessed for given amount of time in seconds
339 390 beaker.session.timeout = 2592000
340 391 beaker.session.httponly = true
341 392
342 393 ; Path to use for the cookie. Set to prefix if you use prefix middleware
343 394 #beaker.session.cookie_path = /custom_prefix
344 395
345 396 ; Set https secure cookie
346 397 beaker.session.secure = false
347 398
348 399 ## auto save the session to not to use .save()
349 400 beaker.session.auto = false
350 401
351 ## default cookie expiration time in seconds, set to `true` to set expire
352 ## at browser close
402 ; default cookie expiration time in seconds, set to `true` to set expire
403 ; at browser close
353 404 #beaker.session.cookie_expires = 3600
354 405
355 406 ; #############################
356 407 ; SEARCH INDEXING CONFIGURATION
357 408 ; #############################
358 409
359 ## WHOOSH Backend, doesn't require additional services to run
360 ## it works good with few dozen repos
410 ; Full text search indexer is available in rhodecode-tools under
411 ; `rhodecode-tools index` command
412
413 ; WHOOSH Backend, doesn't require additional services to run
414 ; it works good with few dozen repos
361 415 search.module = rhodecode.lib.index.whoosh
362 416 search.location = %(here)s/data/index
363 417
364 418 ; ####################
365 419 ; CHANNELSTREAM CONFIG
366 420 ; ####################
367 421
368 422 ; channelstream enables persistent connections and live notification
369 423 ; in the system. It's also used by the chat system
370 424
371 425 channelstream.enabled = false
372 426
373 427 ; server address for channelstream server on the backend
374 428 channelstream.server = 127.0.0.1:9800
375 429
376 430 ; location of the channelstream server from outside world
377 431 ; use ws:// for http or wss:// for https. This address needs to be handled
378 432 ; by external HTTP server such as Nginx or Apache
379 433 ; see Nginx/Apache configuration examples in our docs
380 434 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
381 435 channelstream.secret = secret
382 436 channelstream.history.location = %(here)s/channelstream_history
383 437
384 438 ; Internal application path that Javascript uses to connect into.
385 439 ; If you use proxy-prefix the prefix should be added before /_channelstream
386 440 channelstream.proxy_path = /_channelstream
387 441
388 442
389 443 ; ##############################
390 444 ; MAIN RHODECODE DATABASE CONFIG
391 445 ; ##############################
392 446
393 447 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
394 448 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
395 449 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode?charset=utf8
396 450 ; pymysql is an alternative driver for MySQL, use in case of problems with default one
397 451 #sqlalchemy.db1.url = mysql+pymysql://root:qweqwe@localhost/rhodecode
398 452
399 453 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db?timeout=30
400 454
401 455 ; see sqlalchemy docs for other advanced settings
402 456 ; print the sql statements to output
403 457 sqlalchemy.db1.echo = false
404 458
405 459 ; recycle the connections after this amount of seconds
406 460 sqlalchemy.db1.pool_recycle = 3600
407 461 sqlalchemy.db1.convert_unicode = true
408 462
409 463 ; the number of connections to keep open inside the connection pool.
410 464 ; 0 indicates no limit
411 465 #sqlalchemy.db1.pool_size = 5
412 466
413 467 ; The number of connections to allow in connection pool "overflow", that is
414 468 ; connections that can be opened above and beyond the pool_size setting,
415 469 ; which defaults to five.
416 470 #sqlalchemy.db1.max_overflow = 10
417 471
418 472 ; Connection check ping, used to detect broken database connections
419 473 ; could be enabled to better handle cases if MySQL has gone away errors
420 474 #sqlalchemy.db1.ping_connection = true
421 475
422 476 ; ##########
423 477 ; VCS CONFIG
424 478 ; ##########
425 479 vcs.server.enable = true
426 480 vcs.server = localhost:9901
427 481
428 482 ; Web server connectivity protocol, responsible for web based VCS operations
429 483 ; Available protocols are:
430 484 ; `http` - use http-rpc backend (default)
431 485 vcs.server.protocol = http
432 486
433 487 ; Push/Pull operations protocol, available options are:
434 488 ; `http` - use http-rpc backend (default)
435 489 vcs.scm_app_implementation = http
436 490
437 491 ; Push/Pull operations hooks protocol, available options are:
438 492 ; `http` - use http-rpc backend (default)
439 493 vcs.hooks.protocol = http
440 494
441 495 ; Host on which this instance is listening for hooks. If vcsserver is in other location
442 496 ; this should be adjusted.
443 497 vcs.hooks.host = 127.0.0.1
444 498
445 499 ; Start VCSServer with this instance as a subprocess, useful for development
446 500 vcs.start_server = false
447 501
448 502 ; List of enabled VCS backends, available options are:
449 503 ; `hg` - mercurial
450 504 ; `git` - git
451 505 ; `svn` - subversion
452 506 vcs.backends = hg, git, svn
453 507
454 508 ; Wait this number of seconds before killing connection to the vcsserver
455 509 vcs.connection_timeout = 3600
456 510
457 511 ; Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
458 512 ; Set a numeric version for your current SVN e.g 1.8, or 1.12
459 513 ; Legacy available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
460 514 #vcs.svn.compatible_version = 1.8
461 515
462 516 ; Cache flag to cache vcsserver remote calls locally
463 517 ; It uses cache_region `cache_repo`
464 518 vcs.methods.cache = false
465 519
466 520 ; ####################################################
467 521 ; Subversion proxy support (mod_dav_svn)
468 522 ; Maps RhodeCode repo groups into SVN paths for Apache
469 523 ; ####################################################
470 524
471 525 ; Enable or disable the config file generation.
472 526 svn.proxy.generate_config = false
473 527
474 528 ; Generate config file with `SVNListParentPath` set to `On`.
475 529 svn.proxy.list_parent_path = true
476 530
477 531 ; Set location and file name of generated config file.
478 532 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
479 533
480 534 ; alternative mod_dav config template. This needs to be a valid mako template
481 535 ; Example template can be found in the source code:
482 536 ; rhodecode/apps/svn_support/templates/mod-dav-svn.conf.mako
483 537 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
484 538
485 539 ; Used as a prefix to the `Location` block in the generated config file.
486 540 ; In most cases it should be set to `/`.
487 541 svn.proxy.location_root = /
488 542
489 543 ; Command to reload the mod dav svn configuration on change.
490 544 ; Example: `/etc/init.d/apache2 reload` or /home/USER/apache_reload.sh
491 545 ; Make sure user who runs RhodeCode process is allowed to reload Apache
492 546 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
493 547
494 548 ; If the timeout expires before the reload command finishes, the command will
495 549 ; be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
496 550 #svn.proxy.reload_timeout = 10
497 551
498 552 ; ####################
499 553 ; SSH Support Settings
500 554 ; ####################
501 555
502 556 ; Defines if a custom authorized_keys file should be created and written on
503 557 ; any change user ssh keys. Setting this to false also disables possibility
504 558 ; of adding SSH keys by users from web interface. Super admins can still
505 559 ; manage SSH Keys.
506 560 ssh.generate_authorized_keyfile = true
507 561
508 562 ; Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
509 563 # ssh.authorized_keys_ssh_opts =
510 564
511 565 ; Path to the authorized_keys file where the generate entries are placed.
512 566 ; It is possible to have multiple key files specified in `sshd_config` e.g.
513 567 ; AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
514 568 ssh.authorized_keys_file_path = %(here)s/rc/authorized_keys_rhodecode
515 569
516 570 ; Command to execute the SSH wrapper. The binary is available in the
517 571 ; RhodeCode installation directory.
518 572 ; e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
519 573 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
520 574
521 575 ; Allow shell when executing the ssh-wrapper command
522 576 ssh.wrapper_cmd_allow_shell = false
523 577
524 578 ; Enables logging, and detailed output send back to the client during SSH
525 579 ; operations. Useful for debugging, shouldn't be used in production.
526 580 ssh.enable_debug_logging = false
527 581
528 582 ; Paths to binary executable, by default they are the names, but we can
529 583 ; override them if we want to use a custom one
530 584 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
531 585 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
532 586 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
533 587
534 588 ; Enables SSH key generator web interface. Disabling this still allows users
535 589 ; to add their own keys.
536 590 ssh.enable_ui_key_generator = true
537 591
538 592 ; Statsd client config, this is used to send metrics to statsd
539 593 ; We recommend setting statsd_exported and scrape them using Promethues
540 594 #statsd.enabled = false
541 595 #statsd.statsd_host = 0.0.0.0
542 596 #statsd.statsd_port = 8125
543 597 #statsd.statsd_prefix =
544 598 #statsd.statsd_ipv6 = false
545 599
546
547 600 ; configure logging automatically at server startup set to false
548 601 ; to use the below custom logging config.
602 ; RC_LOGGING_FORMATTER
603 ; RC_LOGGING_LEVEL
604 ; env variables can control the settings for logging in case of autoconfigure
605
549 606 logging.autoconfigure = false
550 607
551 608 ; specify your own custom logging config file to configure logging
552 609 #logging.logging_conf_file = /path/to/custom_logging.ini
553 610
554 611 ; Dummy marker to add new entries after.
555 612 ; Add any custom entries below. Please don't remove this marker.
556 613 custom.conf = 1
557 614
558 615
559 616 ; #####################
560 617 ; LOGGING CONFIGURATION
561 618 ; #####################
619
562 620 [loggers]
563 keys = root, sqlalchemy, beaker, rhodecode, ssh_wrapper
621 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
564 622
565 623 [handlers]
566 624 keys = console, console_sql
567 625
568 626 [formatters]
569 keys = generic, color_formatter, color_formatter_sql
627 keys = generic, json, color_formatter, color_formatter_sql
570 628
571 629 ; #######
572 630 ; LOGGERS
573 631 ; #######
574 632 [logger_root]
575 633 level = NOTSET
576 634 handlers = console
577 635
578 636 [logger_routes]
579 637 level = DEBUG
580 638 handlers =
581 639 qualname = routes.middleware
582 640 ## "level = DEBUG" logs the route matched and routing variables.
583 641 propagate = 1
584 642
585 643 [logger_sqlalchemy]
586 644 level = INFO
587 645 handlers = console_sql
588 646 qualname = sqlalchemy.engine
589 647 propagate = 0
590 648
591 649 [logger_beaker]
592 650 level = DEBUG
593 651 handlers =
594 652 qualname = beaker.container
595 653 propagate = 1
596 654
597 655 [logger_rhodecode]
598 656 level = DEBUG
599 657 handlers =
600 658 qualname = rhodecode
601 659 propagate = 1
602 660
603 661 [logger_ssh_wrapper]
604 662 level = DEBUG
605 663 handlers =
606 664 qualname = ssh_wrapper
607 665 propagate = 1
608 666
609 667 [logger_celery]
610 668 level = DEBUG
611 669 handlers =
612 670 qualname = celery
613 671
614 672
615 673 ; ########
616 674 ; HANDLERS
617 675 ; ########
618 676
619 677 [handler_console]
620 678 class = StreamHandler
621 679 args = (sys.stderr, )
622 680 level = DEBUG
681 ; To enable JSON formatted logs replace 'generic/color_formatter' with 'json'
682 ; This allows sending properly formatted logs to grafana loki or elasticsearch
623 683 formatter = generic
624 ; To enable JSON formatted logs replace generic with json
625 ; This allows sending properly formatted logs to grafana loki or elasticsearch
626 #formatter = json
627 684
628 685 [handler_console_sql]
629 686 ; "level = DEBUG" logs SQL queries and results.
630 687 ; "level = INFO" logs SQL queries.
631 688 ; "level = WARN" logs neither. (Recommended for production systems.)
632 689 class = StreamHandler
633 690 args = (sys.stderr, )
634 691 level = WARN
692 ; To enable JSON formatted logs replace 'generic/color_formatter_sql' with 'json'
693 ; This allows sending properly formatted logs to grafana loki or elasticsearch
635 694 formatter = generic
636 695
637 696 ; ##########
638 697 ; FORMATTERS
639 698 ; ##########
640 699
641 700 [formatter_generic]
642 701 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
643 702 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
644 703 datefmt = %Y-%m-%d %H:%M:%S
645 704
646 705 [formatter_color_formatter]
647 706 class = rhodecode.lib.logging_formatter.ColorFormatter
648 707 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
649 708 datefmt = %Y-%m-%d %H:%M:%S
650 709
651 710 [formatter_color_formatter_sql]
652 711 class = rhodecode.lib.logging_formatter.ColorFormatterSql
653 712 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
654 713 datefmt = %Y-%m-%d %H:%M:%S
655 714
656 715 [formatter_json]
657 format = %(message)s
658 class = rhodecode.lib._vendor.jsonlogger.JsonFormatter No newline at end of file
716 format = %(timestamp)s %(levelname)s %(name)s %(message)s %(req_id)s
717 class = rhodecode.lib._vendor.jsonlogger.JsonFormatter
General Comments 0
You need to be logged in to leave comments. Login now