##// END OF EJS Templates
auth-plugins: remove the in-object cache that cauused invalidation failures....
super-admin -
r4885:2768c2d4 default
parent child Browse files
Show More
@@ -1,107 +1,121 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 import time
21 22 import logging
22 23
23 24 from pyramid.exceptions import ConfigurationError
24 25 from zope.interface import implementer
25 26
26 27 from rhodecode.authentication.interface import IAuthnPluginRegistry
28 from rhodecode.model.settings import SettingsModel
27 29 from rhodecode.lib.utils2 import safe_str
28 from rhodecode.model.settings import SettingsModel
30 from rhodecode.lib.statsd_client import StatsdClient
31 from rhodecode.lib import rc_cache
29 32
30 33 log = logging.getLogger(__name__)
31 34
32 35
33 36 @implementer(IAuthnPluginRegistry)
34 37 class AuthenticationPluginRegistry(object):
35 38
36 39 # INI settings key to set a fallback authentication plugin.
37 40 fallback_plugin_key = 'rhodecode.auth_plugin_fallback'
38 41
39 42 def __init__(self, settings):
40 43 self._plugins = {}
41 self._plugins_for_auth = None
42 44 self._fallback_plugin = settings.get(self.fallback_plugin_key, None)
43 45
44 46 def add_authn_plugin(self, config, plugin):
45 47 plugin_id = plugin.get_id()
46 48 if plugin_id in self._plugins.keys():
47 49 raise ConfigurationError(
48 50 'Cannot register authentication plugin twice: "%s"', plugin_id)
49 51 else:
50 52 log.debug('Register authentication plugin: "%s"', plugin_id)
51 53 self._plugins[plugin_id] = plugin
52 54
53 55 def get_plugins(self):
54 56 def sort_key(plugin):
55 57 return str.lower(safe_str(plugin.get_display_name()))
56 58
57 59 return sorted(self._plugins.values(), key=sort_key)
58 60
59 61 def get_plugin(self, plugin_id):
60 62 return self._plugins.get(plugin_id, None)
61 63
62 64 def get_plugin_by_uid(self, plugin_uid):
63 65 for plugin in self._plugins.values():
64 66 if plugin.uid == plugin_uid:
65 67 return plugin
66 68
67 def invalidate_plugins_for_auth(self):
68 log.debug('Invalidating cached plugins for authentication')
69 self._plugins_for_auth = None
70
71 def get_plugins_for_authentication(self):
69 def get_plugins_for_authentication(self, cache=True):
72 70 """
73 71 Returns a list of plugins which should be consulted when authenticating
74 72 a user. It only returns plugins which are enabled and active.
75 73 Additionally it includes the fallback plugin from the INI file, if
76 74 `rhodecode.auth_plugin_fallback` is set to a plugin ID.
77 75 """
78 if self._plugins_for_auth is not None:
79 return self._plugins_for_auth
76
77 cache_namespace_uid = 'cache_auth_plugins'
78 region = rc_cache.get_or_create_region('cache_general', cache_namespace_uid)
80 79
81 plugins = []
80 @region.conditional_cache_on_arguments(condition=cache)
81 def _get_auth_plugins(name, key, fallback_plugin):
82 plugins = []
82 83
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.
85 enabled_plugins = SettingsModel().get_auth_plugins()
86 raw_settings = SettingsModel().get_all_settings(cache=True)
87 for plugin_id in enabled_plugins:
88 plugin = self.get_plugin(plugin_id)
89 if plugin is not None and plugin.is_active(
90 plugin_cached_settings=raw_settings):
84 # Add all enabled and active plugins to the list. We iterate over the
85 # auth_plugins setting from DB because it also represents the ordering.
86 enabled_plugins = SettingsModel().get_auth_plugins()
87 raw_settings = SettingsModel().get_all_settings(cache=False)
88 for plugin_id in enabled_plugins:
89 plugin = self.get_plugin(plugin_id)
90 if plugin is not None and plugin.is_active(
91 plugin_cached_settings=raw_settings):
92
93 # inject settings into plugin, we can re-use the DB fetched settings here
94 plugin._settings = plugin._propagate_settings(raw_settings)
95 plugins.append(plugin)
91 96
92 # inject settings into plugin, we can re-use the DB fetched settings here
93 plugin._settings = plugin._propagate_settings(raw_settings)
94 plugins.append(plugin)
97 # Add the fallback plugin from ini file.
98 if fallback_plugin:
99 log.warn(
100 'Using fallback authentication plugin from INI file: "%s"',
101 fallback_plugin)
102 plugin = self.get_plugin(fallback_plugin)
103 if plugin is not None and plugin not in plugins:
104 plugin._settings = plugin._propagate_settings(raw_settings)
105 plugins.append(plugin)
106 return plugins
95 107
96 # Add the fallback plugin from ini file.
97 if self._fallback_plugin:
98 log.warn(
99 'Using fallback authentication plugin from INI file: "%s"',
100 self._fallback_plugin)
101 plugin = self.get_plugin(self._fallback_plugin)
102 if plugin is not None and plugin not in plugins:
103 plugin._settings = plugin._propagate_settings(raw_settings)
104 plugins.append(plugin)
108 start = time.time()
109 plugins = _get_auth_plugins('rhodecode_auth_plugins', 'v1', self._fallback_plugin)
110
111 compute_time = time.time() - start
112 log.debug('cached method:%s took %.4fs', _get_auth_plugins.func_name, compute_time)
105 113
106 self._plugins_for_auth = plugins
107 return self._plugins_for_auth
114 statsd = StatsdClient.statsd
115 if statsd:
116 elapsed_time_ms = round(1000.0 * compute_time) # use ms only
117 statsd.timing("rhodecode_auth_plugins_timing.histogram", elapsed_time_ms,
118 use_decimals=False)
119
120 return plugins
121
@@ -1,924 +1,918 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import re
23 23 import hashlib
24 24 import logging
25 25 import time
26 26 from collections import namedtuple
27 27 from functools import wraps
28 28 import bleach
29 29 from pyramid.threadlocal import get_current_request, get_current_registry
30 30
31 31 from rhodecode.lib import rc_cache
32 32 from rhodecode.lib.utils2 import (
33 33 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
34 34 from rhodecode.lib.vcs.backends import base
35 35 from rhodecode.lib.statsd_client import StatsdClient
36 36 from rhodecode.model import BaseModel
37 37 from rhodecode.model.db import (
38 38 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
39 39 from rhodecode.model.meta import Session
40 40
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 UiSetting = namedtuple(
46 46 'UiSetting', ['section', 'key', 'value', 'active'])
47 47
48 48 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
49 49
50 50
51 51 class SettingNotFound(Exception):
52 52 def __init__(self, setting_id):
53 53 msg = 'Setting `{}` is not found'.format(setting_id)
54 54 super(SettingNotFound, self).__init__(msg)
55 55
56 56
57 57 class SettingsModel(BaseModel):
58 58 BUILTIN_HOOKS = (
59 59 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
60 60 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
61 61 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL,
62 62 RhodeCodeUi.HOOK_PUSH_KEY,)
63 63 HOOKS_SECTION = 'hooks'
64 64
65 65 def __init__(self, sa=None, repo=None):
66 66 self.repo = repo
67 67 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
68 68 self.SettingsDbModel = (
69 69 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
70 70 super(SettingsModel, self).__init__(sa)
71 71
72 72 def get_ui_by_key(self, key):
73 73 q = self.UiDbModel.query()
74 74 q = q.filter(self.UiDbModel.ui_key == key)
75 75 q = self._filter_by_repo(RepoRhodeCodeUi, q)
76 76 return q.scalar()
77 77
78 78 def get_ui_by_section(self, section):
79 79 q = self.UiDbModel.query()
80 80 q = q.filter(self.UiDbModel.ui_section == section)
81 81 q = self._filter_by_repo(RepoRhodeCodeUi, q)
82 82 return q.all()
83 83
84 84 def get_ui_by_section_and_key(self, section, key):
85 85 q = self.UiDbModel.query()
86 86 q = q.filter(self.UiDbModel.ui_section == section)
87 87 q = q.filter(self.UiDbModel.ui_key == key)
88 88 q = self._filter_by_repo(RepoRhodeCodeUi, q)
89 89 return q.scalar()
90 90
91 91 def get_ui(self, section=None, key=None):
92 92 q = self.UiDbModel.query()
93 93 q = self._filter_by_repo(RepoRhodeCodeUi, q)
94 94
95 95 if section:
96 96 q = q.filter(self.UiDbModel.ui_section == section)
97 97 if key:
98 98 q = q.filter(self.UiDbModel.ui_key == key)
99 99
100 100 # TODO: mikhail: add caching
101 101 result = [
102 102 UiSetting(
103 103 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
104 104 value=safe_str(r.ui_value), active=r.ui_active
105 105 )
106 106 for r in q.all()
107 107 ]
108 108 return result
109 109
110 110 def get_builtin_hooks(self):
111 111 q = self.UiDbModel.query()
112 112 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
113 113 return self._get_hooks(q)
114 114
115 115 def get_custom_hooks(self):
116 116 q = self.UiDbModel.query()
117 117 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
118 118 return self._get_hooks(q)
119 119
120 120 def create_ui_section_value(self, section, val, key=None, active=True):
121 121 new_ui = self.UiDbModel()
122 122 new_ui.ui_section = section
123 123 new_ui.ui_value = val
124 124 new_ui.ui_active = active
125 125
126 126 repository_id = ''
127 127 if self.repo:
128 128 repo = self._get_repo(self.repo)
129 129 repository_id = repo.repo_id
130 130 new_ui.repository_id = repository_id
131 131
132 132 if not key:
133 133 # keys are unique so they need appended info
134 134 if self.repo:
135 135 key = hashlib.sha1(
136 136 '{}{}{}'.format(section, val, repository_id)).hexdigest()
137 137 else:
138 138 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
139 139
140 140 new_ui.ui_key = key
141 141
142 142 Session().add(new_ui)
143 143 return new_ui
144 144
145 145 def create_or_update_hook(self, key, value):
146 146 ui = (
147 147 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
148 148 self.UiDbModel())
149 149 ui.ui_section = self.HOOKS_SECTION
150 150 ui.ui_active = True
151 151 ui.ui_key = key
152 152 ui.ui_value = value
153 153
154 154 if self.repo:
155 155 repo = self._get_repo(self.repo)
156 156 repository_id = repo.repo_id
157 157 ui.repository_id = repository_id
158 158
159 159 Session().add(ui)
160 160 return ui
161 161
162 162 def delete_ui(self, id_):
163 163 ui = self.UiDbModel.get(id_)
164 164 if not ui:
165 165 raise SettingNotFound(id_)
166 166 Session().delete(ui)
167 167
168 168 def get_setting_by_name(self, name):
169 169 q = self._get_settings_query()
170 170 q = q.filter(self.SettingsDbModel.app_settings_name == name)
171 171 return q.scalar()
172 172
173 173 def create_or_update_setting(
174 174 self, name, val=Optional(''), type_=Optional('unicode')):
175 175 """
176 176 Creates or updates RhodeCode setting. If updates is triggered it will
177 177 only update parameters that are explicitly set Optional instance will
178 178 be skipped
179 179
180 180 :param name:
181 181 :param val:
182 182 :param type_:
183 183 :return:
184 184 """
185 185
186 186 res = self.get_setting_by_name(name)
187 187 repo = self._get_repo(self.repo) if self.repo else None
188 188
189 189 if not res:
190 190 val = Optional.extract(val)
191 191 type_ = Optional.extract(type_)
192 192
193 193 args = (
194 194 (repo.repo_id, name, val, type_)
195 195 if repo else (name, val, type_))
196 196 res = self.SettingsDbModel(*args)
197 197
198 198 else:
199 199 if self.repo:
200 200 res.repository_id = repo.repo_id
201 201
202 202 res.app_settings_name = name
203 203 if not isinstance(type_, Optional):
204 204 # update if set
205 205 res.app_settings_type = type_
206 206 if not isinstance(val, Optional):
207 207 # update if set
208 208 res.app_settings_value = val
209 209
210 210 Session().add(res)
211 211 return res
212 212
213 213 def get_cache_region(self):
214 214 repo = self._get_repo(self.repo) if self.repo else None
215 215 cache_key = "repo.{}".format(repo.repo_id) if repo else "general_settings"
216 216 cache_namespace_uid = 'cache_settings.{}'.format(cache_key)
217 217 region = rc_cache.get_or_create_region('cache_general', cache_namespace_uid)
218 218 return region, cache_key
219 219
220 220 def invalidate_settings_cache(self):
221 from rhodecode.authentication.base import get_authn_registry
222
223 221 region, cache_key = self.get_cache_region()
224 222 log.debug('Invalidation cache region %s for cache_key: %s', region, cache_key)
225 223 region.invalidate()
226 registry = get_current_registry()
227 if registry:
228 authn_registry = get_authn_registry(registry)
229 if authn_registry:
230 authn_registry.invalidate_plugins_for_auth()
231 224
232 225 def get_all_settings(self, cache=False, from_request=True):
233 226 # defines if we use GLOBAL, or PER_REPO
234 227 repo = self._get_repo(self.repo) if self.repo else None
235 228
236 229 # initially try the requests context, this is the fastest
237 230 # we only fetch global config
238 231 if from_request:
239 232 request = get_current_request()
240 233
241 234 if request and not repo and hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
242 235 rc_config = request.call_context.rc_config
243 236 if rc_config:
244 237 return rc_config
245 238
246 239 region, cache_key = self.get_cache_region()
247 240
248 241 @region.conditional_cache_on_arguments(condition=cache)
249 242 def _get_all_settings(name, key):
250 243 q = self._get_settings_query()
251 244 if not q:
252 245 raise Exception('Could not get application settings !')
253 246
254 247 settings = {
255 248 'rhodecode_' + res.app_settings_name: res.app_settings_value
256 249 for res in q
257 250 }
258 251 return settings
259 252
260 253 start = time.time()
261 254 result = _get_all_settings('rhodecode_settings', cache_key)
262 255 compute_time = time.time() - start
256 log.debug('cached method:%s took %.4fs', _get_all_settings.func_name, compute_time)
263 257
264 258 statsd = StatsdClient.statsd
265 259 if statsd:
266 260 elapsed_time_ms = round(1000.0 * compute_time) # use ms only
267 261 statsd.timing("rhodecode_settings_timing.histogram", elapsed_time_ms,
268 262 use_decimals=False)
269 263
270 264 log.debug('Fetching app settings for key: %s took: %.4fs: cache: %s', cache_key, compute_time, cache)
271 265
272 266 return result
273 267
274 268 def get_auth_settings(self):
275 269 q = self._get_settings_query()
276 270 q = q.filter(
277 271 self.SettingsDbModel.app_settings_name.startswith('auth_'))
278 272 rows = q.all()
279 273 auth_settings = {
280 274 row.app_settings_name: row.app_settings_value for row in rows}
281 275 return auth_settings
282 276
283 277 def get_auth_plugins(self):
284 278 auth_plugins = self.get_setting_by_name("auth_plugins")
285 279 return auth_plugins.app_settings_value
286 280
287 281 def get_default_repo_settings(self, strip_prefix=False):
288 282 q = self._get_settings_query()
289 283 q = q.filter(
290 284 self.SettingsDbModel.app_settings_name.startswith('default_'))
291 285 rows = q.all()
292 286
293 287 result = {}
294 288 for row in rows:
295 289 key = row.app_settings_name
296 290 if strip_prefix:
297 291 key = remove_prefix(key, prefix='default_')
298 292 result.update({key: row.app_settings_value})
299 293 return result
300 294
301 295 def get_repo(self):
302 296 repo = self._get_repo(self.repo)
303 297 if not repo:
304 298 raise Exception(
305 299 'Repository `{}` cannot be found inside the database'.format(
306 300 self.repo))
307 301 return repo
308 302
309 303 def _filter_by_repo(self, model, query):
310 304 if self.repo:
311 305 repo = self.get_repo()
312 306 query = query.filter(model.repository_id == repo.repo_id)
313 307 return query
314 308
315 309 def _get_hooks(self, query):
316 310 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
317 311 query = self._filter_by_repo(RepoRhodeCodeUi, query)
318 312 return query.all()
319 313
320 314 def _get_settings_query(self):
321 315 q = self.SettingsDbModel.query()
322 316 return self._filter_by_repo(RepoRhodeCodeSetting, q)
323 317
324 318 def list_enabled_social_plugins(self, settings):
325 319 enabled = []
326 320 for plug in SOCIAL_PLUGINS_LIST:
327 321 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
328 322 )):
329 323 enabled.append(plug)
330 324 return enabled
331 325
332 326
333 327 def assert_repo_settings(func):
334 328 @wraps(func)
335 329 def _wrapper(self, *args, **kwargs):
336 330 if not self.repo_settings:
337 331 raise Exception('Repository is not specified')
338 332 return func(self, *args, **kwargs)
339 333 return _wrapper
340 334
341 335
342 336 class IssueTrackerSettingsModel(object):
343 337 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
344 338 SETTINGS_PREFIX = 'issuetracker_'
345 339
346 340 def __init__(self, sa=None, repo=None):
347 341 self.global_settings = SettingsModel(sa=sa)
348 342 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
349 343
350 344 @property
351 345 def inherit_global_settings(self):
352 346 if not self.repo_settings:
353 347 return True
354 348 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
355 349 return setting.app_settings_value if setting else True
356 350
357 351 @inherit_global_settings.setter
358 352 def inherit_global_settings(self, value):
359 353 if self.repo_settings:
360 354 settings = self.repo_settings.create_or_update_setting(
361 355 self.INHERIT_SETTINGS, value, type_='bool')
362 356 Session().add(settings)
363 357
364 358 def _get_keyname(self, key, uid, prefix=''):
365 359 return '{0}{1}{2}_{3}'.format(
366 360 prefix, self.SETTINGS_PREFIX, key, uid)
367 361
368 362 def _make_dict_for_settings(self, qs):
369 363 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
370 364
371 365 issuetracker_entries = {}
372 366 # create keys
373 367 for k, v in qs.items():
374 368 if k.startswith(prefix_match):
375 369 uid = k[len(prefix_match):]
376 370 issuetracker_entries[uid] = None
377 371
378 372 def url_cleaner(input_str):
379 373 input_str = input_str.replace('"', '').replace("'", '')
380 374 input_str = bleach.clean(input_str, strip=True)
381 375 return input_str
382 376
383 377 # populate
384 378 for uid in issuetracker_entries:
385 379 url_data = qs.get(self._get_keyname('url', uid, 'rhodecode_'))
386 380
387 381 pat = qs.get(self._get_keyname('pat', uid, 'rhodecode_'))
388 382 try:
389 383 pat_compiled = re.compile(r'%s' % pat)
390 384 except re.error:
391 385 pat_compiled = None
392 386
393 387 issuetracker_entries[uid] = AttributeDict({
394 388 'pat': pat,
395 389 'pat_compiled': pat_compiled,
396 390 'url': url_cleaner(
397 391 qs.get(self._get_keyname('url', uid, 'rhodecode_')) or ''),
398 392 'pref': bleach.clean(
399 393 qs.get(self._get_keyname('pref', uid, 'rhodecode_')) or ''),
400 394 'desc': qs.get(
401 395 self._get_keyname('desc', uid, 'rhodecode_')),
402 396 })
403 397
404 398 return issuetracker_entries
405 399
406 400 def get_global_settings(self, cache=False):
407 401 """
408 402 Returns list of global issue tracker settings
409 403 """
410 404 defaults = self.global_settings.get_all_settings(cache=cache)
411 405 settings = self._make_dict_for_settings(defaults)
412 406 return settings
413 407
414 408 def get_repo_settings(self, cache=False):
415 409 """
416 410 Returns list of issue tracker settings per repository
417 411 """
418 412 if not self.repo_settings:
419 413 raise Exception('Repository is not specified')
420 414 all_settings = self.repo_settings.get_all_settings(cache=cache)
421 415 settings = self._make_dict_for_settings(all_settings)
422 416 return settings
423 417
424 418 def get_settings(self, cache=False):
425 419 if self.inherit_global_settings:
426 420 return self.get_global_settings(cache=cache)
427 421 else:
428 422 return self.get_repo_settings(cache=cache)
429 423
430 424 def delete_entries(self, uid):
431 425 if self.repo_settings:
432 426 all_patterns = self.get_repo_settings()
433 427 settings_model = self.repo_settings
434 428 else:
435 429 all_patterns = self.get_global_settings()
436 430 settings_model = self.global_settings
437 431 entries = all_patterns.get(uid, [])
438 432
439 433 for del_key in entries:
440 434 setting_name = self._get_keyname(del_key, uid)
441 435 entry = settings_model.get_setting_by_name(setting_name)
442 436 if entry:
443 437 Session().delete(entry)
444 438
445 439 Session().commit()
446 440
447 441 def create_or_update_setting(
448 442 self, name, val=Optional(''), type_=Optional('unicode')):
449 443 if self.repo_settings:
450 444 setting = self.repo_settings.create_or_update_setting(
451 445 name, val, type_)
452 446 else:
453 447 setting = self.global_settings.create_or_update_setting(
454 448 name, val, type_)
455 449 return setting
456 450
457 451
458 452 class VcsSettingsModel(object):
459 453
460 454 INHERIT_SETTINGS = 'inherit_vcs_settings'
461 455 GENERAL_SETTINGS = (
462 456 'use_outdated_comments',
463 457 'pr_merge_enabled',
464 458 'hg_use_rebase_for_merging',
465 459 'hg_close_branch_before_merging',
466 460 'git_use_rebase_for_merging',
467 461 'git_close_branch_before_merging',
468 462 'diff_cache',
469 463 )
470 464
471 465 HOOKS_SETTINGS = (
472 466 ('hooks', 'changegroup.repo_size'),
473 467 ('hooks', 'changegroup.push_logger'),
474 468 ('hooks', 'outgoing.pull_logger'),
475 469 )
476 470 HG_SETTINGS = (
477 471 ('extensions', 'largefiles'),
478 472 ('phases', 'publish'),
479 473 ('extensions', 'evolve'),
480 474 ('extensions', 'topic'),
481 475 ('experimental', 'evolution'),
482 476 ('experimental', 'evolution.exchange'),
483 477 )
484 478 GIT_SETTINGS = (
485 479 ('vcs_git_lfs', 'enabled'),
486 480 )
487 481 GLOBAL_HG_SETTINGS = (
488 482 ('extensions', 'largefiles'),
489 483 ('largefiles', 'usercache'),
490 484 ('phases', 'publish'),
491 485 ('extensions', 'hgsubversion'),
492 486 ('extensions', 'evolve'),
493 487 ('extensions', 'topic'),
494 488 ('experimental', 'evolution'),
495 489 ('experimental', 'evolution.exchange'),
496 490 )
497 491
498 492 GLOBAL_GIT_SETTINGS = (
499 493 ('vcs_git_lfs', 'enabled'),
500 494 ('vcs_git_lfs', 'store_location')
501 495 )
502 496
503 497 GLOBAL_SVN_SETTINGS = (
504 498 ('vcs_svn_proxy', 'http_requests_enabled'),
505 499 ('vcs_svn_proxy', 'http_server_url')
506 500 )
507 501
508 502 SVN_BRANCH_SECTION = 'vcs_svn_branch'
509 503 SVN_TAG_SECTION = 'vcs_svn_tag'
510 504 SSL_SETTING = ('web', 'push_ssl')
511 505 PATH_SETTING = ('paths', '/')
512 506
513 507 def __init__(self, sa=None, repo=None):
514 508 self.global_settings = SettingsModel(sa=sa)
515 509 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
516 510 self._ui_settings = (
517 511 self.HG_SETTINGS + self.GIT_SETTINGS + self.HOOKS_SETTINGS)
518 512 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
519 513
520 514 @property
521 515 @assert_repo_settings
522 516 def inherit_global_settings(self):
523 517 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
524 518 return setting.app_settings_value if setting else True
525 519
526 520 @inherit_global_settings.setter
527 521 @assert_repo_settings
528 522 def inherit_global_settings(self, value):
529 523 self.repo_settings.create_or_update_setting(
530 524 self.INHERIT_SETTINGS, value, type_='bool')
531 525
532 526 def get_global_svn_branch_patterns(self):
533 527 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
534 528
535 529 @assert_repo_settings
536 530 def get_repo_svn_branch_patterns(self):
537 531 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
538 532
539 533 def get_global_svn_tag_patterns(self):
540 534 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
541 535
542 536 @assert_repo_settings
543 537 def get_repo_svn_tag_patterns(self):
544 538 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
545 539
546 540 def get_global_settings(self):
547 541 return self._collect_all_settings(global_=True)
548 542
549 543 @assert_repo_settings
550 544 def get_repo_settings(self):
551 545 return self._collect_all_settings(global_=False)
552 546
553 547 @assert_repo_settings
554 548 def get_repo_settings_inherited(self):
555 549 global_settings = self.get_global_settings()
556 550 global_settings.update(self.get_repo_settings())
557 551 return global_settings
558 552
559 553 @assert_repo_settings
560 554 def create_or_update_repo_settings(
561 555 self, data, inherit_global_settings=False):
562 556 from rhodecode.model.scm import ScmModel
563 557
564 558 self.inherit_global_settings = inherit_global_settings
565 559
566 560 repo = self.repo_settings.get_repo()
567 561 if not inherit_global_settings:
568 562 if repo.repo_type == 'svn':
569 563 self.create_repo_svn_settings(data)
570 564 else:
571 565 self.create_or_update_repo_hook_settings(data)
572 566 self.create_or_update_repo_pr_settings(data)
573 567
574 568 if repo.repo_type == 'hg':
575 569 self.create_or_update_repo_hg_settings(data)
576 570
577 571 if repo.repo_type == 'git':
578 572 self.create_or_update_repo_git_settings(data)
579 573
580 574 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
581 575
582 576 @assert_repo_settings
583 577 def create_or_update_repo_hook_settings(self, data):
584 578 for section, key in self.HOOKS_SETTINGS:
585 579 data_key = self._get_form_ui_key(section, key)
586 580 if data_key not in data:
587 581 raise ValueError(
588 582 'The given data does not contain {} key'.format(data_key))
589 583
590 584 active = data.get(data_key)
591 585 repo_setting = self.repo_settings.get_ui_by_section_and_key(
592 586 section, key)
593 587 if not repo_setting:
594 588 global_setting = self.global_settings.\
595 589 get_ui_by_section_and_key(section, key)
596 590 self.repo_settings.create_ui_section_value(
597 591 section, global_setting.ui_value, key=key, active=active)
598 592 else:
599 593 repo_setting.ui_active = active
600 594 Session().add(repo_setting)
601 595
602 596 def update_global_hook_settings(self, data):
603 597 for section, key in self.HOOKS_SETTINGS:
604 598 data_key = self._get_form_ui_key(section, key)
605 599 if data_key not in data:
606 600 raise ValueError(
607 601 'The given data does not contain {} key'.format(data_key))
608 602 active = data.get(data_key)
609 603 repo_setting = self.global_settings.get_ui_by_section_and_key(
610 604 section, key)
611 605 repo_setting.ui_active = active
612 606 Session().add(repo_setting)
613 607
614 608 @assert_repo_settings
615 609 def create_or_update_repo_pr_settings(self, data):
616 610 return self._create_or_update_general_settings(
617 611 self.repo_settings, data)
618 612
619 613 def create_or_update_global_pr_settings(self, data):
620 614 return self._create_or_update_general_settings(
621 615 self.global_settings, data)
622 616
623 617 @assert_repo_settings
624 618 def create_repo_svn_settings(self, data):
625 619 return self._create_svn_settings(self.repo_settings, data)
626 620
627 621 def _set_evolution(self, settings, is_enabled):
628 622 if is_enabled:
629 623 # if evolve is active set evolution=all
630 624
631 625 self._create_or_update_ui(
632 626 settings, *('experimental', 'evolution'), value='all',
633 627 active=True)
634 628 self._create_or_update_ui(
635 629 settings, *('experimental', 'evolution.exchange'), value='yes',
636 630 active=True)
637 631 # if evolve is active set topics server support
638 632 self._create_or_update_ui(
639 633 settings, *('extensions', 'topic'), value='',
640 634 active=True)
641 635
642 636 else:
643 637 self._create_or_update_ui(
644 638 settings, *('experimental', 'evolution'), value='',
645 639 active=False)
646 640 self._create_or_update_ui(
647 641 settings, *('experimental', 'evolution.exchange'), value='no',
648 642 active=False)
649 643 self._create_or_update_ui(
650 644 settings, *('extensions', 'topic'), value='',
651 645 active=False)
652 646
653 647 @assert_repo_settings
654 648 def create_or_update_repo_hg_settings(self, data):
655 649 largefiles, phases, evolve = \
656 650 self.HG_SETTINGS[:3]
657 651 largefiles_key, phases_key, evolve_key = \
658 652 self._get_settings_keys(self.HG_SETTINGS[:3], data)
659 653
660 654 self._create_or_update_ui(
661 655 self.repo_settings, *largefiles, value='',
662 656 active=data[largefiles_key])
663 657 self._create_or_update_ui(
664 658 self.repo_settings, *evolve, value='',
665 659 active=data[evolve_key])
666 660 self._set_evolution(self.repo_settings, is_enabled=data[evolve_key])
667 661
668 662 self._create_or_update_ui(
669 663 self.repo_settings, *phases, value=safe_str(data[phases_key]))
670 664
671 665 def create_or_update_global_hg_settings(self, data):
672 666 largefiles, largefiles_store, phases, hgsubversion, evolve \
673 667 = self.GLOBAL_HG_SETTINGS[:5]
674 668 largefiles_key, largefiles_store_key, phases_key, subversion_key, evolve_key \
675 669 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS[:5], data)
676 670
677 671 self._create_or_update_ui(
678 672 self.global_settings, *largefiles, value='',
679 673 active=data[largefiles_key])
680 674 self._create_or_update_ui(
681 675 self.global_settings, *largefiles_store, value=data[largefiles_store_key])
682 676 self._create_or_update_ui(
683 677 self.global_settings, *phases, value=safe_str(data[phases_key]))
684 678 self._create_or_update_ui(
685 679 self.global_settings, *hgsubversion, active=data[subversion_key])
686 680 self._create_or_update_ui(
687 681 self.global_settings, *evolve, value='',
688 682 active=data[evolve_key])
689 683 self._set_evolution(self.global_settings, is_enabled=data[evolve_key])
690 684
691 685 def create_or_update_repo_git_settings(self, data):
692 686 # NOTE(marcink): # comma makes unpack work properly
693 687 lfs_enabled, \
694 688 = self.GIT_SETTINGS
695 689
696 690 lfs_enabled_key, \
697 691 = self._get_settings_keys(self.GIT_SETTINGS, data)
698 692
699 693 self._create_or_update_ui(
700 694 self.repo_settings, *lfs_enabled, value=data[lfs_enabled_key],
701 695 active=data[lfs_enabled_key])
702 696
703 697 def create_or_update_global_git_settings(self, data):
704 698 lfs_enabled, lfs_store_location \
705 699 = self.GLOBAL_GIT_SETTINGS
706 700 lfs_enabled_key, lfs_store_location_key \
707 701 = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)
708 702
709 703 self._create_or_update_ui(
710 704 self.global_settings, *lfs_enabled, value=data[lfs_enabled_key],
711 705 active=data[lfs_enabled_key])
712 706 self._create_or_update_ui(
713 707 self.global_settings, *lfs_store_location,
714 708 value=data[lfs_store_location_key])
715 709
716 710 def create_or_update_global_svn_settings(self, data):
717 711 # branch/tags patterns
718 712 self._create_svn_settings(self.global_settings, data)
719 713
720 714 http_requests_enabled, http_server_url = self.GLOBAL_SVN_SETTINGS
721 715 http_requests_enabled_key, http_server_url_key = self._get_settings_keys(
722 716 self.GLOBAL_SVN_SETTINGS, data)
723 717
724 718 self._create_or_update_ui(
725 719 self.global_settings, *http_requests_enabled,
726 720 value=safe_str(data[http_requests_enabled_key]))
727 721 self._create_or_update_ui(
728 722 self.global_settings, *http_server_url,
729 723 value=data[http_server_url_key])
730 724
731 725 def update_global_ssl_setting(self, value):
732 726 self._create_or_update_ui(
733 727 self.global_settings, *self.SSL_SETTING, value=value)
734 728
735 729 def update_global_path_setting(self, value):
736 730 self._create_or_update_ui(
737 731 self.global_settings, *self.PATH_SETTING, value=value)
738 732
739 733 @assert_repo_settings
740 734 def delete_repo_svn_pattern(self, id_):
741 735 ui = self.repo_settings.UiDbModel.get(id_)
742 736 if ui and ui.repository.repo_name == self.repo_settings.repo:
743 737 # only delete if it's the same repo as initialized settings
744 738 self.repo_settings.delete_ui(id_)
745 739 else:
746 740 # raise error as if we wouldn't find this option
747 741 self.repo_settings.delete_ui(-1)
748 742
749 743 def delete_global_svn_pattern(self, id_):
750 744 self.global_settings.delete_ui(id_)
751 745
752 746 @assert_repo_settings
753 747 def get_repo_ui_settings(self, section=None, key=None):
754 748 global_uis = self.global_settings.get_ui(section, key)
755 749 repo_uis = self.repo_settings.get_ui(section, key)
756 750
757 751 filtered_repo_uis = self._filter_ui_settings(repo_uis)
758 752 filtered_repo_uis_keys = [
759 753 (s.section, s.key) for s in filtered_repo_uis]
760 754
761 755 def _is_global_ui_filtered(ui):
762 756 return (
763 757 (ui.section, ui.key) in filtered_repo_uis_keys
764 758 or ui.section in self._svn_sections)
765 759
766 760 filtered_global_uis = [
767 761 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
768 762
769 763 return filtered_global_uis + filtered_repo_uis
770 764
771 765 def get_global_ui_settings(self, section=None, key=None):
772 766 return self.global_settings.get_ui(section, key)
773 767
774 768 def get_ui_settings_as_config_obj(self, section=None, key=None):
775 769 config = base.Config()
776 770
777 771 ui_settings = self.get_ui_settings(section=section, key=key)
778 772
779 773 for entry in ui_settings:
780 774 config.set(entry.section, entry.key, entry.value)
781 775
782 776 return config
783 777
784 778 def get_ui_settings(self, section=None, key=None):
785 779 if not self.repo_settings or self.inherit_global_settings:
786 780 return self.get_global_ui_settings(section, key)
787 781 else:
788 782 return self.get_repo_ui_settings(section, key)
789 783
790 784 def get_svn_patterns(self, section=None):
791 785 if not self.repo_settings:
792 786 return self.get_global_ui_settings(section)
793 787 else:
794 788 return self.get_repo_ui_settings(section)
795 789
796 790 @assert_repo_settings
797 791 def get_repo_general_settings(self):
798 792 global_settings = self.global_settings.get_all_settings()
799 793 repo_settings = self.repo_settings.get_all_settings()
800 794 filtered_repo_settings = self._filter_general_settings(repo_settings)
801 795 global_settings.update(filtered_repo_settings)
802 796 return global_settings
803 797
804 798 def get_global_general_settings(self):
805 799 return self.global_settings.get_all_settings()
806 800
807 801 def get_general_settings(self):
808 802 if not self.repo_settings or self.inherit_global_settings:
809 803 return self.get_global_general_settings()
810 804 else:
811 805 return self.get_repo_general_settings()
812 806
813 807 def get_repos_location(self):
814 808 return self.global_settings.get_ui_by_key('/').ui_value
815 809
816 810 def _filter_ui_settings(self, settings):
817 811 filtered_settings = [
818 812 s for s in settings if self._should_keep_setting(s)]
819 813 return filtered_settings
820 814
821 815 def _should_keep_setting(self, setting):
822 816 keep = (
823 817 (setting.section, setting.key) in self._ui_settings or
824 818 setting.section in self._svn_sections)
825 819 return keep
826 820
827 821 def _filter_general_settings(self, settings):
828 822 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
829 823 return {
830 824 k: settings[k]
831 825 for k in settings if k in keys}
832 826
833 827 def _collect_all_settings(self, global_=False):
834 828 settings = self.global_settings if global_ else self.repo_settings
835 829 result = {}
836 830
837 831 for section, key in self._ui_settings:
838 832 ui = settings.get_ui_by_section_and_key(section, key)
839 833 result_key = self._get_form_ui_key(section, key)
840 834
841 835 if ui:
842 836 if section in ('hooks', 'extensions'):
843 837 result[result_key] = ui.ui_active
844 838 elif result_key in ['vcs_git_lfs_enabled']:
845 839 result[result_key] = ui.ui_active
846 840 else:
847 841 result[result_key] = ui.ui_value
848 842
849 843 for name in self.GENERAL_SETTINGS:
850 844 setting = settings.get_setting_by_name(name)
851 845 if setting:
852 846 result_key = 'rhodecode_{}'.format(name)
853 847 result[result_key] = setting.app_settings_value
854 848
855 849 return result
856 850
857 851 def _get_form_ui_key(self, section, key):
858 852 return '{section}_{key}'.format(
859 853 section=section, key=key.replace('.', '_'))
860 854
861 855 def _create_or_update_ui(
862 856 self, settings, section, key, value=None, active=None):
863 857 ui = settings.get_ui_by_section_and_key(section, key)
864 858 if not ui:
865 859 active = True if active is None else active
866 860 settings.create_ui_section_value(
867 861 section, value, key=key, active=active)
868 862 else:
869 863 if active is not None:
870 864 ui.ui_active = active
871 865 if value is not None:
872 866 ui.ui_value = value
873 867 Session().add(ui)
874 868
875 869 def _create_svn_settings(self, settings, data):
876 870 svn_settings = {
877 871 'new_svn_branch': self.SVN_BRANCH_SECTION,
878 872 'new_svn_tag': self.SVN_TAG_SECTION
879 873 }
880 874 for key in svn_settings:
881 875 if data.get(key):
882 876 settings.create_ui_section_value(svn_settings[key], data[key])
883 877
884 878 def _create_or_update_general_settings(self, settings, data):
885 879 for name in self.GENERAL_SETTINGS:
886 880 data_key = 'rhodecode_{}'.format(name)
887 881 if data_key not in data:
888 882 raise ValueError(
889 883 'The given data does not contain {} key'.format(data_key))
890 884 setting = settings.create_or_update_setting(
891 885 name, data[data_key], 'bool')
892 886 Session().add(setting)
893 887
894 888 def _get_settings_keys(self, settings, data):
895 889 data_keys = [self._get_form_ui_key(*s) for s in settings]
896 890 for data_key in data_keys:
897 891 if data_key not in data:
898 892 raise ValueError(
899 893 'The given data does not contain {} key'.format(data_key))
900 894 return data_keys
901 895
902 896 def create_largeobjects_dirs_if_needed(self, repo_store_path):
903 897 """
904 898 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
905 899 does a repository scan if enabled in the settings.
906 900 """
907 901
908 902 from rhodecode.lib.vcs.backends.hg import largefiles_store
909 903 from rhodecode.lib.vcs.backends.git import lfs_store
910 904
911 905 paths = [
912 906 largefiles_store(repo_store_path),
913 907 lfs_store(repo_store_path)]
914 908
915 909 for path in paths:
916 910 if os.path.isdir(path):
917 911 continue
918 912 if os.path.isfile(path):
919 913 continue
920 914 # not a file nor dir, we try to create it
921 915 try:
922 916 os.makedirs(path)
923 917 except Exception:
924 918 log.warning('Failed to create largefiles dir:%s', path)
General Comments 0
You need to be logged in to leave comments. Login now