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